はじめての chu ネタサイトを作りました

二週間ほど前に流れてきたツイートなんですが、chu 言語を作ればいいわけで、なるほど…と思っていたのです。どうせならば、.NET 環境で動くものがいいし、どうせならば F# っぽい関数言語がいいし、どうせならば chu に関したものがいいですね。ってことで、作ってみました(笑)。

http://hajimete-no-chu.com/ 
http://chu-lang.com/

image

swift で絵文字が使えるならば、いっそのことプログラム言語のキーワード自体に絵文字を使ってみようという試みです。<- の代わりに、 を使ったり、let の代わりに、 を使ったりします。いちいち、絵文字を打つのは大変なので、

    ://: F# like
    :let: :heart: :<-: "I"
    :let: :blue_heart: :<-: "chu"
    :fun: :smiley: :cat: :dog: :->: :cat: + ":heart:" + :dog:
    :let: :dolphin: :<-: :smiley: :heart: :blue_heart:
    :print: :dolphin:
    > I:heart:chu

な風に書くと、以下な風に書き換える javascript が仕込んであります。キーワードは

image

どうせならば、きちんと動くものを作りたいので、現在文法を練り込み中。HSP ぐらいには動くものを作っておきたい。

絵文字の画像と名前は、以下から借用しています。

Complete List of Emoji
http://www.fileformat.info/info/emoji/list.htm

カテゴリー: chu | はじめての chu ネタサイトを作りました はコメントを受け付けていません

通常のプログラムでも PrivateObject を使う方法

