ぷろじぇくと、みすじら。

DNS-SD(Bonjour)をUniversal Windows Platformから利用してサービスを探す

Created at:

Windows 10からDNS-SD/mDNS、いわゆるAppleのBonjourをネイティブサポートするようになりました。 要するに何ができるかというとIPアドレスやホスト名を知らなくても同じネットワークにいるデバイスとサービスを探すことができる仕組みです。

例えば、iTunesが他のコンピューターのiTunes 共有ライブラリを見つけるといったことに利用されています。そのちょっと便利な機能をUWPのAPIを通して利用できるようになっています。

アプリからDNS-SDでサービスを探す

さて、どうやって使うかですがまあまあ簡単で、通常のデバイスを列挙するのと同様にDeviceInformation クラスを利用します……が、列挙しようと思ってFindAllAsyncメソッドを使うと何も返ってこず(awaitすると進まない)、暫くするとエラーが出ます。

この挙動が正しいのかどうかよくわかりませんが、ともかく何やらうまく動きません。

そこでDeviceInformation.CreateWatcher メソッドを利用します。 このメソッドはデバイスがネットワークに追加されたり削除されたりというのを監視する方法です。この方法でならば列挙できます。

Watcherを作ってStartメソッドを呼び、監視を始めるとデバイスが見つかりAddedイベントが呼び出されますのでそこで適当に情報を保持しておけばよいです。 ちなみに監視して列挙終了時の EnumerationCompleted イベントが発生しないので完了を待つというのはやめた方がいいでしょう(多分それがFindAllAsyncが返ってこない原因な気がします)。

とりあえず、ネットワークにあるAndroid TV(正確にはAndroid TVが持つリモコン用サービス)を列挙するソースコードはこんな感じなります。

using Windows.Devices.Enumeration;

// ...

// DNS-SDのサービス名
var serviceName = "_androidtvremote._tcp";
// DNS-SDを利用する
var protocolId = "{4526e8c1-8aac-4153-9b16-55e86ada0e54}";
// デバイス検索文字列
var aqsFilter = $"System.Devices.AepService.ProtocolId:={protocolId} AND System.Devices.Dnssd.Domain:=\"local\" AND System.Devices.Dnssd.ServiceName:=\"{serviceName}\"";
// 取得するプロパティ
var properties = new[] {
    "System.Devices.Dnssd.HostName", // ホスト名 (自称)
    "System.Devices.IpAddress", // IPアドレスの配列(string[])
};

var watcher = DeviceInformation.CreateWatcher(aqsFilter, properties, DeviceInformationKind.AssociationEndpointService);
watcher.Added += (sender, args) =>
{
    Debug.WriteLine("Added: " + args.Id);
    foreach (var prop in args.Properties)
    {
        var value = prop.Value is string[] ? (String.Join(", ", (string[])prop.Value)) : prop.Value + "";
        Debug.WriteLine($"  - {prop.Key}: {value}");
    }
};
watcher.Updated += (sender, args) =>
{
    Debug.WriteLine("Updated: " + args.Id);
    foreach (var prop in args.Properties)
    {
        var value = prop.Value is string[] ? (String.Join(", ", (string[])prop.Value)) : prop.Value + "";
        Debug.WriteLine($"  - {prop.Key}: {value}");
    }
};
watcher.Start();

実行するとこんな感じにホスト名やIPアドレスなどの情報が取れます。

DnsSd#KJ-55X8500C._androidtvremote._tcp.local#0
  - System.ItemNameDisplay: KJ-55X8500C
  - System.Devices.DeviceInstanceId: 
  - System.Devices.Icon: 
  - System.Devices.GlyphIcon: 
  - System.Devices.InterfaceEnabled: 
  - System.Devices.IsDefault: 
  - System.Devices.PhysicalDeviceLocation: 
  - System.Devices.Aep.CanPair: False
  - System.Devices.Aep.IsPaired: False
  - {A35996AB-11CF-4935-8B61-A6761081ECDF} 18: 
  - {A35996AB-11CF-4935-8B61-A6761081ECDF} 13: 
  - System.Devices.Dnssd.HostName: Android.local
  - System.Devices.IpAddress: 192.168.1.100

アプリはこの情報をもとにサーバー(サービス)に接続すれば、相手の名前をあらかじめ知っていなくてもアクセスできる、というわけです。

手順をおさらいすると、DeviceWatcherを作るCreateWatcherメソッドの引数に渡すAQS文字列と呼ばれる検索フィルター文字列に、 DNS-SDプロトコル({4526e8c1-8aac-4153-9b16-55e86ada0e54})を使うことを指定して、さらにDNS-SDのサービス名(_daap.tcpなど)を指定して、検索を実行すれば見つかるという形になっています。例えばサービス名に_daap.tcpを指定するとネットワーク上のiTunesの共有ライブラリを探すことができます。

