LoginSignup
11
6

More than 3 years have passed since last update.

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

Posted at

この投稿では、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言語で書かれているようです。

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

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6