[C#]ユニットテストで private なメンバにアクセスしよう | 星空は撫子色
http://muu000.net/wordpress/?p=1691

実は、PrivateObject の存在を知らなかったんですよ。私の場合、テストしやすいように protected にしておくか、

Silverlight の UnitTest | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/330
PrivateObject クラス (Microsoft.VisualStudio.TestTools.UnitTesting)
http://msdn.microsoft.com/ja-jp/library/Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject.aspx

な感じで、internal にして、InternalsVisibleTo でアクセス許可を与えています。古い TDD の議論で private メソッドにアクセスするのは云々、という話もありますが、複雑怪奇なアルゴリズムを作るときは細かくメソッド分割をしてテストをしたほうがよいので、最終的に見せるのはひとつのメソッドだけど内部的には十数個のメソッドの組み合わせにしているというパターンがよくあります。F# の場合は内部関数を使って入れ子にしたりする訳で。まあ、最初は public で書いておいてテストが済んだら private に変換するってのが一番お手軽かと。

■リフレクションの GetTypeInfo().GetDeclaredField() を使う

MSDN のマニュアルを見ると、GetDeclaredField メソッドは public なフィールドを取得するはずなのですが、試してみると private フィールドも拾えます。なんででしょうね?例の WinRT から COM アクセスするときに、アクセスできるようになっちゃったのかもしれませんが、このあたり良く調べていません。
が、以下な感じで、プライベートなフィールドが取得できます。

public object GetField(string name)
{
    Type t = this.RealType;
    var pi = t.GetRuntimeField(name);
    if (pi == null)
    {
        // MSDN ではパブリックフィールドになっているが、プライベートも取得できる
        pi = t.GetTypeInfo().GetDeclaredField(name);
    }
    if (pi != null)
    {
        var obj = pi.GetValue(this.Target);
        if (obj != null)
        {
            return obj;
        }
    }
    return null;
}
public T GetField<T>(string name)
{
    object o = GetField(name);
    return o == null ? default(T) : (T)o;
}

一旦、GetRuntimeField で拾っているのは、public なフィールドをチェックしている訳で、いきなり GetDeclaredField で調べても構いません。元ネタの Microsoft.VisualStudio.TestTools.UnitTesting のほうは、戻り値が object だけだったので、型変換できるようにしています。
このコードは PCL 用なので GetRuntimeFiled を使っていますが、Native ライブラリの場合もそのまま使えます。

テストコードは、こんな感じです。

適当な private だらけなクラスを作っておきます。

/// <summary>
/// for test sample class
/// </summary>
public class TestSample
{
    private int privateInt = 10;
    private string privateStr = &quot;masuda&quot;;

    private int _privateProp = 20;
    private int privateProp
    {
        get { return _privateProp; }
    }

    private int privateMethod(int x, int y)
    {
        return x + y;
    }

    public void init( int a, string b )
    {
        privateInt = a;
        privateStr = b;
    }
    public int GetInt() { return privateInt; }
    public string GetStr() { return privateStr; }
}

それでもって、自作の PrivateObject でアクセスします。

public void TestGetField()
{
    var target = new TestSample();
    var o = new D4Reflection.Lib.PrivateObject(target);

    target.init(1, &quot;tomoaki&quot;);
    Assert.AreEqual(1, o.GetField<int>(&quot;privateInt&quot;));
    Assert.AreEqual(&quot;tomoaki&quot;, o.GetField<string>(&quot;privateStr&quot;));
    target.init(10, &quot;masuda&quot;);
    Assert.AreEqual(10, o.GetField<int>(&quot;privateInt&quot;));
    Assert.AreEqual(&quot;masuda&quot;, o.GetField<string>(&quot;privateStr&quot;));
}

■プライベートなフィールド/プロパティ/メソッドにアクセスする

moonmile/D4Reflection
http://github.com/moonmile/D4Reflection

この中にある D4PrivateObject プロジェクトがそれですね。

http://github.com/moonmile/D4Reflection/blob/master/D4PrivateObject/PrivateObject.cs

これだけをコピーしても大丈夫です。

■何に使うのか?

Windows Store app の場合、x:Name で作ったオブジェクトは private なんですよね。だから、この方式を使っています。同じアセンブリならば FindName を使ってもいいわけですが、Xamarin.iOS/Android と共通に使うとなると結構厄介だったので、最終的に private フィールドを直で持ってきます。

そうそう、名前で検索するのは面倒なので、TypeProvider を使えないかなと…思ったけど C# だとうまく動かないわけで、そうなると T4 の出番ですかね。

カテゴリー: C# | 通常のプログラムでも PrivateObject を使う方法 はコメントを受け付けていません

PCL の DLL からプロファイル名を表示するツールを公開

PCL (Portable Class Library) – Xamarin 3 の新しいコード共有テクニック : XLsoft エクセルソフト
http://www.xlsoft.com/jp/products/xamarin/portable_class_libraries.html 
Xamarinと、ポータブル・クラス・ライブラリ(PCL) – Build Insider
http://www.buildinsider.net/mobile/insidexamarin/13 
Xamarin.iOS/Android に対応している Profile78 とは何か? | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6137

ひとまず、ポータブルクラスライブラリで作っておけば、Xamarin.iOS/Android 間で共有できる訳ですが、Windows Store App や Windows Phone 8.1 を含めるとややこしいわけで、それなりに取捨選択しないといけません。

が、PCL 作成時に対象のフレームワークを選択せねばならず、将来的な未知のフレームワークにはどう対応するのか?という問題もあり、田淵@XLsoft さんの記事にある通り、プラットフォーム依存する場合には Dependency Injection などを使ってうまく吸収してやらねばいけません。と言いますか、

  • プラットフォーム依存しない部分を、PCL で書いて、TDD する。
  • 機種依存/プラットフォーム依存する場合は、各コードで書く(場合によっては Objective-C とか)
  • つなぎの部分を Interface/DI する。

ってな感じの地味な作業がベターかなと今は思ってます。作業量中心に考えて、それぞれのアプリ作成の状況に最適化、ってところですね。プラットフォーム毎のコードに苦労するのであれば、Xamarin.Forms などを使うメリットが低くなってしまうということです。

Reflection を使って、プラットフォームの差分を吸収しようかと思ったのですが、Xamarin.Android/iOS 自身が取っている TypeForwardedToAttribute を使うのもいいかもしれないと考えつつありますが。

■既存のプロファイルを調べる

というわけで、新しいプラットフォームに対応しようとすると、新しいプロファイルに対応しないといけません。それぞれのプラットフォームの組み合わせによって「Profile999」な感じで番号が振ってあるという大混乱ぶりなのですが、ソースコードが github などにアップされていれば(あるいは社内で持っていれば)プロファイルを切り替えてリビルドすれば ok です。

が、ソースコードのプロジェクトがあればよいのですが、DLL しかない状態に陥ると「これは、どのプロファイルなのか?」ってのが問題になる…と思いますよね。DLL を Visual Studio に参照設定しようとすると「異なるプロファイルなので、追加できません」とエラーが出るですが、果たして、その DLL は何のプロファイルなのか?

image

PCL の DLL をバイナリエディタで覗いてみると、プロファイルが書いてあります。これをバイナリで検索してもよいのですが、Assembly.LoadFile した後に、CustomAttributes コレクションの中身を探ってやれば見つかります。型が System.Runtime.Versioning.TargetFrameworkAttribute なものが見つかるので、そこに、”.NETPortable,Version=v4.5,Profile=Profile7″ な書式で書かれています。

■プロファイル名を抽出するツール

https://github.com/moonmile/CheckPCLProfile に、プロファイル名と、対応するプラットフォームを表示させるツールを公開しました。

image

コマンドラインで PCL の DLL を渡すと、プロファイ名が出てきます。たぶん、PCL 以外の DLL を渡すと落ちます。

AssemblyResolve で動的に DLL を読み込ませているのは FSharp.Core.dll のためです。どうやら、F# で作った PCL は CustomAttributes コレクションを探索するときに FSharp.Core を使うみたいですね。大抵の場合、PCL の DLL と同じ場所に FSharp.Core.dll があるので、それも一緒に読み込ませています。

■プロファイル名を直接書き換えると、別プロファイルで読み込めるのか?

実は、これが本題で作ったツールです。バイナリに「Profile7」と書かれている訳だから、「Profile78」と書き換えてしまえば、疑似的に対応する DLL が作れるのではないか?ってことです。名前が長くなってしまうので、文字列長あたりを直さないと駄目なんでしょうが、これを焼てみると…

結論は、だめでした。Assembly.Load 時に例外が発生して落ちてしまいますね。どうせ、制限されたクラスやメソッドしか使っていないのであれば、プロファイル名だけを切り替えればいいような気もするのですが…まぁ、これはリビルドってことで。

カテゴリー: ツール, C#, Xamarin | PCL の DLL からプロファイル名を表示するツールを公開 はコメントを受け付けていません

Xamarin.iOS/Android に対応している Profile78 とは何か?

f# – FS2024 Static linking error when PCL project use by TypeProvider – Stack Overflow
http://stackoverflow.com/questions/25175031/fs2024-static-linking-error-when-pcl-project-use-by-typeprovider

@AshtonKJ さんから解答を貰った、以下のところを、少しメモ書き

Once that is done, you will need to select either Profile78, or Profile259 (I would recommend 78, as the current Xamarin.Forms nuget package doesn’t support 259).

Portable Class Library を作るときに、対応するサブセットがあって、Xamarin.iOS/Android の場合に対応しているのが Profile78 です。Profile 自体は、対応する実行環境のセットがあって、Windows 8.1 やら Windows Phone やらの組み合わせでサブセットの番号が決まっています。対応自体は、C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\ の中にある、SupportedFrameworks フォルダの中身を見ればいいのですが、なかなか手間なので、対応表を作りました。

Profile 対応表 https://onedrive.live.com/view.aspx?resid=1709CDA2BB05E665!22702&ithint=file%2cxlsx&app=Excel&authkey=!ACgy7LWqZ4QjVZ8

私の環境では、Xamarin.iOS/Android が入っているので、これの対応も入っています。プログラム自体は、https://gist.github.com/moonmile/91573c79fed65062a217 にあるので、自分の環境で作ってみてください。

プロファイルの中にある DLL は名前は同じですが、API の実装が異なるので、それぞれの環境にあったプロファイルの DLL を使わないといけません。ですから、プロファイルの中にある API の一覧がほしい…のですが、無いのかな? Leveraging existing code across .NET platforms – .NET Blog – Site Home – MSDN Blogs がその役割をしているような気もするけど。ちょっときちんと調べていない。

■Xamarin.Forms の Mobile Apps で PCL を作る

まず、Mobile Apps で Xamarin.Forms 用の PCL を作ると、これが「Profile78」になっています。

image

ソリューションエクスプローラーで、PCL の「参照設定」→「.NET」を選択した状態でプロパティウィンドウのパスを見ます。

image

ここに「Profile78」が書いてある。

image

プロジェクトのプロパティから「ライブラリ」→「ターゲット」を見ると、どのプラットフォームに対応するのかが出てきます。

image

Xamarin Studio だと、もうちょっと直接的にプロファイルが見れて、プロジェクトのオプションから「ビルド」→「一般」でプロファイルがわかる。

□Profile78 の場合

image

ここの Target Framework をちょいちょいと変えてやると対応するプロファイルの番号がかわります。Xamarin.Forms の場合は、Windows Phone Silverlight が入っているので「Profile78」になっていますが、Silverlight を外すと「Profile7」に変わります。

□Profile7 の場合

image

□Profile259

Profile259 ってのは、Windows Phone 8.1 を含んだ場合ですね。結構制限された環境であることが想像できます。

image

  7 78 259 47
.NET 4.5
SilverLight 5      
Widnows Phone SL8    
Windows 8
Windows 8.1    
Windows Phone 8.1      
Xamarin.Android
Xamarin.iOS

そんな訳で、Xamarin.iOS/Android に対応した PCL を作る場合には、Profile78 か Profile259 を作ればよいわけです。

■F# でポータブルライブラリを作るとどうなるのか?

C# では PCL を作るときやプロジェクトで対応する Profile を指定できるのですが、F# の場合はこれができません。何故できないのかは不思議です。まあ、歴史的経緯で FSharp.Core があちこちに散らばってしまっているためなのかな、と。さまざまな FSHarp.Core は、C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\ にあります。

image

あと、ビルド時には C:\Program Files (x86)\Microsoft SDKs\F#\ を参照していたりします。.NETCore にある 3.78.3.1 とか 3.259.3.1 は、それぞれ Profile78 と Profile259 に対応したプロファイルです。これは、https://visualfsharp.codeplex.com/ からダウンロード&インストールするとできるフォルダです。

Visual F# Tools – Home
https://visualfsharp.codeplex.com/wikipage?title=Current%20Priorities

にある通り、「Put the finishing touches to Profile78 and 259」されます。

プロジェクトを作成するときも、新しいテンプレートが増えて、

  • Portable Library(.NET 4.5 Widnows Store, Windows Phone 8 Silverlight)
    → Profile78 相当
  • Portable Library(.NET 4.5 Widnows Store, Windows 8.1, Windows Phone 8 Silverlight)
    → Profile259 相当

image

になります。

試しに、「Portable Library(.NET 4.5 Widnows Store, Windows Phone 8 Silverlight)」のほうを作って、プロパティのパスを見ると、「C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile\Profile78」になっていて、プロジェクトのプロパティではターゲットが「F# 3.1 (FSharp.Core, 3.78.3.1)」になります。このマイナーバージョン部分が、プロファイル番号に相当するという混沌さです(苦笑)。

image

おそらく、FShap.Core を C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\ に突っ込んでしまうか、C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\ 以下、相当のフォルダを C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\.NETPortable\ に作ってしまうほうが素直だと思うのですが…先行きどうなるか分かりません。対応するプロファイルが増えると混沌の度合いが増しそうです。実際、FSharp.Data が動的ロードする FSharp.Core に Profile78 を追加するときは、このマイナーバージョンを見る必要があってで、えらいことになりそうです。

ちなみに

  • ポータブルライブラリ
    → Profile7 .NET 4.5 → FSharp.Core 3.3.1.0
  • ポータブルライブラリ(レガシー)
    → Profile47 .NET 4.0 → FSharp.Core 2.3.5.1

になります。

  Profile FSharp.Core .NET
Portable Library(.NET 4.5 Widnows Store, Windows Phone 8 Silverlight) 78 3.78.3.1 4.5
Portable Library(.NET 4.5 Widnows Store, Windows 8.1, Windows Phone 8 Silverlight) 259 3.259.3.1 4.5
ポータブルライブラリ – Portable Library 7 3.3.1.0 4.5
ポータブルライブラリ(レガシー) – Portable Library Legacy 47 2.3.5.1 4.0
ライブラリ – Library Native 4.3.1.0 4.5

■結論

というわけで、F# で Xamarin.iOS/Android 用のライブラリを作るときに、Profile7 しか選択肢がなかったけど、Profile78 か Profile259 が増えたよ、って話です。Xamarin.Forms を使う時は、Profile78 を使えば OK。

Xamarin.Forms 自体が Windows Phone 8.1 に対応していないので意味はありませんが、Profile259 にしておくことで、Windows Phone 8.1 から参照設定できるようになるので、ライブラリとして利用させることができます。F# のポータブルライブラリ=Profile47 相当が Window Phone 8.1 を含んでいなかったので、これで、Xamarin.iOS/Android, Windows Store 8.1, Windows Phone 8.1 の共通ライブラリが作れるようになった、ってことです。

~~~

FSharp.Core がややこしいことになっているのと、先の TypeProvider の謎の実行エラー(StringStream のコンストラクタがないエラー)も、ここに関わっているような気がするので、Profile 間で呼び出せる API を総ざらいしたほうがよさそうですね。ビルドは通るけど実行エラーになるのはマルチプラットフォーム開発の常なので、もうちょっと突っ込んで調べたほうが安全そうです。

カテゴリー: F#, Xamarin | 1件のコメント

PCL対応のTypeProviderを作…れるのかな?の巻

Xamarin.Forms の TypeProvider を作ろうとしたが断念したの巻 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6104

の続きです。FSharp.Data だと PCL で動いているよん、という話と。 @Reed Copsey, Jr. さんが独自だけど Xamarin.Forms も動いたよ(まだ変なバグで修正中だそうですが)とうことで、週末に FSharp.Data のアプローチを見ていきました。

逃れる手段としては、

f# – FS2024 Static linking error when PCL project use by TypeProvider – Stack Overflow
http://stackoverflow.com/questions/25175031/fs2024-static-linking-error-when-pcl-project-use-by-typeprovider/25194727#25194727

な風にリフレクションを使えばいいのだけど、そもそも Native のライブラリを Android に直接持っていくのも変な話だし、危うい点があるので、できることならば PCL で持っていきたい。そこで、FSharp.Data の XmlProvider のところをちまちまと読み込んだところ、なるほど、

  • TypeProvider でビルドしているときは、Native で動かす。
  • 作成した Type を動かすときは、それぞれの Profile に従う。

という構造になっています。こんな風に、FSharp.Data.DesignTime と FSharp.Data.Profile47 を分けます。DesingTime のほうはフルセットが入っているバージョンで、Visual Studio を使って TypeProvider するときに使うものです。Profile47 のほうは、実行時に呼び出されるもので、それぞれのプロファイルでプロジェクトを作ります。

image

実行時に使う法は。FSharp.Data.dll という名前に統一しておいて、対応する FSharp.Core は動的に読み込むという仕組みですね。なるほど、確かにこれで動きそうです。FSharp.Core の動的読み込みは、AssemblyResolver.fs に書いてあるので、Profile78 と Profile259 を追加します。この2つは、Visual F# Tools で使われるプロファイルで、Profile78 が Xamarin.iOS/Android で使われる PCL になります。プロジェクト自体は、こんな感じで追加されています。

 image

でもって、Profile47 と似た感じで作っては見たのですが、何故かコンソールで動かすと、追加情報:メソッドが見つかりません: ‘Void System.IO.StringReader..ctor(System.String)’ なエラーを吐きます。非常に不思議です。さらに不思議なのは、TypeProvider で作った文字列をそのまま使う場合には出なくて、何か加工しようとすると出るエラーです。

image

また、XmlProvider にある Parse メソッドを使おうとすると、以下のようなエラーが出ます。これも不思議です。

image

このエラーは、Profile47 の場合はでなくて、もともとの FSharp.Data にある Profile7 の場合も出ます。何か変な感じで PCL のロードをしているような気もするのですが、たぶん原因は AssemblyResolver.fs にあるような気がしています。System.IO.StringReader が無いことはないので、対応する System のロードが間違っている感じ。

ひとまず、以下のアプローチをすれば PCL 内で TypeProvider を使えるようになるらしい。

  • TypeProvider のデザイン時の DLL を分けて、動的ロードできるようにする。
  • 実行時のアセンブリは実行環境のプロファイルに合わせて、デザイン時には、DesignTime を動的に読み込む。
  • ビルド時の FSharp.Core は、プロファイルに合わせて動的読み込み?
  • ビルド時に必要なアセンブリは動的読み込みしている? > AssemblyResolver.fs の referencedAssembliesPairs これが足りないような気がする。

実験コードは、こちら
https://github.com/moonmile/FSharp.Data.PCL

カテゴリー: F# | 1件のコメント

meArmPi の動作メモ

F# TypeProvider を PCL 上で動かす試みは、FSharp.Data と同じ方式を取ればよいということが分かったのだが、何故か Profile47 しかうまく動作しない。元の XmlProvider に Profile78 と Profile259 を加えててもうまくいかないし、試しに Profile7 を作っても駄目。何故か System.IO.StringStream のコンストラクタがないという謎なエラーでる…まあ、これはひとまず、冷ましておいて、Raspberry Pi で meArm を動かすことに集中しよう

閑話休題

meArm は、http://www.phenoptix.com/products/mearm-pocket-sized-robot-arm から 29.99 ユーロ(約 3500円ぐらい)で買えるロボットアームです。駆動系は10個3000円で変えるサーボを使っているので、自作できる方はそれで。手っ取り早くアームの動きだけ試したい場合はこれでよいかと。

PCA9685搭載16チャネル PWM/サーボ ドライバー (I2C接続) 経由でサーボを動かすのを忘れずに。meArm 自体は 4チャンネルなので、後12チャンネル動かせますね。手元にプチロボのサーボが余っているので、何か別のものも同時に動かします。あと、Raspberry Pi の Raspberry Pi用T型I/O延長基板 があると工作が楽です。半田付けは苦手なのですが、まあ、ちまちまやればなんとかなるかと。

■プログラムを動かす

サーボを動かすのは何でもいいのですが、Raspberry Pi から制御したいので、https://github.com/RorschachUK/meArmPi にある動作確認用のコードで動かしてみます。

サーボの順番

  • Servo 0: meArm rotating base
  • Servo 1: meArm shoulder (right hand side servo)
  • Servo 2: meArm elbow (left hand side servo)
  • Servo 3: meArm gripper

Raspberry Pi のピンの順番

  • Adafruit GND to RPi GND
  • Adafruit SCL to RPi SCL0
  • Adafruit SDA to RPi SDA0
  • Adafruit VCC to RPi 3.3V
  • Adafruit V+ to RPi 5V

Raspberry pi のピン自体は http://wiringpi.com/pins/ にあります。そのまま拝借

あとは、5V の電源(電池4個で6Vですが)が必要なので、適当な電池ボックスにいれてブレットボードに接続あるいは、先のサーボドライバーの青いところに接続します。GND のほうを電池のマイナスにすればOK(初心者だし…)。

Python で試験動作

まずは、この python コードを C# に移植から。

カテゴリー: RaspberryPi | meArmPi の動作メモ はコメントを受け付けていません

Xamarin.Forms の TypeProvider を作ろうとしたが断念したの巻

先日の F# Meet up であった TypeProvider の資料をもとに、Xamarin.Forms 用の TypeProvider を作ろうとしたのですが、ちょっと挫折…の記録です。

TypeProviderについて、勝手に補足 – ぐるぐる~
http://bleis-tift.hatenablog.com/entry/kos59125-typeprovider
type-providers.pptx – Microsoft PowerPoint Online
http://onedrive.live.com/view.aspx?resid=FD448A567D4BC37E!5132&ithint=file%2cpptx&app=PowerPoint&authkey=!ADhuGqIaXBhs0ak

TypeProvider 自体が悪いのではなくて、Xamarin.Forms が PCL で提供しているのと、TypeProvider 自体がネイティブ環境(Windows環境で動く)のと関係で、断念しています。何か回避策があったら教えてください~。

■XFormsPreviewer から移植する

もともと、TypeProvider もどきのロジックはあって、以下の中にある XFormsProvider がそれです。動的に XAML ファイルをロードして Xamarin.Forms.Page を返します。

XFormsPreviewer
http://github.com/moonmile/XFormsPreviewer

XAML に書かれている x:Name 部分は、FindByName メソッドで取り出しができるので実用的には問題ないのですが、型が決まっていないのがアレだし、いちいち FindByName で取り出すのも変な感じです。なので、TypeProvider の作り方がわかったら、いずれ移行する予定にしていました。

■ざっと移植する

XamarinFormsTypeProvider
http://github.com/moonmile/XamarinFormsTypeProvider

それなりに苦労しましたが1日ちょっとで移植できました。プロパティを順々に生成するところと、内部のデータの保持が結構肝なのですが、<a href=”https://github.com/fsprojects/FsXaml”>FsXaml</a> を参考にして作っています。

type MainPage = Moonmile.XamarinFormsTypeProvider.XAML<&quot;MainPage.xaml&quot;>

こんな感じで、XAML のファイルを指定することで、MainPage というクラスが生成すると、結構「おおおッ」ってな感じになります。ファイルの更新を監視していない(FsXamlは監視してます)ので、先の XAML ファイル名の部分をちょっといじる(コメントアウトして元に戻すとか)して、再生成させると x:Name で指定した名前がそのままプロパティになります。
テスト用に Literal な XAML 文字列も XamarinFormsTypeProvider.XAML に渡すこともできます。

この時点で、結構動いて Android エミュレータで画面が表示できるところまで出来たので、これはいけるなーと思ったのです。ラベルの表示もできて、Text プロパティで変更できるところまで確認島した。

■Clicked.Add すると「静的リンクエラー」になる。

お次は、ボタンイベントを作ろうとして、

type MainPageEx(target:MainPage) =
    let mutable count = 0
    do
        target.btn1.Clicked.Add( fun e ->
            count <- count + 1
            target.btn1.Text <- &quot;Clicked &quot; + count.ToString())
    member this.CurrentPage
        with get() = target.CurrentPage

な感じで、Clicked イベントをつけてビルドしようとすると、

FSC: エラー FS2024: 静的リンクでは、別のプロファイルを対象にしたアセンブリは使用されない場合があります。
プロジェクト "SimpleEventTypeLocalLib.fsproj" のビルドが終了しました -- 失敗。

というエラーメッセージがでビルドができません。このエラーがどういう意味なのか分からなくて、1日ほど悩みました。TypeProvider のプロジェクトは、F# ライブラリで作ってあり、MainPage のプロジェクトも F# ライブラリで作ってあります。本当は PCL で作りたかったのですが、TypeProvider のクラス自体が PCL に対応していません。System.Reflection.Emit.TypeBuilder が potable library のほうにはないのです。

タイプライブラリを作るときは Windows 上で動くけど、生成されたクラスは Android 上で動くわけだから、参照されるライブラリ自体が異なるんですよね。動作環境も異なる。

■FS2024 のエラー

Static linking PCL assembly with mscorlib reference ・ Issue #224 ・ fsharp/fsharp
http://github.com/fsharp/fsharp/issues/224
Consider merging this packaging repository with the contribution repository ・ Issue #303 ・ fsharp/fsharp
http://github.com/fsharp/fsharp/issues/303#issuecomment-39557870

似たパターンがあって、どうやら直っているらしいんだけど、私の環境だといまだに静的リンクエラーが出てます。たぶん原因が違うのかなと思って、実験用のサンプルコードを書きました。

■SimpleEventTypeProvider

moonmile/SimpleEventTypeProvider
http://github.com/moonmile/SimpleEventTypeProvider

プロジェクトの構造がややこしいですが、こんな感じです。

  • SimplePclTypeProvider タイププロバイダ本体
  • XamlPcl プリミティブなクラスのみ使ったイベント処理
  • XamlPclXamarin Xamarin.Forms を使ったイベント処理
  • SimpleEventTypeLib PCL で作ったライブラリ
  • SimpleEventTypeLocalLib Library で作ったライブラリ

タイププロバイダ本体は、こんなコードになります。

namespace Moonmile.FSharp.Lib
open System
open Microsoft.FSharp.Core.CompilerServices
open ProviderImplementation.ProvidedTypes
open System.Reflection

[<assembly:TypeProviderAssembly>]
do ()

type MyButton() =

    let event1 = new Event<_>()
    [<CLIEvent>]
    member this.Click = event1.Publish
    member this.ClickEvent(arg) =
        event1.Trigger(this, arg)    

[<TypeProvider>]
type SimpleEventType(config:TypeProviderConfig) as this = 
    inherit TypeProviderForNamespaces()
    let namespaceName = &quot;Moonmile.SimpleEventTypeProvider&quot; 
    let thisAssembly = Assembly.GetExecutingAssembly()


    /// 型生成を残す場合
    /// [<Litelal>]
    /// let xaml = &quot;<ContentPage>...</ContentPage>&quot;
    /// type MainPage = SimpleEventTypeProvider.XAML< xaml >
    // 型の定義
    let t = ProvidedTypeDefinition(thisAssembly, namespaceName, &quot;XAML&quot;, Some(typeof<obj>), IsErased = false )
    do t.DefineStaticParameters(
        [ProvidedStaticParameter(&quot;xaml&quot;, typeof<string>)],
        fun typeName parameterValues -> 

            let outerType = 
                ProvidedTypeDefinition (thisAssembly, namespaceName, 
                    typeName, Some(typeof<obj>), IsErased = false )
            // テンポラリアセンブリに出力
            let tempAssembly = ProvidedAssembly(System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), &quot;.dll&quot;))
            tempAssembly.AddTypes <| [ outerType ]

            // コンストラクタの生成
            let ctor = ProvidedConstructor([], 
                            InvokeCode = fun args -> <@@ () @@> )
            do outerType.AddMember( ctor )

            // プロパティを追加
            let prop = 
                ProvidedProperty( &quot;Name&quot;, typeof<string>, 
                    GetterCode = fun args -> <@@ &quot;masuda tomoaki&quot; @@> )
            do outerType.AddMember( prop )

            // ボタンを追加
            let propButotn = 
                ProvidedProperty( &quot;Button&quot;, typeof<MyButton>, 
                    GetterCode = fun args -> 
                        <@@  
                            // let me = %%(args.[0]):obj
                            new MyButton()
                        @@> )
            do outerType.AddMember( propButotn )
            (*
            // 直接参照しても駄目
            let propXButton =
                ProvidedProperty( &quot;XButton&quot;, typeof<Xamarin.Forms.Button>, 
                    GetterCode = fun args -> 
                        <@@  
                            // let me = %%(args.[0]):obj
                            new Xamarin.Forms.Button()
                        @@> )
            do outerType.AddMember( propXButton )
            *)


            let propXamlPcl =
                ProvidedProperty( &quot;XmlPCL&quot;, typeof<XamlPcl.XamlPage>, 
                    GetterCode = fun args -> 
                        <@@  
                            // let me = %%(args.[0]):obj
                            new XamlPcl.XamlPage()
                        @@> )
            do outerType.AddMember( propXamlPcl )
            /// Xamarin.Forms 関連を PCL 外出しにしても駄目    
            let propXamarinPcl =
                ProvidedProperty( &quot;XmlXamarinPCL&quot;, typeof< XamlPclXamarin.XamarinButton >, 
                    GetterCode = fun args -> 
                        <@@  
                            // let me = %%(args.[0]):obj
                            new XamlPclXamarin.XamarinButton()
                        @@> )
            do outerType.AddMember( propXamarinPcl )

            outerType
    )
    // 名前空間に型を追加
    do this.AddNamespace( namespaceName, [t] )

    // Xamarin.Forms.Core 用に追加
    override this.ResolveAssembly(args) = 
        let name = System.Reflection.AssemblyName(args.Name)
        let existingAssembly = 
            System.AppDomain.CurrentDomain.GetAssemblies()
            |> Seq.tryFind(fun a -> System.Reflection.AssemblyName.ReferenceMatchesDefinition(name, a.GetName()))
        match existingAssembly with
        | Some a -> a
        | None -> 
            // Fallback to default behavior
            base.ResolveAssembly(args)

