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# | 後付けの拡張メソッドを使って既存の拡張メソッドをオーバーライドする はコメントを受け付けていません

Xamarin.Forms+F# で Navigation.PushAsync を使うときの注意

Xamarin.Forms で、次のページに遷移させるときには NavigationPage クラスを使って置いて、遷移先のページに対して、

await this.Navigation.PushAsync(new SubPage());

なことをやります。NavigationPage を使うと複数ページを簡単に扱えるので、超簡単なスマートフォンアプリを作る分には楽ですよね。テスト的に作るときにいいです。

これを F# でやります。

■サンプルコード

FSharpNavigationPushAsync
http://github.com/moonmile/XamarinSamples/tree/master/FSharpNavigationPushAsync

簡単な遷移だけのサンプルコードです。
iOS/Android/Windows Phone で動くようにしてあります。フロントは面倒なので C# で書いてありますが、中身の PCL のところは F# になっています。フロント部分は、基本そのままなので C# でも良いかと。

以下、F# で Xamarin.Forms を扱う時の手順も含めて書いておきます。

■Xamarin.Forms のプロジェクトを作る

残念ながら F# には、Mobile Apps がないので、C# で作ります。

Xamarin.Forms を使う PCL は Visual Studio 2013 でしか作れません。Xamarin Studio では作れません。これを、元の C# の PCL とすり替えます。

NuGet で Xamarin.Forms を更新しておきます。

各プロジェクトのバージョンが違うと、実行エラーになります。Android/iOS の場合は、特に問題がでないのですが、Windows Phone の場合は実行時にコケルので曲者です。

■F# PCL を standalone でビルドする

F# 単体で動かす場合には、FSharp.Core を気にしなくていいのですが、Xamarin を使って Android/iOS/Windows Phone で共通のライブラリを作るときは、standalone スイッチをつけてビルドをします。

standalone をつけなくても、Android/iOS の場合は大丈夫なのですが、何故か Windows Phone のときだけうまくいきません。FSharp.Core のバージョン違いのようで、Windows Phone SL なので、これは仕方がないっぽいです。
まあ、standalone の注意は、以下を見てください。

恐ろしい standalone オプション – 2つのアンコール
http://hafuu.hatenablog.com/entry/20121214

複雑に絡みあいはじめると、変なことになりそうですが、F# で作った PCL を C# のフロントで使う場合にはこれで十分でしょう。

■Windows Phone の ProductID を直す

Xamarin.Forms のプロジェクトテンプレートのバグです。Windows Phone のプロジェクトの ProductID のところを手作業で直しておきます。Properties/WMAppManifest.xml を Visual Studio で開こうとすると、「マニフェストを読む込むことができません」というエラーがでます。

このまま、XML エディタを開いて、

  <App xmlns=&quot;&quot; ProductID=&quot;f653f4bf-b71d-4572-b073-bc2c48395070&quot; Title=&quot;FSharpNavigationPushAsync.WinPhone&quot; RuntimeType=&quot;Silverlight&quot; Version=&quot;1.0.0.0&quot; Genre=&quot;apps.normal&quot;  Author=&quot;FSharpNavigationPushAsync.WinPhone author&quot; Description=&quot;Sample description&quot; Publisher=&quot;FSharpNavigationPushAsync.WinPhone&quot; PublisherID=&quot;f1f3a5b4-b6e0-4488-9fde-ac0b70a9f79e&quot;>

これを、以下のように {} が付くように直します。プロジェクトテンプレートの {} 忘れです。

  <App xmlns=&quot;&quot; ProductID=&quot;{f653f4bf-b71d-4572-b073-bc2c48395070}&quot; Title=&quot;FSharpNavigationPushAsync.WinPhone&quot; RuntimeType=&quot;Silverlight&quot; Version=&quot;1.0.0.0&quot; Genre=&quot;apps.normal&quot;  Author=&quot;FSharpNavigationPushAsync.WinPhone author&quot; Description=&quot;Sample description&quot; Publisher=&quot;FSharpNavigationPushAsync.WinPhone&quot; PublisherID=&quot;{f1f3a5b4-b6e0-4488-9fde-ac0b70a9f79e}&quot;>

Visual Studio Mobile Apps – Blank App (Portable/Shared) – Xamarin Forums
http://forums.xamarin.com/discussion/18694/visual-studio-mobile-apps-blank-app-portable-shared

あたりを参考にしてください。

■F#版の App.fs

C# で書いてある、Hello Forms をそのまま F# で書き直すと、こんな感じになります。

type App() =
    static member GetMainPage() =
        new ContentPage(
            Content = new Label(
                Text = "Hello, Forms !",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand ))

この状態で、Android/iOS/Windows Phoneが動作することを確認してください。

■F#で PushAsync する

遷移先のページを作っておきます。

type SubPage() as this =
    inherit ContentPage()
    do
        this.Content <-
            new Label(
                Text=&quot;This is SubPage&quot;,
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand )

遷移元(メインページ)は、こんな感じです。StackLayout.Children のところが入れ子にできないので、C# のように書けないのが残念なのですが(今度、拡張メソッドを作りましょう)、ひとつひとつ組み立てていきます。

type App() =
(*
    static member GetMainPage() =
        new ContentPage(
            Content = new Label(
                Text = &quot;Hello, Forms !&quot;,
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand ))
*)

(*
    let pushNextPage(next) =
            let AwaitTaskVoid : (Task -> Async<unit>) =
                Async.AwaitIAsyncResult >> Async.Ignore
            page.Navigation.PushAsync(next) |> AwaitTaskVoid |> ignore
*)
    static member GetMainPage() =
        let button = new Button( Text = &quot;Go Next Page&quot;)
        let layout = new StackLayout()
        layout.Children.Add( new Label( Text = &quot;Navigation.PushAsync&quot;))
        layout.Children.Add( button )
        let page = new ContentPage ( Content = layout )
        button.Clicked.Add( fun(_) ->

                // C# の場合は
                // await page.Navigation.PushAsync(new SubPage())
                // これで動作する

                /// 1.これで良さそうな気もするのだがこれは動かない
                // let t = button.Navigation.PushAsync( page )
                // t.Start()
                // t.Wait()
                ///

                /// 2.スレッドを変えてみたが、Android/iOS だけ動く
                // 何故か Windows Phone では動かず
                // let tk = new Task(fun() ->
                //    let t = page.Navigation.PushAsync(new SubPage())
                //    t.Start()
                // )
                // tk.RunSynchronously()

                /// 3. async すると戻り値がvoidなので、ややこしいのだが、
                /// この方式だと3プラットフォームで動く
                /// 参考 http://stackoverflow.com/questions/8022909/how-to-async-awaittask-on-plain-task-not-taskt
                /// async {} の場合、戻り値を持たない Task が使えないのでこうやるらしい。
                /// Page.Navigation.PushAsync が戻り値を持たないのでややこしいだけ。
                let AwaitTaskVoid : (Task -> Async<unit>) =
                    Async.AwaitIAsyncResult >> Async.Ignore
                page.Navigation.PushAsync(new SubPage()) |> AwaitTaskVoid |> ignore

            )
        new NavigationPage(page)

結論から言うと、PushAsync するときに、async の結果を捨てるための補助関数が必要になります。
F# では async/await を書けないので、async {} を使うわけですが、動きが微妙に違うのが曲者です。いや、どちらがいいという訳ではないのですが、一対一にコンバートできません。

/// 1.これで良さそうな気もするのだがこれは動かない
let t = button.Navigation.PushAsync( page )
t.Start()
t.Wait()

一見、これで動きそうな気もするのですが、iOS/Android の実装が異なるらしく動きません。Windows Phoneのほうも駄目なので、PushAsync が返している Task は別ものっぽいです。

/// 2.スレッドを変えてみたが、Android/iOS だけ動く
// 何故か Windows Phone では動かず
let tk = new Task(fun() ->
   let t = page.Navigation.PushAsync(new SubPage())
   t.Start()
)
tk.RunSynchronously()

描画スレッドの違いがおかしいらしいので、別に Task を作ると動くようになります。最初、Xamarin.Forms.XamlProvider は、この方式でやっていたのですが、昨晩 Windows Phone で動かしてみると実行エラーがでることがわかりました。