ちなみにこのUniversalなAPIはいわゆる従来のデスクトップアプリケーションからも呼び出すことができます。

制限事項

得られたホスト名(.localで終わる名前)をHttpClientなどに渡しても名前解決されますが、たまに失敗するのでIPアドレスを使った方がいいかもしれません。

なお、どうやら試した範囲ではサービス名を指定せずにネットワークに存在するサービスをすべて列挙する、ということはできないようです。System.Devices.Dnssd.ServiceNameを指定しないで検索すると何も返ってこないことに注意が必要です。

また、Hyper-Vが有効の場合にも動作しないという話もあります。

ChakraCoreをビルドしてC#から使うはじめの一歩

Created at:

ついにChakraCoreが公開されたので、早速ビルドしてC#からとりあえずハローしてみます。

ChakraCore is 何

とその前にChakraCoreのおさらいです。

まずChakraCoreはInternet ExplorerやEdgeで使われているJavaScriptエンジン ChakraのWindows固有の機能を外したライブラリです。要するにV8とかJavaScriptCoreみたいなものです。

オープンソースになった後のロードマップが公開され、いろいろ書いてあります。

ChakraCoreのメリット

従来のChakraはWindowsまたはInternet Explorerと共にバージョンが管理されていました。 つまり現在、ChakraにはInternet Explorer 9, 10, 11(Windows Vista/7/2008/2008 R2)、Windows 8.1、10、10 Version 1511というバリエーションがあることになり、 それをアプリケーションに組み込んで利用したい場合にバージョンの差異が発生することになります。

そこでChakraCoreはWindowsやブラウザから分離したJavaScriptのエンジンとなったので、 アプリケーションに独立して組み込むことができるようになり、環境ごとのバージョンの差異に悩まされないで済むようになります。

そもそも従来のWindowsではWindows Scripting Hostを経由してエンジンを組み込むことができましたが、APIもCOMベースでJavaScriptのエンジンを使うということ以外を考慮していたりして使いづらく、エンジンも古いのでモダンなJavaScriptを動かすことができなかったという話もあります。

ChakraCoreをビルドする

2016年1月14日現在、WindowsサポートのみなのでGitHubのリポジトリをcloneし、BuildディレクトリにあるChakraCore.slnをVisual Studio 2015で開いてビルドすればよいです。

と、簡単そうな気がしたのですが日本語環境(CP932がデフォルト環境)ではソースコードがUnicodeじゃないよというWarning(C4819)が出て、WarningがErrorとして扱われてビルドが失敗します。幸い数は少ないのでBOM付きUTF-8で適当に保存しなおしましょう。

ビルドすると Build\VcBuild\bin\x64_debug などに ChakraCore.dll が生成されます。

ChakraCoreをC#から使う

ChakraCoreを無事ビルドできたらC#からChakraCoreを使ってみます。

ChakraCoreはJSRT API(JavaScript Runtime API)というAPIを持っているのでそれを利用してChakraCoreの機能を呼び出します。ちなみにJSRTはChakraCoreよりも以前からWindows 10以降(またはInternet Explorer 11以降)ではChakraを利用する手段として提供されています。

詳しくはMSDNのJavaScript ランタイムのホスト処理に書いてあります。

ただしJSRTは.NET Framework向けのAPIではないアンマネージドなAPIなため、P/Invokeでの呼び出しを行う必要があります。 幸いにもGitHubにChakra-Samples@Microsoftというリポジトリがあり、JSRTのC#のバインディングがすでに作られているのでこれを使います(自分で書こうとするとだいぶ大変です)。

というわけでChakra-Samplesをcloneやダウンロードしておきます。Chakra-SamplesにはHello WorldやHostingサンプルがあるのでまあそれでいいという話もあるのですが、最小限の構成を作ってみることにします。

準備

まず初めに適当にコンソールアプリケーションのプロジェクトを作ります。

プロジェクトを作ったら先ほどビルドしたChakraCore.dllをプロジェクトに追加し、プロパティでビルド時にコピーされるように設定します。

ちなみに普通にプロジェクトを作るとPrefer 32bitということで32bitプロセスとして起動されるのでx86版DLLをコピーしておきましょう。

次にChakra-SamplesからChakraCore Samples\JSRT Hosting Samples\C#\ChakraCoreHostにあるHostingフォルダをプロジェクトにコピーします。これがJSRTのバインディングです。