ResolveAssembly がオーバーライドされているのは、タイププロバイダを利用するプロジェクト(SimpleEventTypeLocalLib)がビルドをするときに、Xamarin.Forms.Core を要求するためにこうしています。他にも別なのを要求してるのですが、まあ、こうやっておくと同じフォルダにある DLL を読み込んでくれます。

呼び出し側のコードはこんな感じです。Xamarin.Forms.Color.Black のようなプロパティの設定はうまくいくのですが、target.XButton.Clicked を呼び出した途端に「静的リンクエラー」になります。逆に言えば、Clicked を使わない限りは、ビルドが正常に通ります。

namespace SimpleEventTypeLocalLib

type MainPage = Moonmile.SimpleEventTypeProvider.XAML<&quot;MainPage.xaml&quot;>

type MainPageEx(target:MainPage) =
    let mutable Name = &quot;&quot;
    
    do
       Name <- target.Name
       target.Button.Click |> Event.add( fun e -> ())  
       // Xamarin.Forms.Core が動的ロードされているので、静的リンクエラーになる
       // target.XButton.Clicked |> Event.add( fun e -> ())  
       (*
       let col = target.XButton.BackgroundColor
       let a = col.A
       target.XButton.BackgroundColor <- Xamarin.Forms.Color.Black
       target.XButton.Clicked |> Event.add( fun e -> ())
       *)
       let pcl = target.XmlPCL.Xaml
       target.XmlPCL.Click |> Event.add( fun e -> ())

       let xpcl = target.XmlXamarinPCL
       xpcl.Text <- &quot;test&quot;
       // この時点で、Xamarin.Forms の型が参照されて静的リンクエラーになる
       xpcl.Clicked |> Event.add( fun e ->())
       ()

