読者です 読者をやめる 読者になる 読者になる

Volta 探検 (4): P/Invoke

.NET

いわゆる P/Invoke ですが,今回の“ネイティブコード”は JavaScript にあたります.以下のように Import 属性でコードごと埋め込むことができます.

public static class JSUtil
{
    private const string setTimeoutExCode =
    @"function(handler, delayTime, arg)
    {
        var func = function() { handler(arg); };
        setTimeout(func, delayTime);
    }";
    [Import(setTimeoutExCode)]
    public static extern void SetTimeout(Action<int> handler, int delayTime, int arg);

    // こうすると実行時エラー
    // public static extern void SetTimeout<T>(Action<T> handler, int delayTime, T arg);
}

これを使って先ほどのコードを書き直し.

using System;
using Microsoft.LiveLabs.Volta.Html;
using Microsoft.LiveLabs.Volta;

namespace FizzBuzzVolta
{
    public static class JSUtil
    {
        private const string setTimeoutExCode =
        @"function(handler, delayTime, arg)
        {
            var func = function() { handler(arg); };
            setTimeout(func, delayTime);
        }";
        [Import(setTimeoutExCode)]
        public static extern void SetTimeout(Action<int> handler, int delayTime, int arg);
    }

    public partial class VoltaPage1 : Page
    {
        public VoltaPage1()
        {
            InitializeComponent();

            var button = new Button() { InnerText = "FizzBuzz!" };
            var input = new Input() { Value = "1000" };

            var para = new Paragraph()
            {
                button,
                new Span(){ InnerText = "upto: "},
                input,
            };

            Func<int, string> fizzbuzz = i =>
                {
                    if (i % 15 == 0) return "FizzBuzz";
                    else if (i % 5 == 0) return "Buzz";
                    else if (i % 3 == 0) return "Fizz";
                    else return i.ToString();
                };

            button.Click += () =>
            {
                int upto = 0;
                if (!int.TryParse(input.Value, out upto)) return;

                this.Document.Body.Add(new Table() { new TableBody() { Id = "FizzBuzzTable" } });

                Action<int> callback = null;
                callback = (int fizzBuzzCounter) =>
                {
                    if (fizzBuzzCounter < upto)
                    {
                        var tb = this.Document.GetById<TableBody>("FizzBuzzTable");
                        tb.Add(new TableRow()
                                {
                                    new TableCell() { InnerText = fizzBuzzCounter.ToString() },
                                    new TableCell() { InnerText = fizzbuzz(fizzBuzzCounter) }
                                });

                        JSUtil.SetTimeout(callback, 0, fizzBuzzCounter + 1);
                    }
                };

                JSUtil.SetTimeout(callback, 0, 1);
            };

            this.Document.Body.Add(para);
        }
    }
}

一応,C# コードから JavaScript (Native) コードの呼び出しと,その逆両方がうまくいっているように見えます.
雰囲気としてはこんな感じでしょうか.

CLR on x86 CLR on Volta on JavaScript
Platform Invoke .NET のメソッド呼び出しをアンマネージ関数呼び出し (関数ポインタへの jump) にマッピングできる .NET のメソッド呼び出しを JavaScript コードにマッピングできる
Reverse Platform Invoke (Callback) マネージ関数を関数ポインタ (Thunk) として公開できる マネージ関数を JavaScript の関数オブジェクトとして公開できる

参考

いつかはこういった“ネイティブコード“とうまく連携させてみたいところ.