2014年12月24日水曜日

F#における非同期Socket

.NET FrameworkにはVersion 1.0からSocketクラスがあり、APM; Asynchronous Programming Modelと呼ばれるBegin / End系メソッドが用意されています。しかし実際にはIOの度にIAsyncResultオブジェクトを作成する必要があり、ハイパフォーマンスなアプリケーションは実現しづらいものでした。 そのため、Version 2.0 SP1にてSocketAsyncEventArgsクラス及びAsync系のメソッドが新規に追加されました。こちらは内部状態を持つSocketAsyncEventArgsクラスを再利用することで効率の良い非同期処理が行えるものとなっています。なお、Version 4.5で導入された非同期処理とメソッド名の命名規則が一致していますが全くの別物となっています。
これをF#で扱えないものかと検索したところF#-friendly SocketAsyncEventArgsを見つけました。ただし、残念なことにSocketAsyncEventArgsクラスの設計思想を意識されておらず、毎回SocketAsyncEventArgsオブジェクトを再作成するだけの単なるwrapperでしかありませんでした。さらに言えばF#には非同期ワークフローもありますからこちらも利用したいところです。 仕方がないので自作してみました。 書いただけでまだ使っていないので動くかわかりません。
蛇足ですが、Socketクラスは内部でWinsockを使っていますが、このWinsockの機能の一つにaccept()で接続を受け付けると同時にrecv()を行うことができます。また対称にconnect()と同時にsend()もできます。こうすることでHTTPなど一般的なプロトコルでリクエストの送受信ができ、システムコール回数を減らし、システムの応答性能が向上します。Socketクラスはこの機能に対応しているため、今回の拡張メソッドにも含めています。

2014年9月10日水曜日

WPF ListView (GridView) のソート

WPFにはListViewのGridViewモードとDataGridの2つのコントロールで表形式の表示が行えます。DataGridの方はソート機能が組み込まれていますが、ListViewの方は自前でソートコードを記述する必要があります。MSDNにも方法 : ヘッダーがクリックされたときに GridView 列を並べ替えるという記事が用意されていたりしますがいまいちパッとしません。 そこで簡単に扱えるようにライブラリ化しました。 使い方は

<ListView ItemsSource="{Binding SelectedValue.Files, ElementName=tree}" xmlns:v="clr-namespace:Sayuri.Windows;assembly=GridViewSortLibrary" v:GridViewSort.IsEnabled="True"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListView.ItemContainerStyle> <ListView.View> <GridView> <GridViewColumn Header="名前" DisplayMemberBinding="{Binding Name}" /> <GridViewColumn Header="サイズ" v:GridViewSort.MemberPath="Length"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock TextAlignment="Right" Text="{Binding Length, StringFormat=N0}" /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView>
こんな感じです。要点は
  • xmlnsでアセンブリ・名前空間を指定します
  • <ListView>に添付プロパティGridViewSort.IsEnabled="True"を指定します
  • <GridViewColumn>にDisplayMemberBindingの指定があればそのプロパティでソートが行われます
  • <GridViewColumn>にDisplayMemberBindingを指定できない場合はGridViewSort.MemberPathにプロパティ名をしてします
ソースコードはGistに貼り付けておきました。
作成にあたって次の2つの記事を参考にしました。

2014年8月16日土曜日

F# のアセンブリ表現

F#は言語としてはとても素晴らしい設計をしていますが、コンパイルされたアセンブリは結構残念だったりします。F# + Entity Framewrok で ASP.NET WebAPI サーバ立てたら、返ってくる JSON がおかしいという記事を見かけたのでスコープについてまとめておこうと思います。 まずF#言語でアクセス制御するためにはpublic internal privateの3つのキーワードが用意されています。次にアセンブリでのアクセス制御についてはCorHdr.hに定義されています。関連部分を引用すると