■アセンブリを動的生成すると Native から Portable ライブラリが参照できない???

Static linking PCL assembly with mscorlib reference ・ Issue #224 ・ fsharp/fsharp
http://github.com/fsharp/fsharp/issues/224

の最初のコメントにあるコードを見ていくと

http://github.com/TIHan/fsharp/blob/master/src/fsharp/fsc.fs#L1656

error を出しているところがあります。これはオープンソース版ですが、行数は違いますが同じチェックがあります。

              // Rewrite type and assembly references
              let ilxMainModule =
                  let isMscorlib = ilGlobals.primaryAssemblyName = PrimaryAssembly.Mscorlib.Name
                  let validateTargetPlatform (scopeRef : ILScopeRef) = 
                      let name = getNameOfScopeRef scopeRef
                      if (isMscorlib && name = PrimaryAssembly.DotNetCore.Name) || (not isMscorlib && name = PrimaryAssembly.Mscorlib.Name) then
                          error (Error(FSComp.SR.fscStaticLinkingNoProfileMismatches(), rangeCmdArgs))
                      scopeRef
                  let rewriteAssemblyRefsToMatchLibraries = NormalizeAssemblyRefs tcImports
                  Morphs.morphILTypeRefsInILModuleMemoized ilGlobals (Morphs.morphILScopeRefsInILTypeRef (validateTargetPlatform >> rewriteExternalRefsToLocalRefs >> rewriteAssemblyRefsToMatchLibraries)) ilxMainModule

アセンブリを書き込むとき(ビルドするとき?)に .NETCore や mscorlib の整合性をチェックしているので、これにひっかかっているのかもしれません。

図解したように、出来るだけ遠くに(苦笑)Xamarin.Forms の PCL を置いて、直接 Android のほうから呼び出してやればうまくいくかもしれません。少なくとも、F# で作成するコードビハイドのような SimpleEventTypeLocalLib からは Clicked のような PCL の型を呼び出そうとすると「静的リンクエラー」になります。

まあ、コードビハイドはやめて、MVVM にして ICommand オンリーにすれば通るのですが…ちょっと面白くない。せっかくだから、Clicked のままやりたいのです。そうすると、内部的にリフレクションを使って iPhone の TouchUpInside も取れるのですがね。ムズイ。

コンパイラを直してしまう方法も考えたのですが、タイププロバイダ部分のビルドならともかく、コードビハイド部分の生成にオレオレコンパイラを使うのもちょっと難点が多いのでパスです。先のバグが直ったのか直ってないのかわかりませんが、現状の最新を取ってきても同じ現象になります。

にしても、最初の TypeProvider の作成で1日以内にできたのは、F# meet up のおかげです。感謝。

カテゴリー: F#, Xamarin | Xamarin.Forms の TypeProvider を作ろうとしたが断念したの巻 はコメントを受け付けていません

既存のツールを Windows 8.1 のスタート画面に表示させる方法

デスクトップ PC で Windows 8.1 を使っているときスタート画面は、ほとんど使いません。というか、Win キーでスタート画面を開いた直後に、アプリの名前をキーボードで直接打って、目的のアプリを起動させています。まあ、Surface のようなタブレットの場合には、指でぽちぽちやるんでしょうけど、キーボードが付いている場合はこれが一番早いです。

こんな風に「vi」と打つと、アプリ名が補間されて「Visual Studio 2013」が起動できます。

image

この検索されるアプリ名ですが、前方一致なのか部分一致なのか、微妙なところでよくわかりません。たぶん、単語単位の前方一致になっているハズです。なので「i_view32」は、アンダーバーが単語の区切りとして認識されて、i_view32 としてマッチしているようです。

なので、うまく検索に出てこないときは、ショートカット名に適当なコマンド名を単語単位でつけておくとよいでしょう。たとえば、コマンドラインで C# をビルドするための「開発者コマンドプロンプト for VS2013」はよく動かす(私だけだと思うけど)のですが、「cmd」でマッチさせるために、ショートカットの名前を変更しています。まあ、自分好みのショートカット名にしてもよいわけですね。

image

■既存のアプリをスタート画面に出るようにする

さて、スタート画面からキーボードで直接アプリを起動できることは分かったのですが、そもそも既存のアプリをスタート画面に表示する場合にはどうするのか?ってのが疑問です。うまくインストールされているものは、スタート画面にアイコンが表示されるのですが、Vector などからダウンロードするインストーラー無しのツール(exe だけコピーするもの)は、うまいこと出ません。

仕方がないので、デスクトップにショートカットをつけて凌いでいたのですが、ふと「既にあるアイコンと同じ場所に置けばよかろう」と思い至りました。いや、探してみれば、フォルダ名はわかるはずなのですが、これが面倒臭いので。

image

こんな感じに、既にアイコンが出ているアプリを右クリックして、マウスで「ファイルの場所を開く」を選択します。ここでは、Firefox を選択していますが、Windows ストア アプリ以外ならば何でもいいです。

そうすると、「C:ProgramDataMicrosoftWindowsStart MenuPrograms」というフォルダが開かれます。この中身を検索しているっぽいので、ここに既存のアプリのショートカットを突っ込めば OK ですね。

image

常用している「QX」と「QTClick」を入れておきます。スタートアップに入れると、Windows 7 の頃のように PC の起動時に読み込まれます…が、あまりお勧めできません。Windows 8 の起動の速さの一因は「スタートアップに何もいれない」からってのもあるので。なんだかなー。

image

こうすると、スタート画面に「新しいアプリがインストールされました」ってのが出るので、スタート画面にピン留めしたり、スタート画面から直接検索したりできます。

image

適当なバッチファイルとかおくと、バッチを動かすのもスタート画面でできますね…って、それはデスクトップでやればいい話ですが。

カテゴリー: windows 8.1 | 既存のツールを Windows 8.1 のスタート画面に表示させる方法 はコメントを受け付けていません

F# で Windows.UI.Xaml のクラスをリフレクションを使ってラップして Windows ストア アプリ作る試み

今、手元で作っている WinXamlProvider は、Windows Store 8.1 と Windows Phone 8.1 で使っている XAML を F# で動かそうという試みのひとつです。

WinXamlProvider
http://github.com/moonmile/WinXamlProvider

Windows Store アプリを作る F# プロジェクトが Microsoft から提供されればいいのですが、そんな雰囲気もない。正当な方法としては、自前で F# プロジェクトのテンプレートを作ればよいのですが、どうやら *.target のほうを大きく変えないと駄目っぽくて、頓挫中です。おそらく MSBuild あたりを詳細に調べないとうまくいかなそうなのと、XAML ファイルから C#/VB/C++ コードに落としている箇所に、うまく F# を追加しないといけません。MVVM パターンのみでやるか、ここでやっているように実行時にコードビハイドをバインディングする方法をとれば、ビルド時に XAML からコードビハイドを出力する必要はありません。あるいは、F# の Type Provider を作って、ビルド時に静的に作る方法でもよいでしょう。WPF の XAML 用の Type Provider は FsXaml
https://github.com/fsprojects/FsXaml を使えばよいんですが、WinStore 用の XAML がありません。なぜ、WinStore の XAML に私がこだわるのかといえば、Surface RT のような WinRT タブレットでの使い手を想定しているためです。ええ、ユニバーサルアプリにして Windows Phone 8.1 でもうまく動くようになったのは「おまけ」ですから。

■プロジェクトを分ける

現状では、WinStore と WinPhone ではメインプロジェクトに F# を据えることが難しいので、

  • フロントエンドを C# プロジェクトで作る
  • バックエンドを F# プロジェクトで作る

ということにします。この方式は、Xamarin.Forms を使って iOS/Android を作るときと同じで、バックエンドのロジック部分をライブラリ化して、可搬性を高めるという方式です。ただし、バックエンド部分が PCL(Portable Class Library) になってしまうので、使えるライブラリに制限がでてきます。

20140729_01

こんな風に、C# プロジェクトでは簡単に参照できる Windows.Xaml.UI.* も、PCL の中ではできません。
直接 F# プロジェクトのファイルを開いて、ターゲットの書き換えと、

    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
    <TargetFrameworkProfile>Profile32</TargetFrameworkProfile>

ターゲットプラットフォームに Windows, Version=8.1 を無理やり追加すると Windows.UI.Xaml の namespace は参照できるようになるのですが、

  <ItemGroup>
    <TargetPlatform Include=&quot;Windows, Version=8.1&quot; />
    <TargetPlatform Include=&quot;WindowsPhoneApp, Version=8.1&quot; />
    <Compile Include=&quot;Class1.fs&quot; />
  </ItemGroup>

実行時に

{"ファイルまたはアセンブリ 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。":"Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null"}

という謎なエラーを出力します。確かに、無理に追加しただけなので、ロード自体がうまくいっていないっぽいのです。ここは別途修正するとうまくいのかも。

実験したプロジェクトは https://github.com/moonmile/FsApp です。

■Windows.UI.Xaml をラッピングする

PCL から直接 Windows.UI.Xaml を扱うことはできませんが、リフレクションを使うとアクセスができます。

既にフロントのほうでは、Windwos.UI.Xaml に必要なアセンブリはロードされているハズですから、それめがけてちまちまとインターフェースを作っていく方法ですね。

この方法自体は、Xamarin.Forms の拡張レンダラーとか、MvvmCross のサービスの作り方だとかと似た感じで、Xamarin.Forms や MvvmCross の場合は、適当なインターフェースを使って呼び出すようにしていますが、面倒なので、直接リフレクション呼び出しをしています。インターフェースを使うと、F# の場合に型キャストでややこしいってのもあるので。

■プロジェクト構成

今、電卓アプリのサンプルを作っている途中ですが、構成はこんな感じです。

  1. WinStore/Phone のユニバーサルアプリを作成
  2. F# PCL を作成
  3. NuGet で WinXamlProvider をインストール(製作中)
  4. WinStore/Phone で XAML ファイルを動的にロードするように設定(調節中)
  5. Page クラスのコンストラクタで、BindInit を呼び出し。