プロジェクト構成

ChakraCoreを実行してみる

というわけでまずはランタイム(=実行エンジン)を作り、実行用のコンテキストを作って実行に備えます。

// ランタイムを作る
JavaScriptRuntime runtime;
Native.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null, out runtime);

// 実行コンテキストを作る
var context = runtime.CreateContext();

// 現在のスレッドの実行コンテキストをセットする
Native.JsSetCurrentContext(context);

次に実際にJavaScriptのコードを実行します。

// 実行するスクリプト
var script = @"
    class Greeter {
        hello() { return 'コンニチハ!'; }
    }

    new Greeter().hello();
";

// スクリプトのソースの位置を記録するためのコンテキスト
var currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);
// スクリプトを実行する
JavaScriptValue result;
Native.JsRunScript(script, currentSourceContext++, "", out result);

JsRunScriptメソッドはランタイムもコンテキストも渡さず、現在のスレッドのコンテキストから良しなに実行するので少し不思議な感じがしますね。

正常に実行できれば戻り値がresult変数に格納されます。戻り値はJavaScriptの値となっているのでそこからさらにCLRオブジェクトへ変換します。ToStringメソッドがお手軽に変換してくれるので任せましょう。

// 戻り値をJavaScriptの値からCLRのStringに変換する
// ちなみにConvertToStringメソッドはJavaScriptのStringなので注意。
var resultString = result.ToString(); // Native.JsStringToPointer + Marshal.PtrToStringUni

// 出力
Console.WriteLine(resultString);

最後はお片付けです。

// 後片付け
Native.JsSetCurrentContext(JavaScriptContext.Invalid);
runtime.Dispose(); // IDisposableなのでusingでもいい

というわけでここまでのコードをまとめるとこんな感じ。

実行すると「コンニチハ!」という出力が出てくるかと思います。割とお手軽ですね。

なお実行してBadImageFormatExceptionAn attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)と言われたらそれはx86/x64のDLLを間違えている可能性があります。

まとめ

もちろんエラーハンドリングや.NETからJavaScript側へオブジェクトの公開などもありますがとりあえず今回はここまでです。

意外と簡単に動かせるようになっているのでアプリケーションに組み込み用途にはいいかなと思うものの、ホストとChakraCoreとのやり取りが増えると割と高度なことを必要とするのでちょっと難易度高いかもという感じもします。

Visual Studio CodeのシンタックスハイライトをWebページで使う

Created at:

Visual Studio Codeのエディタ部分はVisual Studio Codeがリリースされるよりも前からいろいろなところで使われていて、 最初はVisual Studio Online "Monaco"のエディタ部分として公開されていました。

それから徐々にMicrosoftの中での利用範囲が広がりTypeScriptのPlaygroundやWinJSのPlayground、 ちょっと変わったところではInternet Explorer/EdgeのF12開発者ツールの中でも使われていたりします。

そして月日は流れて、Visual Studio Codeがオープンソースになってついにエディタ部分(Monaco Editor)が公開されました。

ということでそれを使う方法を調べていたのですが、たまたまシンタックスハイライトの機能が備わっているのを発見したので単体でも使えるようにするライブラリを作りました。

mayuki/Mimosa - GitHub

使い方

GitHubのReleasesに一式を固めたものがあるのでそれをとってきて適当に展開します。

<script src="/shared/js/mimosa/vs/loader.js"></script>
<script src="/shared/js/mimosa/mimosa.min.js"></script>
<script>
    require.config({
        baseUrl: '/shared/js/mimosa/', // MimosaとMonacoの入っているディレクトリへのパス
    });

    // 自動で pre.vs[data-lang] な要素を探してシンタックスハイライトを適用する
    Mimosa.initialize();
</script>

こんな感じでMimosaを読み込ませるものをページに書いておきます。 loaderはVSCode Loaderなのですが、これがほかのrequire機構と被ると何が起きるのかは謎です。

そして以下のように対象となるソースコードを pre 要素にして vs クラス (とスタイルのために monaco-editor)をつけておきます。 あと data-lang 属性に言語名を指定する必要があるのでそれも指定します。 対応している言語名はcss, html, javascript,...csharp, bat, powershell, ...といった感じです。

<pre data-lang="csharp" class="monaco-editor vs">
class A
{
    public async Task<int> Hoge()
    {
        await Task.Delay(1000);
        return 10;
    }
}</pre>

そうすると以下のような感じでシンタックスハイライトが適用されます。

class A
{
    public async Task<int> Hoge()
    {
        await Task.Delay(1000);
        return 10;
    }
}