// TypeDef/ExportedType attr bits, used by DefineTypeDef. typedef enum CorTypeAttr { // Use this mask to retrieve the type visibility information. tdVisibilityMask = 0x00000007, tdNotPublic = 0x00000000, // Class is not public scope. tdPublic = 0x00000001, // Class is public scope. tdNestedPublic = 0x00000002, // Class is nested with public visibility. tdNestedPrivate = 0x00000003, // Class is nested with private visibility. tdNestedFamily = 0x00000004, // Class is nested with family visibility. tdNestedAssembly = 0x00000005, // Class is nested with assembly visibility. tdNestedFamANDAssem = 0x00000006, // Class is nested with family and assembly visibility. tdNestedFamORAssem = 0x00000007, // Class is nested with family or assembly visibility. ... } CorTypeAttr; // MethodDef attr bits, Used by DefineMethod. typedef enum CorMethodAttr { // member access mask - Use this mask to retrieve accessibility information. mdMemberAccessMask = 0x0007, mdPrivateScope = 0x0000, // Member not referenceable. mdPrivate = 0x0001, // Accessible only by the parent type. mdFamANDAssem = 0x0002, // Accessible by sub-types only in this Assembly. mdAssem = 0x0003, // Accessibly by anyone in the Assembly. mdFamily = 0x0004, // Accessible only by type and sub-types. mdFamORAssem = 0x0005, // Accessibly by sub-types anywhere, plus anyone in assembly. mdPublic = 0x0006, // Accessibly by anyone who has visibility to this scope. // end member access mask ... } CorMethodAttr;
という感じです。 先にC#言語のアクセス制御の説明をしておくとわかりやすいでしょうか。C#言語ではpublic private protected internal protected internalの5種類です。これがアセンブリとどのような対応をしているかというと非常にわかりやすく
C#クラスアセンブリ表現
publictdPublic
internaltdNotPublic
C#メンバーアセンブリ表現
publicmdPublic
privatemdPrivate
protectedmdFamily
internalmdAssem
protected internalmdFamORAssem
となります。 さて本題のF#言語ですが、C#より種類が少ないはずなのに予想を超える複雑さをしています。まずクラスですが、
F#クラスアセンブリ表現C#相当クラス
publictdPublicpublic
privatetdNotPublicinternal
internaltdNotPublicinternal
と順当です。次にメンバーですが
F#メンバーアセンブリ表現C#相当メンバー
publicmdPublicpublic
privatemdAsseminternal
internalmdAsseminternal
…はい、F#上ではクラス外からアクセスできなくなるprivateですがC#のinternalに相当しアクセス可能となります。まだ簡単に見えますか? 恐ろしいのはここからです。F#にもC#にも自動実装プロパティがあります。プロパティの値を保持するためにコンパイラが自動的にフィールド(backing field)を用意しプロパティアクセッサを実装する機能です。当然、コンパイラによる自動的なフィールドである以上、プログラムからアクセスできるべきではありません。実際、C#ではmdPrivate、privateフィールドと同等です。さてF#はそうではありません。
F#メンバーアセンブリ表現C#相当メンバー
publicmdPublicpublic
privatemdAsseminternal
internalmdAsseminternal
backing fieldmdAsseminternal
…はい、privateメンバーと同様にC#のinternal fieldに相当します。まだ簡単に見えますか? 実はこの表はまだ不完全です。F#ではクラス自身のスコープがメンバーのスコープに影響を与えます。
F#クラスF#メンバーアセンブリ表現C#相当メンバー
publicpublicmdPublicpublic
privatemdAsseminternal
internalmdAsseminternal
backing fieldmdAsseminternal
privatepublicmdAsseminternal
privatemdAsseminternal
internalmdAsseminternal
backing fieldmdAsseminternal
internalpublicmdAsseminternal
privatemdAsseminternal
internalmdAsseminternal
backing fieldmdAsseminternal
…要するにpublicクラスのpublicメンバーだけがC#のpublic相当であり、それ以外はなんであれ全てC#のinternal相当です。ちなみにmutableでないフィールドにfdInitOnly(C#におけるreadonly)が付けられていないため、アセンブリ内からであれば書き換え可能という問題もあります。 これを踏まえて要望をまとめておきます。
  • backing fieldはmdPrivateにして欲しい
  • privateメンバーもmdPrivateにして欲しい
  • internal / privateクラスであってもpublicメンバーはmdPublicにして欲しい
といったところでしょうか。

2014年6月9日月曜日

艦これ 司令部室について

F# 談話室の15回に参加し、艦これ 司令部室について発表してきました。これに合わせてGitHubでリポジトリも公開しました。
COMの素晴らしさを力説し、また布教してきました。比較的に好感触でした。
たいしたことをは書いていませんが、発表に使ったプレゼンテーションはこちら


2014年3月12日水曜日

WinFormsのTextBox ControlでのIME制御

WinFormsのTextBox Controlの話題です。とても基本的なControlですが落とし穴がありました。IMEで漢字変換中にフォーカスを失うと未確定文字はそのままIMEが持って行ってしまいます。 WinFormsがこのような挙動をすることを知らず何も制御していなかったために、自作のアプリケーションがクソ呼ばわりされる事態に陥ってしまいました。 とても悲しかったのでTextBoxを派生して挙動を改良してみました。 IMEが開いているかどうかはImeContext.IsOpen()で調べることができますが、そのあと同じハンドルを使用して処理することになるため、IsOpenは使いませんでした。