のようにすると、特定の Xaml/Page が F# の Page クラスにバインドされます。4 の手順は、動的に XAML をロードする必要があって、こうなっています。元ネタの XAML はバイナリ形式の *.xbf ファイルになって手がだせないので、テキスト形式でコンテンツ埋め込みをさせています。

一応、動作させると、

な感じで動きます。

現在、Button, TextBlock, TextBox, Page クラスのプロパティしかバインドしていないので、かなり限定的ですが。

■リフレクションでバインド

詳細なコードは、WinXamlProvider にありますが、主要部分を抜き出すとこんな感じです。

type ParseXaml() =

    let findName( page, propName ) =
        let mi = page.GetType().GetRuntimeMethod(&quot;FindName&quot;, [|typeof<string>|])
        let res = mi.Invoke( page, [|propName|])
        res

    let bindMethod( page:obj, bind:obj, target:obj, eventName:string, methodName:string) =
        let ei = target.GetType().GetRuntimeEvent(eventName)
        let dt = ei.AddMethod.GetParameters().[0].ParameterType
        let mi = bind.GetType().GetRuntimeMethod(methodName,[|typeof<obj>; typeof<RoutedEventArgs>|])

        let handler =
            new Action<obj,obj>(
                fun sender eventArgs ->
                    let e = new RoutedEventArgs(eventArgs)
                    mi.Invoke( bind, [|sender; e|]) |> ignore )

        let handlerInvoke = handler.GetType().GetRuntimeMethod(&quot;Invoke&quot;,[|typeof<obj>; typeof<Type[]> |]);
        let dele = handlerInvoke.CreateDelegate(dt, handler)

        let add = new Func<Delegate, EventRegistrationToken> ( fun (t) ->
                        let ret = ei.AddMethod.Invoke(target, [|t|] )
                        ret :?> EventRegistrationToken
                    )
        let remove = new Action<EventRegistrationToken>( fun(t) ->
                ei.RemoveMethod.Invoke( target, [|t|]) |> ignore )
        WindowsRuntimeMarshal.AddEventHandler<Delegate>( add, remove, dele)

    let bindProperty( page:obj, bind:obj, propName:string, t:Type ) =
        let pprop = findName( page, propName )
        if pprop <> null then
            let bi = bind.GetType().GetRuntimeProperty(propName)

            match t.Name with
            | &quot;TextBlock&quot; -> bi.SetValue( bind, new TextBlock(target = pprop))
            | &quot;TextBox&quot; -> bi.SetValue( bind, new TextBox(target = pprop))
            | &quot;Button&quot;    -> bi.SetValue( bind, new Button(target=pprop))
            | _ -> bi.SetValue( bind, new UIElement( target=pprop ))

Page.FindName が必要なので、findName 関数でリフレクションを使います。
bindMethod 関数は、XAML で記述してある Click=”OnClickButton” のイベントを、F# のクラスメソッドに倍度する仕組みですね。イベントハンドラの登録は、WindowsRuntimeMarshal.AddEventHandler を使います。残念ながら、元ネタのイベントを削除していないので(頑張ればできる目途は立ちそうなのですが)、元のイベントと F# のイベントメソッドの両方を呼び出してしまいます。まあ C# のイベントハンドラは空なので、そのままでいいでしょう。

このあたりは、頭がちぎれそうになるぐらいややこしいのですが、まあなんとか。いくつか C# のコードも出てくるので、それを参照に組み立てます。こまめに小さなメソッドにしたほうがわかりやすいですね。

プロパティ呼び出しもいちいちリフレクションします。

namespace Moonmile.WinXamlProvider.UI
open System
open System.Reflection

[<AllowNullLiteral>]
type BaseElement() =
    member val target:obj  = null with get, set
    member this.getProp<'T>( propName:string ) =
        let pi = this.target.GetType().GetRuntimeProperty( propName )
        pi.GetValue(this.target) :?> 'T
    member this.setProp( propName:string, value:obj ) =
        let pi = this.target.GetType().GetRuntimeProperty( propName )
        pi.SetValue( this.target, value)

これを使って、Windows.UI.Xaml を再構築します。
Button クラスを、こんな風にリフレクションでバインドします。

type Button() =
    inherit ButtonBase()
    member this.Flyout
        with get() = base.getProp<FlyoutBase>(&quot;Flyout&quot;)
        and set(value:FlyoutBase) = base.setProp(&quot;Flyout&quot;, value )
    member this.CommandParameter
        with get() = base.getProp<Object>(&quot;CommandParameter&quot;)
        and set(value:Object) = base.setProp(&quot;CommandParameter&quot;, value )
    member this.Command
        with get() = base.getProp<Windows.Input.ICommand>(&quot;Command&quot;)
        and set(value:Windows.Input.ICommand) = base.setProp(&quot;Command&quot;, value )
    member this.ClickMode
        with get() = base.getProp<ClickMode>(&quot;ClickMode&quot;)
        and set(value:ClickMode) = base.setProp(&quot;ClickMode&quot;, value )
...

このあたりは手作業で書くと大変なので、適当なツールを作って、コード出力しています。
そのうち自動生成させてしまうつもり。

■F# で MainPage クラスを書いてみる

namespace WinXamlProvider.Lib
open System
open Moonmile.WinXamlProvider
open Moonmile.WinXamlProvider.UI

type MainPage() =
    member val textMessage:TextBlock = null with get, set
    member this.Button_Click(sender:obj, e:RoutedEventArgs) =
        this.textMessage.Text <- &quot;New F# message.&quot;

Button をクリックしたときに Button_Click イベントが呼び出されて、textMessage の内容を書き換えるコードです。まだ MVVM タイプの SetBinding を実装していないので、WinForm っぽい書き方になりますが、これで Widows Store アプリと Windows Phone アプリを F# で書くことが可能になります。

■今後は

Button.Click イベントあたりをバインドしてしまえば、直接 this.btn.Click.Add( … ) が使えるようになるので、コードビハイドのイベントコードを書かなく手済むようになります。これを早急に実装。
あとは、自動生成コードを修正して、Windows.UI.Xaml 以下の全てのクラスをバインドさせてしまう。

カテゴリー: F#, WinRT, XAML | F# で Windows.UI.Xaml のクラスをリフレクションを使ってラップして Windows ストア アプリ作る試み はコメントを受け付けていません

後付けの拡張メソッドを使って既存の拡張メソッドをオーバーライドする

裏ワザなのか基本技なのかわかりませんが、拡張メソッドを使うと「うまくやれば」既存のメソッドの動きを上書きできるかもしれん、という技です。

■Xamarin.Forms の FindByName を入れ替える

XamlProvider では、x:Name の解決のために FindByName を提供しているのですが、本当は元ネタの Xamarin.Forms の FindByName を使いたかったのです。内部実装まで真似て、Xamarin.Forms が使っている FindByName をそのまま使いたかった。が、これは Xamarin.Forms.NameScopeExtensions.FindByName 拡張メソッドで、実際に中でどうやっているかは分からないんですよね。実際、Name をキープするところは、実装依存なところがあるので、そこに手を入れらるかどうかはわかりません。

namespace Xamarin.Forms
{
    // 概要:
    //     Extension methods for Xamarin.Forms.Element and Xamarin.Forms.INameScope
    //     that add strongly-typed FindByName methods.
    //
    // コメント:
    //     To be added.
    public static class NameScopeExtensions
    {
        // 概要:
        //     Returns the instance of type T that has name T in the scope that includes
        //     element.
        //
        // パラメーター:
        //   element:
        //     To be added.
        //
        //   name:
        //     To be added.
        //
        // 型パラメーター:
        //   T:
        //     To be added.
        //
        // 戻り値:
        //     To be added.
        //
        // コメント:
        //     To be added.
        public static T FindByName<T>(this Element element, string name);
    }
}

