Help us understand the problem. What is going on with this article?

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

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

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

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした