誰もが思いつくような小ネタを一つ。
System.Runtime.Serialization.DataContarctSerializer クラスは WCF 用に用意されたシリアライザですが、特徴の一つに、同一オブジェクトを参照としてシリアライズし、同じオブジェクトとしてデシリアライズすることが可能という機能があります。
続きを読む今回のネタはEnumerableの拡張メソッドをGetMethodしたい から。
こういう拡張メソッドはどうでしょうか。Tuple<T1, T2> クラスはサンプルのために作った型引数を二つ取る適当なクラスなので、KeyValuePair<TKey, TValue> などで置き換えできます。ちなみに拡張メソッドにせず一部の Enumerale のメソッドを手で書けば .NET 2.0 以上で動くはずです。
あんまり管理もしてない今日この頃ですが皆様いか(略)。
今回は型引数をバインドするカリー化ができないかと言う話。
一応解説しておくと、カリー化とは要するに引数の一部を固定した関数を作ることです。こんなサンプルコードでご理解いただけるでしょうか。
staticint Add(int x,int y) {return x + y; }staticint Add5(int x) {return Add(x, 5); }publicstaticvoid Main() { Console.WriteLine(Add5(4));}
この場合、Add メソッドの引数 y を 5 に固定した Add5 メソッドを新たに作成しています。まあサンプルのためのサンプルなんで意味は無いですけどね。ちなみに関数作成はラムダ式など使って表現することが多いです。
staticvoid Main() { Func<int,int> addx = x => Add(x, 5); Console.WriteLine(addx(4));}' ちなみに VB だとラムダ式はこんな形になるそうな。PublicSharedSub Main()Dim addxAs Func(OfInteger,Integer) =Function(x) Add(x, 5) Console.WriteLine(addx(4))End Sub
本題を読む最終回の今回は検索機能を実装します。まあありきたりなことをするだけですが。
まず検索に使用する IBindingList のメンバを雛形から、また DataView のメンバもピックアップ。
bool IBindingList.SupportsSearching {get {returnfalse; } }void IBindingList.AddIndex(PropertyDescriptor property);void IBindingList.RemoveIndex(PropertyDescriptor property);int IBindingList.Find(PropertyDescriptor property,object key);publicint Find(object key);publicint Find(object[] key);public DataRowView[] FindRows(object key);public DataRowView[] FindRows(object[] key);
取り敢えず SupportsSearching は true 返すだけですね。
続きを読む多次元配列を私は嫌っていますが、世間的にはそれなりに人気のようです。DataGridViewに2次元配列のレコードを表示させたい と言うスレッドでは、二次元配列を DataGridView の DataSource にできないかという話が出ました。もっとも質問が微妙で、単純にデータソースとして二次元配列を使いたいのか、それとも既にデータソースに DataTable を設定していてそれにデータを追加したいのか判断に迷うところではありましたけど。
二次元配列なぞさっさと捨てるのが幸せになる方法だと思っているのですが、それはそれとして技術的興味はあります。
まず、データソースに使用できるものを調べてみます。DataGridView.DataSource プロパティ の解説によると、以下のいずれかと言うことになります。
続きお久しぶりです。自分のとこですが。
ちょうど書くネタが現れたので、久々に記事を書いてみました。ネタ元はpropertyGridで、プロパティ値を日本語表示したい というスレッド。
列挙型をプロパティグリッドに表示する際に、別名で表示したい。それには TypeConverter 属性を指定すればいいんですが、それを一々列挙型ごとに EnumConverter 派生クラスを作るのは面倒だと。
で、まあ上記のスレッドに私が書いたようなのが基本だと思うので、それをそのままコードに起こしてみました。というか試して動作を確認してから書いてるんで順序が逆なんですが。でも面倒なんで C# だけ。
コード今日はちょっと目先を変えてローカリゼーションの話。いや、某掲示板でうっかり名前を消し忘れたのでついでに。ほとぼりも冷めた頃かと思いますし。
.NET Framework では、アプリケーションのグローバリゼーション・リソースのローカリゼーションが比較的簡単に実現できるようになっています。
続きを読む本日は、Visual Studio® User Group より、複数のキーボードを別処理するには というスレッドを話題にしようと思います。飽くまで簡単に。
同じタイプの複数の入力デバイスからの入力を受けたい、と言うのは、例えばゲームパッドでは良くある話でしょう。ですが、これがキーボードやマウスとなるとなかなか要望も稀で解決策も難しい物があります。ついでに、使いこなすのも大変そうです。
基本的なところでは、Windows はそれらを扱いません。今使っているノートパソコンにはタッチパッドが付いていて、かつ USB でマウスも接続していますが、この両者はごくナチュラルに同居しており、どちらでもカーソルやボタンを操作できます。プログラム的にも、この両者からの入力は分け隔て無く .NET で言えば MouseDown とか MouseMove とかが発生します。そして、どちらからの入力なのかというのは考慮されず、透過的に扱うことを強制されます。
.NET のイベントや、その下層レベルのメッセージである WM_LBUTTONDOWN とか WM_MOUSEMOVE などで扱えないのならばどうするか。その解決になるかも知れない一つが、Raw Input に関連する API 群です。その端っこをちょっとだけ踏んでみます。
続きを読む今回は、C#とNTFSストリームの甘くない関係 のアップデートを行います。ただし、IPropertSetStorage を使ったアクセスは自由度が低いと感じたため、ITaskScheduler みたいなきっちりしたコードではなく、飽くまで「こうすれば動く」程度にとどめたいと思います。
以前の記事では COM オブジェクトが例外出しまくりで何でさ、で終了でしたが、ちょっと COM の知識が増えた今では何が問題だったのかは明らかです。大雑把に言って、COM オブジェクトは IUnknown または IDispatch から派生しますが、IDispatch は名前でメソッドを検索するのに対し、IUnknown は 先頭からの宣言順で検索します。つまり、以前書いたコードの IPropertySetStorage は宣言が Enum メソッドしかないため、IUnknown 的にはこれは Create メソッド相当なんですね。当然引数が全く違うのでメソッドは失敗していたってわけです。
メソッドの宣言順が大事なのであって宣言の正当性はわりとどうでも良いので、以前書いた IPropertySetStorage インターフェイスは以下のように書き換えれば Enum が成功するようになります。
[ComImport, Guid("0000013A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]public interface IPropertySetStorage { int Create(); int Open(); int Delete(); [PreserveSig] int Enum( [MarshalAs(UnmanagedType.Interface)] out IEnumSTATPROPSETSTG propSetStg); }}続きを読む
掲示板で答えるときは大抵事前に動作確認するわけですが、「画面プロパティ」の「画面の色」のビット数を取得・設定するには? というスレッドで「API 使えばできるさ」と答えた関係上、当然ながら最小限のコードで試しています。
で、そのついでに System.Windows.Forms.Screen クラスをいじっていたのですが、ここでちょっとした問題に気付きました。
Screen.DeviceName でディスプレイのデバイス名を取得できますが、これで取得できる文字列は実は本来の文字列よりも長くなってます。ちなみに取得する用の関数で使用する構造体のデバイス名格納用メンバはTCHAR[32]
と定義されています。で、これを .NET の char[] で表現して、そのまま String のコンストラクタに突っ込んだだけみたいな形なんですよね。
つまりわかりやすく言うと、NULL 文字以降のまで保持しちゃってるってことです。
実用上はほぼ問題ないでしょうけど(表示するにもアンマネージドに渡すにも NULL 文字で終端されるでしょうからね)、ちょっとだけ気になった点でした。
引き続きシリーズの外。今回は完全に外。@IT のネイティブアプリとCLRアプリの違い スレッドより。
スレ主さん曰く、Becky! のリストビューは第一カラムが幅固定であると。.NET のプロパティにはそう設定できるプロパティがないと。まあそういうことらしいです。しかし考えてみれば生 API 使うのならもともとプロパティだのなんて存在しませんし、MFC の CListView クラスなどにもそんなのありません。あるのはただ WM_NOTIFY と いくつかのメッセージのみ。別にこの辺がアンマネージドというわけでは、まあありますが、幸いメモリのことは一切勘案することなく、unsafe にもしないで、Marshal クラスも使わず書ける部分です。
WndProc のオーバーライドだって普通に存在しているんだから使わなければ損損。と言う比較的現実主義な私としては早速実装してみるのでした。
続きを読む予告通り、三回目にして速攻で脇道に入ります。
アンマネージドとの相互運用において、面倒なものの一つにアンマネージドメモリの管理があります。.NET ではアンマネージドメモリを扱う場合 System.Runtime.InteropServices.Marshal クラスを使用してアクセスしますが、問題の一つに、解放忘れ即メモリリークにつながるため try-finally が欠かせない、と言うのがあります。面倒です。他にも、バイト配列との相互コピーも事前にバイト配列を確保する必要があったり、文字列を書き込んでも結局何バイト書き込んだのか分からなかったりとか、色々扱いづらい点があります。そこで今回、この辺をクラス化して、多少便利に扱えるようにしたいと思います。もちろん、便利さと速度はプログラミングにおいて大抵は相反するものでして、今回作るのも実行速度に対するパフォーマンスという点は目を瞑っています。ボトルネックになる部分では使用するのも少し考えた方が良いかもしれません。逆に言えばボトルネックでないのなら(私は)気にせず使います。もっとも一回の呼び出しで何秒も使うようなものでもないですから、これの使用そのものがボトルネックになることはあまり考えられませんけど。
ところでこのコンセプトで以前記事を書いたことがあります。05/10/22 の多少は使いやすい共有メモリクラス、とその VB.NET 向け記事 共有メモリクラス for VB.NET です。今回作るクラスはこれを踏まえて、更に改良を加えて(ると思いますけど)作成しています。この共有メモリクラスも今回作ったクラスに応じて書き換え……は今回の記事にするには容量がはみ出るかな。
続きを読む今回から数回にわたって、シリーズとしてお送りしたいと思います。題して、……この記事のタイトルに出てますね。コードは(ほぼ)完成しているので、あとは記事を書くのみ(いやまあそれも骨なんですけど)。
ちなみに、こんなタイトルですが、実際に COM の部分はそう大した量ではないと思います。.NET とのギャップのマッチング部分が私の作成した実装の肝ですし、COM インターフェイスやアンマネージドメモリを便利に扱うためのラッピングクラスにもそれなりに気を配ったり。
いや、一番疲れたのは XML ドキュメント作成部分でしたけどね。
続きを読むお久しぶりです。なかなか書くネタが無くてとぎれがち。
今回はきっちりした記事ではありませんが、普段テストコード書くときはこんな風、というのを無意味に晒してみようという趣旨です。
ネタ元は透過GIFのリサイズというスレッド。残念なことに力及ばず質問者の方を困惑させてしまっただけになってしまい大変申し訳ないのですが。
その課程で使用したテスト用のコードがまあ確かにそれなりの長さになったので、そのまま破棄するのももったいないかと思ってここで再利用というわけです。
ほぼ無修正なので全く読みやすくはありませんしコメントももちろん不十分、途中でこの中には含まれないクラスやらメソッドやらが出てきたりと、参考にもしがたいものですが、まあ公開を意識しなければこんなコードだよ、と言うところです。
ちなみにここから公開用に清書しようと思ったら多分何時間単位だと思います。
問題のコード本日のお題はいつものように@IT会議室より、FromHandleで生成したIconをSaveしたときというスレッドです。
質問内容をかいつまめば、HICONから作ったIconインスタンスをSaveすると何故か16色で保存される、どうしよう、ってことなんですが。
続き今回のお題はDataGridViewの、DataGridViewComboBoxColumnについて。
私がDataGridViewを好かない理由の一つに、この長ったらしい型名があります。DataGridViewがプリフィクスになっちゃっててもうウンザリ。名前空間分けるとかしてもうちょっと何とかならなかったもんですかね。
過去ちょっと名前を挙げただけなのに、わざわざDataGridViewを含むクエリで検索エンジンからいらっしゃる方もいるくらいには需要があるキーワードのようなんですが、まだまだ.NET2.0が出て日も浅く、なかなか資料が見当たりません。単にDataSourceにデータ突っ込むだけみたいなDataGrid代わりとしてはいいんですが、ちょっと踏み込むともうなにがなにやら。
中でも使いやすそうででも案外そうでもなさそうなのが、今回の話題、DataGridViewComboBoxColumnクラスです。
DataGridの頃からComboBoxカラムの要求は強かったことと思います。DataGridViewでついに待望のComboBox追加、なんですが、これがまあどうにもよく分からない。
文字列コレクション扱う分にはどうってことありません。なぜか選択中のCellからSelectedIndexが取得できない(可能なのかな? 見渡した感じ不可能っぽい)なんてよく分からない制限(後々考えると理由も分かるのですが)もありますが、まあ許容範囲内です。一応。
ですが、普通のComboBoxのノリで独自オブジェクトを突っ込んだ途端、深い沼にはまります。
今回はなんとなく操作画面を乗っけてみました。まるで効果的で「ない」使い方ですが。
続き今のところサブ機たるデスクトップPCで頑張っています。一番慣れないのはキーボード。ノートのペラいキータッチに慣れると指の動かす距離(左右奥手前以外に上下にも)が長くなって面倒な感じです。ノートなら左下がCtrlキーでも問題ないんですが、なるほど普通のキーボードだと左下って遠いですね。英数キーに割り当てたい人が多いのも納得。小指付け根で押す派の存在にも頷けます。ていうかなぜこのキーボードにはWinキーがないんだろう。
さて、まともな開発環境のない状況ですが細々と簡単な実験コードくらいならSDKすらなくても何とかなるもんです。今回のネタは、構造体へのポインタからByteの一次元配列へのキャスト方法です。
えー、あらかじめ言っておきますが、アンマネージドとやり取りするわけでもないのにMarshalクラスを使ったりunsafe構文を使ったりするのはお勧めしませんし柔軟性に欠けるため使いにくい部分も多く出るので、今回のは飽くまで参考程度に止めて下さいね。私のこれはまあ言ってしまえば趣味みたいなもので。
さて、構造体とバイト列。C/C++ならごくナチュラルに相互変換するものです。というか何であれデータはバイト列で表されるわけですから、両者に明確な区別はなく単にプログラマが分かりやすいようにそれっぽく形を与えているだけと極論してしまうこともできます。
が、C#やVB.NET、またその背景にあるCLIでは両者は厳密に区分され、それぞれは内部で異なった表現がなされます。端的に言えばそれぞれはクラス(型と言った方が総称的で良いですが。C#/VB.NETではクラスと構造体は峻別されるから)のインスタンスとして表現され、継承関係にあったり型変換が独自に定義されていない限り自由に型を変更することはできません。なによりも、メモリの管理にガベージコレクタを採用している関係もあって、「あるオブジェクトの存在するアドレスが一意である」とは限らないのが重要です。つまりオブジェクトは移動し得ます。
というわけで、基本的にC/C++的な変換はできません。.NET的には、それぞれ独自のToBytes/FromBytesメソッドなどを自前で実装するという方向が正しいでしょう。いや、実際のところ.NETで完結するのならBinaryFormatterとか使ったほうが良いと思いますが。
しかしそれでも、C/C++的な変換をしたいという要求はあります。
そのための手段として以下のような方法が考えられます。
unsafe構文でポインタを利用して型を強制的に変更し、強引に直で代入する方法。まさにCなやり方です。
構造体ポインタをunsafeで扱おうとする場合、相当制約が厳しい点に注意しましょう。例えばunsafeポインタはMarshalAs属性を考慮しません。つまり.NET 1.0/1.1では固定長配列や固定長文字列を簡単に表現する手段が使えないということになります。.NET 2.0では構造体そのものにunsafeコンテキストを適用しフィールドにfixedキーワードを付けることで、unsafe構文内で固定長配列を表現できるようになりましたが、こちらは逆にそのfixedフィールドを非unsafeなところで扱えなくなります。
Marshal.StructureToPtr・PtrToStrucutureメソッドを使用する方法。この場合問題はどうやって引数になるIntPtrを確保するかという点ですね。
Marshal.AllocCoTaskMemメソッドでバッファを用意する。これで構造体とIntPtrをやり取りし、更にMarshal.Copyメソッドを使用してIntPtrとバイト配列とをやりとりします。
間にバッファを入れるのは不効率ってもんだということで、直接バイト配列のアドレスをどうにか取得する。それには配列をGCの影響からはずす、つまりオブジェクトを固定しなければなりませんが、そのためには一般的にGCHandleクラスを使用します。GCHandleType.Pinnedを指定してAlloc静的メソッドを呼んでやれば、生成されたGCHandleインスタンスのFreeメソッドを呼ぶまでGCの管理対象から外されるのでアドレスを問題なく利用することができるようになります。そのアドレスを取得するのにはGCHandle.AddrOfPinnedObjectメソッド、またはMarshal.UnsafeAddrOfPinnedArrayElementメソッドを使用します。
ところで、なぜUnsafeAddrOfPinnedArrayElementメソッドは引数に配列を取るんでしょうねぇ。PinnedでないといけないのならいっそGCHandleを引数にすれば良いのに。と、@ITのMarshal.UnsafeAddrOfPinnedArrayElementについてというスレッドのやり取りで思いました。
上に加え、unsafe構文を使うことで、GCHandleを使わず直接fixedステートメントを使うことで配列のポインタを取得する。GCHandleを使わないことがわざわざunsafeにするほどのコストかどうかは後述。
マーシャリングを最大限に生かす。マーシャリングってのは元々アンマネージドとの相互運用のために存在するのですから、相互運用させましょう。つまり、Win32APIのCopyMemory関数を呼びます。マーシャラは配列をアンマネージドとやり取りする際、自動的に固定します。またポインタを意識する必要もありません。参照を渡せば勝手にポインタにしてくれます。
Marshal.StructureToPtr・PtrToStructureを使うにせよ、CopyMemoryを使うにせよ、扱える構造体にはマーシャラによる制限が課されます。.NET 1.0/1.1において、メンバに構造体の固定長配列をMarshalAs属性を使って表現することができないなどが代表的なものでしょう。
さて色々手段が提示されました。これらの中で一体どれを採用すべきでしょうか。
まず、できればunsafeは避けたいものです。アセンブリごとunsafeになっちゃうし。ということで始めのは除外。
記述の手間はいずれもそう大差ないかと思います。毎回記述するのならできる限り短い方が好ましいですが、汎用関数化するのなら記述の長短は問題でなくなります。汎用か専用かも問題ですね。一般に汎用性を求めればパフォーマンスは犠牲になるものですし。
あとは実行コストの問題ということになります。これはCLRのバージョンや利用者のPCによっても影響されるので、特にこれならOKという記述を断言することはできません。そもそも何十万回も呼び出すのでない限り、コストが実動作に影響することは無いと言って良いでしょう。
とはいえベンチマークは趣味でやる分には楽しいものです。また様々なテストをすることで意外な事実を知ることもあったりとかも。
続きを読む今回のネタは、@IT会議室より、ドラッグ中のマウスカーソルについて。
今まで知らなかったんですが、リストビューのドラッグで表示される半透明イメージもしっかりAPI化されているんですね。該当スレッドのTdnr_Symさんの投稿で初めて知りました。レイヤードウィンドウでも使うのかな、いやそれじゃWin2000以降限定になるし、とか。
知ったら使ってみたくなるのが人情という物なので、早速C#で実装してみます。
概要は簡単な物で、TVM_CREATEDRAGIMAGEメッセージによるドラッグ対象イメージの取得、それらに対するドラッグ用の各種関数をマウスのイベントで実行、と言うだけです。
悩んだのは複数アイコンを選択・ドラッグしたときにどうするか。TVM_CREATEDRAGIMAGEメッセージでは任意の一つのアイコンのイメージしか取得できません。またドラッグを開始する関数であるImageList_BeginDragも、自身のイメージリストの中の任意の一つのイメージしか引数を取らないため、イメージリストにイメージを追加するというのも意味がなさそうです。そもそもImageList_AddはHBITMAPを引数に取るので、イメージリストを返すTVM_CREATEDRAGIMAGEメッセージとは食い合わせが悪いですし。で、MSDNを調べてみたところ、どうやらImageList_Merge関数がどうやら二つのイメージリストをくっつけるのに使えそうです。名前もそのままですが。
あとは適当にちゃちゃっと。簡単にできたのは良いんですが、問題点が二つも出ました。
一つ目は、自分以外のウィンドウに出られないこと。ImageList_DragEnterをデスクトップのハンドルに対して実行し、SetCapture関数を使えば可能なんですが、これやるとListView派生クラスで完結できなくなるような気が。キャプチャできるのはフォームだけですよね……?
で、更にもう一つ、もっと致命的(かどうかは微妙)なのは、ViewがLargeIconのとき、なぜかラベル部分がアイコン部分に比べて右にずれるということです。原因不明。アイコン一つだけでもなるので、ImageList_Mergeには関係なく、TVM_CREATEDRAGIMAGEメッセージで取得できるイメージそのものがおかしいと言うことになるんですが。
まぁそう言うわけで、取りあえず私の元では日の目を見ることがなさそうな技術でした。
参考にもならないようなコード毎度すっかり更新がおろそかになっています。
今回はすぐコードというわけではないですが、NTFSストリームを調べる方法の考察。
NT系で採用されているファイルシステムNTFSには、ストリームという概念があります。一つのファイルに対して複数の情報を持たせることができる仕組みです。ファイルのプロパティの概要タブで色々情報を書き込むことができますが、これはNTFSストリームを利用して実現されています。
ストリームへのアクセスは、[ファイル名]:[ストリーム名]で行います。例えば、hoge.txtにUserストリームを追加しそれにHongliangと書き込むには、コマンドプロンプトで
echo Hongliang> hoge.txt:User
と書くだけでも可能です。echoコマンドで出力されるHongliangをhoge.txt:Userというファイルにリダイレクトで書き出すわけですね。ただし、typeコマンドはNTFSストリームには対応していないようなので、より一般的にはWSHでFileSystemObjectを使用して読み書きするのが良いでしょう。普通にファイルのように扱えます。難点は、あるファイルにどんなストリームが存在するかを確認する手段がないっぽいことです。APIプログラミングには、二通りのアクセス方法があるようです。一つはBackupRead/BackupWrite関数を使うやり方。もう一つはIPropertySetStorageというCOMインターフェイスを中心としたやり方です。
このうち、BackupRead系は、比較的単純です。ファイルに存在する全てのストリームが並べられ、それぞれのストリームにヘッダが用意され、プログラマはそのヘッダを利用してストリームを判断することになります。しかし、これを使った場合、先に挙げた「ファイルのプロパティの概要タブで色々書き込まれた情報」にはまともにアクセスできません。なぜならそれがバイナリデータになっているからです。
もう一つのIPropertySetStorageインターフェイス絡みの方にはもっと頭の痛い問題があります。何故かC#で定義したIPropertySetStorageのメソッドが巧く動かないのです。StgOpenStorageEx関数を使用してIPropertySetStorageを取得します。これは問題ありません。じゃあプロパティを列挙しようとEnumを呼ぶと、out IEnumSTATPROPSETSTGで定義した場合は「フラグが無効です(STG_E_INVALIDFLAG)」と言う例外が、またout objectの場合は「値が有効な範囲にありません」と言う例外が、unsafeでIntPtr*を指定した場合も「 が見つかりませんでした(STG_E_FILENOTFOUND)」という例外が投げられるんです。これには困り果てました。なにせ等価の(はずの)コードをC++で書くと問題なく成功するのですから。ん〜、なにかCOMインターフェイスの宣言に不味いところがあるんでしょうか……。PreserveSig属性をつけたらAccessViolationExceptionが出るようになるし。
さて、どうしようかな……素直にManaged C++で書くか?
その後色々と勉強して、不可能ではないことが分かりました。で、 記事に纏めた ので、そちらを参照してください。
問題再現コードここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。
ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。
あ、こんなのに使ったってコメントを頂ければ嬉しいです。
この広告は90日以上新しい記事の投稿がないブログに表示されております。