仕方がないので、この FindByName を入れ替えて自前の、FindByName を作ってすり替えています。

 [<Extension>]
type PageXaml() =
    static member LoadXaml(xaml:string) =
        Moonmile.XForms.ParseXaml.LoadXaml(xaml)

    static member LoadXaml<'T when 'T :> Page >(xaml:string) =
        Moonmile.XForms.ParseXaml.LoadXaml<'T>(xaml)

    static member FindByName(page:Page, name:string) =
        Moonmile.XForms.FindByName(name, page)

    /// <summary>
    /// Alias FindByName from Xamarin.Forms
    /// </summary>
    /// <param name=&quot;name&quot;></param>
    [<Extension>]
    static member FindByName<'T when 'T :> Element >(this, name:string) =
        FindByName(name, this) :?> 'T

拡張メソッドの [<Extension>] なところは、以下なところを参考するとわかります。

C# から使いやすい F# コードの書き方 – ぐるぐる~
http://bleis-tift.hatenablog.com/entry/20121201/1354362376

で、うまくすり替えてしまったのはいいけれど、実際はどういう呼び出し方になっているのか?(どこが優先なのか)が気になっていました。

■テストコード

既定の BClass と、拡張メソッドを作った BClassExtentions, BClassOverrideExtentions を用意します。

class Program
{
    static void Main(string[] args)
    {
        new Program().main(args);
    }

    public void main(string[] args)
    {
        var b = new BClass(&quot;tomoaki&quot;);
        Console.WriteLine(&quot;{0}&quot;, b.GetPrint());
        Console.WriteLine(&quot;{0}&quot;, b.GetPrintEx());
        Console.WriteLine(&quot;{0}&quot;, (b as object).GetPrint());
        Console.WriteLine(&quot;{0}&quot;, (b as object).GetPrintEx());
    }
}

public class BClass
{
    private string _name;
    public BClass(string name)
    {
        _name = name;
    }
    public string Name { get { return _name; } }
    public string GetPrint()
    {
        return string.Format(&quot;BClass:{0}&quot;, _name);
    }
}

public static class BClassExtentions
{
    /// <summary>
    /// ExtentionLib による拡張 GetPrint
    /// </summary>
    /// <param name=&quot;obj&quot;></param>
    /// <returns></returns>
    public static string GetPrint(this BClass obj)
    {
        return string.Format(&quot;BClassEx:{0}&quot;, obj.Name);
    }
    /// <summary>
    /// ExtentionLib による拡張 GetPrintEx
    /// </summary>
    /// <param name=&quot;obj&quot;></param>
    /// <returns></returns>
    public static string GetPrintEx(this BClass obj)
    {
        return string.Format(&quot;BClassEx:{0}&quot;, obj.Name);
    }
}

public static class BClassOverrideExtentions
{
    /// <summary>
    /// OverrideLib による拡張 GetPrint
    /// </summary>
    /// <param name=&quot;obj&quot;></param>
    /// <returns></returns>
    public static string GetPrint(this object obj)
    {
        return string.Format(&quot;BClassOver:{0}&quot;, (obj as BClass).Name);
    }
    /// <summary>
    /// OverrideLib による拡張 GetPrintEx
    /// </summary>
    /// <param name=&quot;obj&quot;></param>
    /// <returns></returns>
    public static string GetPrintEx(this object obj)
    {
        return string.Format(&quot;BClassOverEx:{0}&quot;, (obj as BClass).Name);
    }
}

結果は、こんな感じで、うまく BClassOverrideExtentions のメソッドが呼び出されています。

BClass:tomoaki
BClassEx:tomoaki
BClassOver:tomoaki
BClassOverEx:tomoaki

まあ、よく見れば AClass#GetPrint で呼び出しているのか object#GetPrint で呼び出しているかの違いがあるので、呼び出すときのキャストで切り分けているのですが、これが、Xamarin.Forms のように BasicClass > Element > Page のような複数の階層になっている場合、Element にくっついている FindByName を Page にくっつけた FindByName で上書きができる、って話です。結構特殊ですが。

public class BClass
{
    private string _name;
    public BClass(string name)
    {
        _name = name;
    }
    public string Name { get { return _name; } }
    public string GetPrint()
    {
        return string.Format(&quot;BClass:{0}&quot;, _name);
    }
}
public class SubClass : BClass
{
    public SubClass(string name) : base(name) { }
}
public class SubSubClass : SubClass {
    public SubSubClass(string name) : base(name) { }
}

public static class SubClassExtentions
{
    /// <summary>
    /// SubClassExtentions による拡張 GetPrint
    /// </summary>
    /// <param name=&quot;obj&quot;></param>
    /// <returns></returns>
    public static string GetPrintEx(this SubClass obj)
    {
        return string.Format(&quot;SubClassEx:{0}&quot;, obj.Name);
    }
}
public static class SubSubClassExtentions
{
    /// <summary>
    /// SubSubClassExtentions による拡張 GetPrint
    /// </summary>
    /// <param name=&quot;obj&quot;></param>
    /// <returns></returns>
    public static string GetPrintEx(this SubSubClass obj)
    {
        return string.Format(&quot;SubSubClassEx:{0}&quot;, obj.Name);
    }
}

こんな風な階層構造にしておいて、最初は、SubSubClassExtentions 拡張をコメントアウトした状態で、次のコードを動かすと、当然 SubClassExtentions#GetPrintEx が動きます。

var bb = new SubSubClass("masuda");
Console.WriteLine("{0}", bb.GetPrint());
Console.WriteLine("{0}", bb.GetPrintEx());

結果
BClass:masuda
SubClassEx:masuda

その後に、SubSubClassExtentions を有効にして、同じコードを動かすと、結果は SubSubClassExtentions#GetPrintEx を呼び出すようになります。

BClass:masuda
SubSubClassEx:masuda

メソッドをサブクラスから順に探索するから、当たり前といえば当たり前なんだけど、なるほど、そういう動きをしていたのか、という記録ですね。

■同じ拡張メソッドがあるとビルド時にエラー

ちなみに、同じメソッド名、同じ引数で拡張メソッドを作るとビルド時にエラーがでます。

エラー	1	次のメソッドまたはプロパティ間で呼び出しが不適切です: 'OverrideLibFSharp.AClassFSharpExtentions.GetPrintEx(BaseLib.AClass)' と 'ExtentionLib.AClassExtentions.GetPrintEx(BaseLib.AClass)'	C:gitSamplesOverrideExtentionsMethodOverrideExtentionsMethodProgram.cs	25	38	OverrideExtentionsMethod

これは、F# で作った AClassFSharpExtentions.GetPrintEx メソッドと、AClassExtentions.GetPrintEx がぶつかっています、という意味です。F# のほうは PCL で作ったものを参照設定して使っています。

というわけで、結構限定的な使い方ですが、

  • クラスが階層構造になっている。
  • 階層構造の途中で拡張メソッドが使われている(Xamarin.Formsの場合は Element に FindByName がある)

このときは、さらにサブクラス/継承クラスに対して拡張メソッドをつけると、元の拡張メソッドを上書きできる、って話しでした。

カテゴリー: C#, F# | 後付けの拡張メソッドを使って既存の拡張メソッドをオーバーライドする はコメントを受け付けていません