Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What is going on with this article?
@RyotaMurohoshi

BlazorにおけるJavaScript相互運用機能をちょっとだけコードリーディング

More than 1 year has passed since last update.

この投稿では、BlazorのJavaSciript相互運用機能のコードを読んでいきます。

を読んでいることを前提とします。

vesrion : 3.0.100-preview5-011568

前提の確認

の確認を行います。

JavaScript相互運用機能の使い方はこのような感じです。

@page "/hello-jsinterop"
@inject IJSRuntime jsRuntime;

<button onclick="@Log">Console.Log with JavaScript</button>

@functions {

    void Log()
    {
        jsRuntime.InvokeAsync<string>("console.log", "hello world JavaScript Interop");
    }
}

@injectはサービスのインジェクトを行います。Componentのrazorファイル内で、IJSRuntimeをインジェクトし、そのインスタンスを使うことで、JavaScriptを呼び出すことができます。

Blazorでは、

  • HttpClient
  • IJSRuntime
  • IUriHelper

はデフォルトで作成されていて、@injectをするだけでサービスをインジェクトすることができます。自分で作ったサービスは、デフォルトで作成されないので設定をする必要があります。

コードリーディング

それではコードを見ていきます。リポジトリは、「aspnet/AspNetCore」です。Blazorのコードは「AspNetCore/src/Components/Blazor/」にあります。

IJSRuntimeはデフォルトで作成されています。ですので、まず作成している場所を探しましょう。

dotnet new blazor -oコマンドでBlazorプロジェクトを作成した時のProgram.csは以下の通りです。

using Microsoft.AspNetCore.Blazor.Hosting;

namespace YourNameSpace
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
            BlazorWebAssemblyHost.CreateDefaultBuilder()
                .UseBlazorStartup<Startup>();
    }
}

BlazorWebAssemblyHostはこのようになっています。CreateDefaultBuilderメソッドは、WebAssemblyHostBuilderのインスタンスを生成しています。

public static class BlazorWebAssemblyHost
{
    /// <summary>
    /// Creates a an instance of <see cref="IWebAssemblyHostBuilder"/>.
    /// </summary>
    /// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
    public static IWebAssemblyHostBuilder CreateDefaultBuilder()
    {
        return new WebAssemblyHostBuilder();
    }
}

次に、WebAssemblyHostBuilderをみてみます。

WebAssemblyHostBuilderCreateServiceProviderメソッド内に注目します。

private void CreateServiceProvider()
{
    var services = new ServiceCollection();
    services.AddSingleton(_BrowserHostBuilderContext);
    services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
    services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
    services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
    services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
    services.AddSingleton<HttpClient>(s =>
    {
        // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
        var uriHelper = s.GetRequiredService<IUriHelper>();
        return new HttpClient
        {
            BaseAddress = new Uri(WebAssemblyUriHelper.Instance.GetBaseUri())
        };
    });

    foreach (var configureServicesAction in _configureServicesActions)
    {
        configureServicesAction(_BrowserHostBuilderContext, services);
    }

    var builder = _serviceProviderFactory.CreateBuilder(services);
    _appServices = _serviceProviderFactory.CreateServiceProvider(builder);
}
  • HttpClient
  • IJSRuntime
  • IUriHelper

はデフォルトで作成されていますが、ここで作成していることがわかりました。Instanceにだけ注目するとここですね。

    services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);

次は、WebAssemblyJSRuntime.Instanceをみていきましょう。

internal static class WebAssemblyJSRuntime
{
    public static readonly MonoWebAssemblyJSRuntime Instance = new MonoWebAssemblyJSRuntime();
}

WebAssemblyJSRuntime.Instanceは、MonoWebAssemblyJSRuntime型のstaticフィールドです。

MonoWebAssemblyJSRuntimeは今までみたきたものと別のアセンブリで、「Mono.WebAssembly.Interop」にあります。コードはこちら

MonoWebAssemblyJSRuntimeの各メソッドは内部でInternalCallsを使っています。

InternalCallsはexternとなっています。コメントによるとMono配布のdriver.cの中に実装があるのではないでしょうか。

internal class InternalCalls
{
    // The exact namespace, type, and method names must match the corresponding entries
    // in driver.c in the Mono distribution

    // We're passing asyncHandle by ref not because we want it to be writable, but so it gets
    // passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones.
    [MethodImpl(MethodImplOptions.InternalCall)]
    public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);

    [MethodImpl(MethodImplOptions.InternalCall)]
    public static extern TRes InvokeJSUnmarshalled<T0, T1, T2, TRes>(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2);
}

まとめ

簡単にですが、JavaScript相互運用機能・IJSRuntimeのコードを追ってみました。

主要な実装はexternでC言語で書かれているようです。

また機会があれば追ってみます。

6
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
RyotaMurohoshi
プログラミングが大好きで、 C#が大好きで、 .NETが大好きで、 LINQが大好きで、 JVM言語が大好きで、 ゲームで遊ぶことが大好きで、 ゲーム開発が大好きで、 頑張るのが大好きで、 Unityが大好きだったから...!

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
6
Help us understand the problem. What is going on with this article?