/// 3. async すると戻り値がvoidなので、ややこしいのだが、
/// この方式だと3プラットフォームで動く
/// 参考 http://stackoverflow.com/questions/8022909/how-to-async-awaittask-on-plain-task-not-taskt
/// async {} の場合、戻り値を持たない Task が使えないのでこうやるらしい。
/// Page.Navigation.PushAsync が戻り値を持たないのでややこしいだけ。
let AwaitTaskVoid : (Task -> Async<unit>) =
    Async.AwaitIAsyncResult >> Async.Ignore
page.Navigation.PushAsync(new SubPage()) |> AwaitTaskVoid |> ignore

結果的に async {} に沿う形に直してやると Windows Phone でも動くようになります。async {} の場合は、戻り値を持たない Task を扱うことができないので、AwaitTaskVoid のような補助メソッドを用意してやります。

Async を拡張して、Async.AwaitTaskVoid にすると、それっぽくなります。

module AsyncExtentions =
    type Async with
        static member AwaitTaskVoid : (Task -> Async<unit>) =
                        Async.AwaitIAsyncResult >> Async.Ignore
open AsyncExtentions
...
page.Navigation.PushAsync(new SubPage()) |> Async.AwaitTaskVoid |> ignore

調子に乗って、コンピュテーション式でやろうとして async do! を使うと動かないんですよねー。これは3機種とも動きません。

async {
    do! page.Navigation.PushAsync(new SubPage()) |> Async.AwaitTaskVoid
} |> ignore

というわけで、F# で PushAsync するときは、Async.AwaitIAsyncResult 拡張を作ってから呼び出すといい、というか注意書きということです。

カテゴリー: F#, Xamarin | Xamarin.Forms+F# で Navigation.PushAsync を使うときの注意 はコメントを受け付けていません

小4の夏休みの宿題をチケット駆動で乗り切る方法を模索

先日の日曜日、日割りでスケジュールを立てた後、1日のタイムスケジュールを立てたのですが、初日からうまくいかないことが判明…いや、途中にプールが挟まったり、学校の短時間の補習がはさまったりして、うまくいきませんでした。初日から、午後6時過ぎまで夏休みの宿題をしている有様に。

まずは、前提から書きますので、参考にしてください。

■夏休みの宿題の全体を把握する

先週の日曜日にやったことは、まず、夏休みの宿題の量を全体として把握しておくことです。

  • 漢字ドリル
  • 計算ドリル
  • 公文算数
  • 一言日記
  • 自由研究(かたつむりの観察日記)
  • 読書感想文

この中で、一言日記は毎日やることなので別な計画にします。自由研究はかたつむりの観察日記だそうで、これも毎日やります。読書感想文は、適当な締切を決めて(祖母の家に遊びに行く前とか、海に遊びに行く前とか)自由にしておきました。

公文算数は週5ペースでやるのですが、週2回教室に行くことになります。持って帰ってきた宿題は週のうちにやればよいので、土日を含んでもよいし、含めなくても良い状態です。

漢字ドリルと計算ドリルは全体のページ数を把握した後、「何日で終わらせるのか?」の計画を立てます。夏休み自体は40日あるのですが、これには土日が含まれています。また、お盆に祖母に家に行ったり、海に行ったりする日も含めてしまっています。これを取り除くと、だいたい20日になります。祖母の家や土日はプロジェクトバッファとして扱います。

■日割り計算する

計算ドリルと漢字ドリルの全体像を把握して、宿題をする日数が20日間と決まったら、これを日割りします。小数点になってしまうので、小4には難しい(分数や小数点は小5からですよね)。あまりを出して、それを上回れば計画通りに終わる、ということを子供に印象付けます。

ここでは、20日間で、2ページずつやれば終わることがわかりました。

そのままでもいいのですが、20日間ずっと宿題が続いているという状態がいいのか、「早めに宿題を終わらせたいのか?」という判断を子供にさせます。これは子供の性質にもよるので、どちらともいえないのですが「自発」を重んじます。