ちなみに vs-dark クラスをつけておくと黒背景になじむカラーリングになります。

class A
{
    public async Task<int> Hoge()
    {
        await Task.Delay(1000);
        return 10;
    }
}

備考

まとめ

どうぞご利用ください。

次回はMonaco Editorを使う方法について書く予定があるとかないとか(ビルド方法とかはそっちで…)。

JavaScriptエンジン"Chakra"がオープンソースになる

Created at:

Microsoft Edge’s JavaScript engine to go open-sourceということでInternet ExplorerのちのEdgeのJavaScriptエンジンであるChakraをオープンソースにします、というアナウンスが。

Node.jsのスクリプトバックエンドをChakraにする魔改造を公開していたりしたので、いずれ来るだろうなあと思っていたのでついにという感じですね。

要するに…

小さいとか軽量とか書いてもいるのでChakraCoreはV8のように、何かに組み込んで使ってほしいという雰囲気がありますね。まあ何はともあれ楽しみです。

RoslynをT4テンプレート内で使う

Created at:

Visual StudioにはT4テンプレートというソースコードを生成する機能があるのですが、その中からRoslynを使おうというお話です。

T4テンプレートはVisual Studioと統合されていることとC#でテンプレートのコードを書いて、C#, VB, TypeScriptなどのコードを吐き出せます。

例えば…

のような感じで、アセンブリを読み込んでコードを生成するという使い方はよく見かけます(プロキシコードの生成とか)。

アセンブリを読み込んでコードを生成するということは「アセンブリがコンパイルされている」ことが前提になります。

例えばプロキシクラスみたいなものはそれで問題ないので、大体はそれでいいのですが「元になるコードと生成したコードを同一のアセンブリに含めたい」場合にはニワトリ卵というか「元になるコードをコンパイル」「コンパイル結果をもとにコード生成」「コード生成を含めてコンパイル」という手順が必要です。

もちろんリフレクションとかファクトリとか登録する式にすればコード生成いらないよね?という場合もあると思いますが、そこはまあ要件しだいというやつです。生成したいときもある、はず…。

例えばEnvDTEを避ける

そんなわけで「コンパイルしてできたアセンブリを元にコードを生成」するのではなく、「現在のコードを元にコードを生成」したいというときはどうするのかというとコードの構造を読み取るためにVisual Studioにアクセスする方法をとります。

一般的にT4テンプレートからVisual StudioにアクセスするにはEnvDTEというかっこいいオブジェクトモデルと仲良くすることになります。EnvDTEはVisual Studioの機能を公開するCOMライブラリなのなので、絶妙な使いづらさがあります。何よりEnvDTEはVisual Studioにくっついている都合、ConsoleApplicationやLINQPadなどで試しに書いてみるというのが難易度高いです。

まあサンプルコードを書く気にならない程度には面倒だということです。

そこでVisual Studio 2015以降ではコンパイラー基盤がRoslynになっているのでそれを使えばいいのではというのが今回のアイデアです。

T4の中でRoslynを使ってコードを読み込む

さてどうやってRoslynを使って読み込むのかという話ですが、残念なことにVisual Studio 2015の内部で「現在動いているRoslynのインスタンス」に直接アクセスすることはできません。

ですが、Visual Studio 2015に含まれているRoslynを使うことはできます。Roslynは C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies におかれているので、それらしいアセンブリを参照して名前空間をインポートします。

<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.Workspaces.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.Workspaces.Desktop.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\System.Collections.Immutable.dll" #>

<#@ import namespace="Microsoft.CodeAnalysis" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
<#@ import namespace="Microsoft.CodeAnalysis.MSBuild" #>

これで使えるようになります!

あとは単純にRoslynを使ってソリューションを読み込んで、コードを解析すればいい感じに処理できるでしょう。

と言ってもいきなりT4でRoslynプログラミングはそれはそれで難しいのでConsoleApplicationかLINQPadで書いてみるのがよいでしょう。

T4の外でコードを書いてみる

とりあえずどこかのC#プロジェクトにあるクラスの一覧を適当に出力する、というコードを書いてみることにします。Roslynのコードの詳しいことは今回割愛しますので別途調べてください。

というわけでLINQPadやConsoleApplicationで動きそうな単純なコードは以下のようになります。

void Main()
{
    // 対象となるプロジェクトの.csprojのパス
    var projPath = @"C:\Users\Tomoyo\Documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.csproj";
    var classNames = GetClassNamesAsync(projPath).Result;
    classNames.Dump();
}

// Roslynを使ってプロジェクトのコードからクラスのコンストラクタ定義を引っこ抜く
async Task<List<string>> GetClassNamesAsync(string csprojPath)
{
    // MSBuildのワークスペース(環境みたいなもの)を作って、プロジェクトファイルを開く
    var workspace = MSBuildWorkspace.Create();
    var project = await workspace.OpenProjectAsync(csprojPath);

    // ソースコードをコンパイル的なことをする(出力するわけではなくて内部的なデータを作るやつ)
    // これでコードをパースした結果を得ることができるようになる
    var compilation = await project.GetCompilationAsync();

    var classNames = new List<string>();
    // シンタックスツリーをファイル単位で適当になめていく
    foreach (var syntaxTree in compilation.SyntaxTrees)
    {
        // セマンティックモデル(シンタックスツリーは文法で、それに対応する「コード的な意味」)を取得する
        var semModel = compilation.GetSemanticModel(syntaxTree);

        // シンタックスツリーからクラス定義のシンタックス(記述)を引っ張り出して、
        // セマンティックモデルに問い合わせることでクラス定義(意味)を引っ張り出して、ふにゃふにゃ処理する。
        classNames.AddRange(
            syntaxTree.GetRoot()
                .DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .Select(x => semModel.GetDeclaredSymbol(x))
                .Select(x => x.ToDisplayString())
        );
    }

    return classNames;
}

実行結果はこんな感じ。

LINQPad

なおこのコードはMicrosoft.CodeAnalysis.CSharp.WorkspaceというNuGetパッケージをインストールすれば動くでしょう。

T4テンプレートの中で使う

で、これとT4テンプレートの中で使うには…という話ですが、先ほどのimportに加えて、適当にコピペしても大体動くはずです。例えば以下のように。

template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Runtime" #>
<#@ assembly name="System.Threading.Tasks" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.Workspaces.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\Microsoft.CodeAnalysis.Workspaces.Desktop.dll" #>
<#@ assembly name="$(DevEnvDir)PrivateAssemblies\System.Collections.Immutable.dll" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Threading.Tasks" #>
<#@ import namespace="Microsoft.CodeAnalysis" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
<#@ import namespace="Microsoft.CodeAnalysis.MSBuild" #>
<#@ output extension=".txt" #>
<#
    // 対象となるプロジェクトの.csprojのパス
    var projPath = @"C:\Users\Tomoyo\Documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.csproj";
    var classNames = GetClassNamesAsync(projPath).Result;
#>

<# foreach (var className in classNames) {
#>- <#= className #>
<# } #>

<#+

// Roslynを使ってプロジェクトのコードからクラスのコンストラクタ定義を引っこ抜く
async Task<List<string>> GetClassNamesAsync(string csprojPath)
{
    // MSBuildのワークスペース(環境みたいなもの)を作って、プロジェクトファイルを開く
    var workspace = MSBuildWorkspace.Create();
    var project = await workspace.OpenProjectAsync(csprojPath);

    // ソースコードをコンパイル的なことをする(出力するわけではなくて内部的なデータを作るやつ)
    // これでコードをパースした結果を得ることができるようになる
    var compilation = await project.GetCompilationAsync();

    var classNames = new List<string>();
    // シンタックスツリーをファイル単位で適当になめていく
    foreach (var syntaxTree in compilation.SyntaxTrees)
    {
        // セマンティックモデル(シンタックスツリーは文法で、それに対応する「コード的な意味」)を取得する
        var semModel = compilation.GetSemanticModel(syntaxTree);

        // シンタックスツリーからクラス定義のシンタックス(記述)を引っ張り出して、
        // セマンティックモデルに問い合わせることでクラス定義(意味)を引っ張り出して、ふにゃふにゃ処理する。
        classNames.AddRange(
            syntaxTree.GetRoot()
                .DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .Select(x => semModel.GetDeclaredSymbol(x))
                .Select(x => x.ToDisplayString())
        );
    }

    return classNames;
}
#>

で、このT4テンプレートを実行すると、以下のようにクラス名の一覧が記述されたファイルが吐き出されます。

ConsoleApplication1.Hauhau
- ConsoleApplication1.Program
- ConsoleApplication1.Hoge
- ConsoleApplication1.Fuga

とても簡単ですね。

まとめ

大抵の場合においてはアセンブリを直接読み込めばいいと思いますが、もし「現在のコードを元にプロジェクトに含めるコードを生成する」という必要がある場面では役に立つのではないでしょうか。

またこの組み合わせで書いてみるとわかるのですがLINQPadでRoslynでソースコードから情報を集約するコードを書いて、それを元にジェネレートする、という手順はテンプレートからロジックの一部を切り出して実装でき、デバッグしやすいのでとてもおすすめです。