その後、再計画を立てて、計算ドリルと漢字ドリルを10日間で終わらせることにしました。

  • 計算ドリルを毎日4ページずつ
  • 漢字ドリルを毎日3ページずつ
  • 公文算数を平日にやる

という計画が立てられました。これに従って進めばいいのですが、さて、いろいろ「誘惑」なりがあり現実はままならないものです。

■ひとまず、計画を実行してみる

計画がスムースにいくのかどうかは、実際やってみないとわかりません。そのためにプロトタイプとして宿題をやるスピードを確かめるのが第一です。で、初日(火曜日)にやってみたところ、結構、散々な結果でした。

  • 午前中に学校のプールがあった。
  • プールから帰ってきて、宿題をやったかどうかは不明(ワタクシは家で仕事中)
  • お昼を食べた
  • 午後に、漢字と計算ドリルの宿題をやった…模様
  • 午後4時頃のテレビを見ていた。
  • 夕方に弟を迎えに行った。
  • 保育園から帰ってくると、公文が終わっていなかった。
  • 夕飯前(7時半)に公文を無理やり終わらせる。
  • 8時半ごろにカタツムリの観察日記を書いている。
  • 寝る直前に一言日記を書いている。

というわけで、ダメダメですね。これらは一見、学習習慣の問題のように見えますが、違います。まあ「習慣」ができると、早めにやる「習慣」ができるのですが、小4の段階ではまだ「習慣」以前なので、もうちょっと違う問題です。

基本、私は家で仕事なので、娘を監視できる立場にはいるのですが、それじゃ面倒ですよね。面倒というか、監視されないと宿題ができないのでは駄目ですし、これからが大変です。なので、面倒な監視作業はしません。が、「習慣」づけは必要なので、この夏はやる予定です。

■夏休みの宿題にも割り込みがたくさんある

毎日の課題(漢字ドリル、計算ドリル)をやるための障害として、「割り込み」があります。日常ルーチンをこなそうとしても、電話が入ったり、メールに答えないと駄目だったり、そういう問題ですね。私の場合、宅配便が一番集中力がそがれるのですが…まあ、これは仕方がない愚痴です。

プールなり学校の補習なりがあれば、一日の中で夏休みの宿題の時間が削られます。計画通り、むりやり夏休みの宿題をする時間をとればいいのですが(実際、毎日のルーチンワークとしての宿題は1時間以内でおさまるでしょう)、ルーチンワークができない小4の娘には、計画がずれても、計画通りに進むスタイルが必要になります。

■チケットを切る

宿題の日割りはできたものの、それを「毎日やる」ことにすると負担が出てしまうので、毎日やる必要がない状態にします。夏休みの宿題の場合は、

  • 全体の量が決まっている(途中でボリュームが増えたりしない)
  • 手戻りがほとんどない(やった宿題が無駄にならない)
  • 締切が決まっている(エンドが決まっている)
  • 分割が可能

という特徴があります。プロジェクトとしての組み立て方も WBS に落とすよりも、日割りで計算する簡単な方法がとれます。これは、読書感想文のような場合には、WBS な落とし込みをするか、別なスタイルが必要です。

というわけで、「毎日、計画通りに漢字ドリルと計算ドリルを数ページずつやる」という計画が破たんしてしまったので、「夏休みの宿題チケットを消費する」というスタイルに変更します。漢字ドリル3ページ、計算ドリル4ページのチケットをそれぞれ作って、これを10枚の付箋にします。全部で20枚になるわけですね。

■チケットを消費する

チケット駆動にすると、あまりに遅いペースでチケットを消費すると、エンドがずれるという現象がおこります。プロジェクトの場合は、締切を動かすことも可能ですが、夏休みの場合はエンドが絶対なので、締切を動かすことができません。このため、チケットが消費されているスピードは適宜「監視」が必要です。

本来ならば、バーンダウンチャートを使ってグラフ化するとわかりやすいのですが、ちょっとそのあたりは実際に娘にとってチケット駆動がうまく動くかどうか確かめてからやってみましょう。

カテゴリー: 雑談 | 小4の夏休みの宿題をチケット駆動で乗り切る方法を模索 はコメントを受け付けていません