2006年03月10日

COM クライアント実装の道程 for TaskScheduler その番外編2 〜 COM オブジェクトと GC とファイナライザ

ノートの方には MSDN の Platform SDK 部分は入れていなかったので、この記事を書くのにオンラインの MSDN を参照することもあります。で、気づいたんですが、なんか TaskScheduler の関連インターフェイスが増殖してる……。え嘘マジ? と大慌てで調べてみたら、その辺全部 Vista で追加されるインターフェイスのようです。…………。ふ、今やってるのも Vista までの寿命か……。いや後方互換性は残されるみたいですけど。Vista では ITaskScheduler の代わりに ITaskService を使って操作するスタイルになるようですね。増えたインターフェイスは ITaskScheduler では構造体として扱っていた部分みたいです。あるいはそもそも WinFX でサポートされるようになるのかしらん。

さて、今回のお題は COM オブジェクトの面倒くささについてです。知っている人は知っている、というか最近特にクローズアップされだしているような気がしますが(というか私がそう言う情報に敏感になっただけかも)、CLI の基礎でもあるガベージコレクタと COM オブジェクトとは、ひたすら食い合わせが悪いのです。

続きを読む
posted by Hongliang at 03:09| Comment(1) | TrackBack(1) | .NET | このブログの読者になる | 更新情報をチェックする

2006年03月08日

COM クライアント実装の道程 for TaskScheduler その4

いつまでもデスクトップを使っているのは電気代にも優しくないので、そう言えば、と初代ノート PC を引っ張り出してきました。液晶モニタが割れたので新しいのに買い換えたんですが、逆に言えば液晶モニタの問題だけでしたので、デスクトップで使っているモニタが二系統の入力を受け付けているのに目を付け、バルクのモニタケーブルを調達してきてでかいモニタでノートです。操作感的には大して不都合もないのですが(意外に液晶部分もそんな気になりません)、難点は致命的に重いってこと。やたら HDD へのアクセスが発生してるところからみてスワップしまくってんでしょうね。HDD も 4200rpm と旧式ですし。メモリが 240 MB( 16 MB はビデオメモリに割り当て)ってのはやはり厳しいものがあるようです。特に開発に使うものじゃありません。あまつさえ .NET となるともう、OpenFileDialog 開けるだけでなんか 30 秒とか待たされたり、ってなんかほかの要因もあるような気が。

さて、前回の続き。今回はインターフェイスの残りをまとめてやってしまいましょう。TaskScheduler の項目にある六つのインターフェイスのうち、一つは無視すると初めに宣言しました。さらに IScheduledWorkItem は完全に ITask に吸収させることにしました。ので、残りは二つ。いずれも小さい単純なインターフェイスです。

続きを読む
posted by Hongliang at 03:43| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2006年03月07日

COM クライアント実装の道程 for TaskScheduler その3

何故か ID3 タグの仕様を調べています。id3v2 には .NET のライブラリとして UltraID3 と言うのが紹介されていて、初めはこれ使えばいいやと思ったのですが、なんとエンコーディングが Unicode と ISO-8859-1 のどちらかしか選べないと言う致命的な問題点があることが判明。これじゃ大抵 Shift_jis 使ってる日本では使えません……。他にこれほどの柔軟性があるのも見当たらないし。

さて、そんなのは置いておいて、COM インターフェイスの実装の続きと参りましょう。

二回連続で脇道に入っていたこともあって些か記憶が曖昧ですが、確か前回は ITaskScheduler インターフェイスの定義とそのメソッド一つの実行に成功したところだったはずです。今回はタスクスケジューラの中心的インターフェイスである、ITask インターフェイスについて話を進めていきましょう。

このインターフェイスの特徴に、IUnknown から直接派生しているわけではなく、IScheduledWorkItem から派生している点があります。.NET において、このインターフェイスの継承というのはどう扱うべきでしょうか?

続きを読む
posted by Hongliang at 05:33| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2006年01月21日

圧縮! ただしNTFSだより

色々ごたごたしていて中々記事を書く暇もありません。

言い訳から始まる久しぶりっぷりです。

さて、今回は某掲示板で発せられた「ファイルの圧縮属性」についてのお話です。

NTFSでは、読み取り専用とかの属性に、新しく圧縮という属性が追加されました。その名の通り、この属性つきのファイルはファイルシステムが自動的に圧縮してディスクに保存してくれるようになります。読み取る側は別にこの属性の有無を把握する必要は無く、ごく普通のファイルとして読み書きできます。もちろん、入出力の際多少コストが増加するのは間違いないですが。また、最近はHDDが安くなったこと、ファイルサイズが大きくなるもの(画像、音声、動画など)は大抵圧縮機構が形式に組み込まれていることなどから、有効性は薄れているでしょう。

.NETではファイルの属性はSystem.IO.FileクラスのGetAttributes/SetAttributesメソッドなどを通して取得・設定するのがスタンダードです。これに使用するFileAttributes列挙体にはCompressedメンバが存在しているため、これを使えば話は早い。

はい、わざわざ記事にしている通り、早くありません。なんとこの属性、変更できないのです。例外も投げられず、ただ変更が無視されるだけ。どういうことかしらん、とSetAttributesの実装を見てみると、このメソッドはどうやらWin32APIのSetFileAttributesのごく薄いラッパです。今度はMSDNでSetFileAttributesを調べることになります。そこには、普通にファイルの圧縮状態を設定するには、FSCTL_SET_COMPRESSION の動作を指定して DeviceIoControl 関数を使ってください。とナチュラルに書いてありました。

ここではこういう思考の流れで正解に辿り着くよう書いていますが、実際に私がこの問題に突き当たったとき、何故かWin32APIのCreateFile関数を調べていました。何故CreateFileを調べようと思ったのかは自分でも本当に謎です。まあ、多分「属性はファイル作るときにも設定するよな、なら圧縮属性のことも書いてあるはず」ってことなんでしょうけど。とはいえFileStreamクラスのコンストラクタには別にFileAttributesを引数にとるオーバーロードがあるわけでもないのに。

ということで、DeviceIoControlです。デバイスと直接制御コードでやり取りする、かなり低レベルIO用のAPIなので、あまり使いたくは無い関数ですね。汎用性が高くなるように曖昧な型で書かれているので、そのままでは少々扱いにくい関数になっています。

設定、または取得は、ファイルのハンドルを通じて、定数FSCTL_SET_COMPRESSION、FSCTL_GET_COMPRESSIONを使用してDeviceIoControlを呼び出します。ちなみにこれらの定数は簡単なマクロで、アクセス許可やらがビット演算されているので移植はちょっと面倒です。

さて、基本骨子がわかったところで.NETでの実装に入りましょう。

まず、DeviceIoControlですが、前述の通り少々扱いにくいので、今回はDllImport属性のEntryPointフィールド、またはDeclare構文のAliasキーワードを利用し、同じ関数のオーバーロードとして圧縮属性取得・設定限定の別名定義を行うことにします。そうすればポインタの受け渡しもref/out(ByRef)で扱え、Marshalクラスのメソッドを使用する必要もなくなります。なにより見た目分かりやすくなります(と思ってるんですが)。ref/out/ByRefは便利な構文ですが、NULLを渡せないのが欠点ですね。

ファイルシステムがNTFSかどうかを確認しなければなりませんが、面倒なので省略。多分FAT32ならSetFileCompressModeは必ずfalseを、GetFileCompressModeは必ずNoneを返すはずです。多分。.NET 2.0ではSystem.IO名前空間にDriveInfoクラスが増えて、簡単に取得できるようになりましたが、1.0/1.1ではWMIかAPIか、さらにWMIはWMI自体が使用可能かどうかという問題もありますし。

.NET 2.0になって、CERなるものが使えるようになりました。詳しくはMSDNの解説に譲るとして、私の理解した範囲内では、指定したtry-catch-finally内で、そのブロック内の処理をスレッドが破棄されようがStackOverflowExceptionが出ようがアプリケーションドメインが破棄されようが確実に実行する手段、と言った感じでしょうか。トランザクション的と言えばいいのかな。

リンク先はMicrosoft Passportのサインインが必要で、サインインにはJavaScriptが必要なんですが、タブブラウザ使ってデフォルトではJavaScriptを切っている私としては面倒なことこの上ない。何が面倒かって、403のアドレスにリダイレクトしやがることです。403画面じゃなくて入力画面にリダイレクトしてnoscript要素を使えと。そうすればワンタッチでJavaScriptをオンにしてさっさとサインインできるのに。

Win32のハンドルを扱うのもこのCER内でやると安全になります。例えば

IntPtr handle = CreateMutex(IntPtr.Zero, true, null);

みたいなコードにしても、CreateMutexの実行と取得したハンドルをhandleに代入するのは別々のアクションです。CreateMutexした直後、handleに代入する前に何らかの原因で実行が中断された場合、このMutexを解放する手段が失われてしまう、つまりリークしてしまいます。この処理をCER内で行うことで、実行が中断されようとしたときもその中断は遅延され、確実に処理が行われるようになるというわけです。

まだ理解が行き届いてませんのでコーディングも見よう見まねですけどねー。

さて、冒頭のきっかけとなった某掲示板の話ですが。MSDNでDeviceIoControlを知って、テスト用にざっと実装、動作を確認したうえでさあ答えよう、と見たらもう質問者がそれに辿り着いていました。切ない。

まあ折角調べてコード書いたので、清書してここに掲載する次第ですが。なお、C#版XMLドキュメントつきコードはここからどうぞ

続きを読む
posted by Hongliang at 18:54| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年12月19日

Visual Studio 2005 Express Edition と .NET Framework 2.0 SDK

さて、先頃遂にVisual Studio 2005 Express Editionがダウンロード可能になりました。事実上無料の開発環境ですから、利用者もこれまでに比べて多少は増えることかと思われます。あんまり変わらないような気もするけど。

私も早速ダウンロードしてインストールいたしました。まだまともにいじっていない(どうせ基本的にはxyzzyでやるしー)ですが。

さて、某掲示板で妙な意見の食い違いがありました。Express EdisionにはSystem.Runtimeがないとか言うんです。いやそもそも標準ライブラリの中に入ってるんだからそんなのありえないし。

どうやらドキュメントにSystem.Runtime名前空間が出てこないらしいんですね。え、でも私のところじゃ見えてるし。別物?

ということで調査の結果。

  • Express Editionを入れただけではクラスライブラリのドキュメントは不完全である。
  • 2.0 SDKを入れればクラスライブラリのドキュメントは完全なものが入る。
  • Express Editionと2.0 SDKの両方を入れても問題なく完全なクラスライブラリドキュメントを使用できる。インストールする順番は問わない。

ということのようです。Express Editionをインストールしてさあ開発という方、ご注意を。もちろんベータ2のSDKとか入れちゃ駄目ですよ。

posted by Hongliang at 00:55| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年12月07日

名前付きイベント

なんとなくふと思い立って、というかまあぶっちゃけ起動中アプリケーションに対しての処理についてというスレッドでレスをつけていて思ったんですが、そう言えばプロセス間通信に名前付きイベントオブジェクトって使えるよなー、と。

.NET FrameworkではAutoResetEvent・ManualResetEventとしてWindowsのイベント機構が実装されていますが、何故か名前なしのイベントしかサポートされていません。これではプロセス間の調停機構には使うことができません。Mutexは名前付きの物をサポートしていますが、用途が違います。

ただ状態を渡すことができないので、使い道は果てしなく限られそうですが。

実装は簡単です。ほとんど基底クラスであるWaitHandle任せと言っても過言ではありません。基底クラスのHandle(.NET 2.0ではSafeWaitHandle)プロパティを使用すれば自前でハンドルを管理する必要すらありません。

どうせ実装するんだったら名前付きパイプとかの方が実際的な気がしますが、まあ簡単だったからということで一応。通知できるMutexみたいなノリで。短いコードなので不要な気もしますが一応XMLドキュメント付きC#のソースもどうぞ。

ソースを表示する
posted by Hongliang at 23:18| Comment(3) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年11月03日

SusiePluginラッパ

さて、随分久し振りになってしまいました。過去のストックが切れてきたこと、忙しいことが組み合わさって途端に更新がぼろぼろです。

あんまり更新がないのもアレなので、以前ちょっとだけ触れたSusieプラグインラッパを紹介しましょう。

ManagedC++で書かれており、目的はSusieプラグイン、つまりspiファイルを読み込んでそれらの関数を.NETから扱えるようにすることです。が、正直C++に対する知識が今一つなのでC++的にはどうかと思うところも多いかと思いますがご了承を。一番分からないのはファイルをどう分割するかなんですが。それぞれの.hで#defineさせ、#ifndefでヘッダをインクルード済みかどうかをチェックさせてみたんですが、一体どう言うのが本道なんでしょう。

時代はC++/CLIだろ、とか言わない。.NET 2.0以降でしか使えないじゃないですか。逆は/clr:oldSyntaxで可能だけど。

仕組み自体は、ごく単純にWin32APIを使用してLoadLibrary/GetProcAddressを使用して呼んでいるだけです。ただそのままではメモリハンドルが返ってきたりBITMAPINFO構造体だったりで.NETからは扱えた物じゃないので、内部で.NETのオブジェクトになるように変換作業も行っています。関数自体、.NETに合うようにオーバーロードや名称変更などを使って極力Susieプラグインの仕様が見えないようになっています。これは長短ある手法でしょうが、私は元々APIの構造体とかもC#で定義する際名前変えたりする人ですし。

メモリに書き出すタイプの関数呼び出しをどうするか困ったのですが、.NET風に行くと言うことで、一旦読み出してMemoryStreamに書き出し、それをStreamインスタンスとして返すことにしました。パフォーマンスはかなり犠牲になるでしょうが、Image.FromStreamとかを考えるとその方が自然かと思ったからです。あ、引数で受け取ったストリームに書き出すってのでも良かったか。まあ必要なら後から付け足す方向で。

ストリームからストリームへの操作が面倒なのが、ストリームの難点と言えば難点。この部分、適切なラッパクラスとかないんですかね。自分で書けとか言うことですか。

SharedMemoryクラスでやったように、これも抽象クラスSusieWrapperの下に実装クラスSusiePictureWrapper・SusieArchiveWrapperがあり、それぞれはSusieWrapperのLoadSpi静的メソッドで自動的に判別され作成されます。SharedMemoryと違うのは、利用者はどちらのインスタンスであるのか理解している必要がある点です。判別用にプロパティでも持たそうかと思いましたが、それは型で判断するなりして貰えばいいかと思って止めました。

全体にまだ完成品というわけではないので、仕様が少々胡乱です。実装させるに当たっての大きな問題は、サポートしない関数呼び出しが行われるときにどうするかと言う点です。ここでは問答無用でNotSupportedExceptionを投げていますが、それだと呼び出し全てに例外ハンドリングが必要になってしまうのでどうしたものかと。CanHogehogeプロパティをpublicに持ってきても実際のSusieWrapperに実装されているメソッドとはズレがあるし、NULLを返すのも微妙だし……。

一番の問題はコードが読みづらいってことかも……。コメントも一切入れてないですしねー。

クラスごとにファイルに分けた結果、cppが3ファイル、hが11ファイルというなかなかの規模になりました。中にはデリゲート一つ定義しているだけのもありますけど。ので、取りあえずzip圧縮して置いておくことにします。同梱のSusieWrapper.batがVS2002向けのコマンドラインですが、まあこれは参考程度にして適当にコンパイルとリンクしてください。

解説等は、また機会があれば。というかその時に今回のと同じのを使う保証が微妙ですが(笑)。

posted by Hongliang at 21:25| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年10月14日

10月7日分のフォロー

該当スレッドを見れば一目瞭然ですが、一応こちらでもフォロー。

10/07の、他プロセスのメモリへのちょっかい@タブコントロールで、サンプルとして(コメントアウトながら)タスクバーのやり方も書いていました。

その後の調査で、どうやらタスクバーのタブボタンの文字列はどうやらTCIF_TEXTでは取得できないようだと言うことが分かりました。かわりにTCIF_PARAMでウィンドウハンドルを取得してEM_GETTEXTで代用するという手法に落ち着きましたが。

何故こんな仕様になってるのかは謎です。或いは何か見落としでもあるんでしょうか……。

それから更に書き忘れていましたが、VirtualAllocExはWinNT系限定の関数です。ですから9x系では他の手段を考えなければなりません。幸いと言うべきか、9x系ではメモリ空間の管理はもうちょっとルーズになっています。ページングファイルに対してメモリマップトファイルを作成し、それのビューを作成すると、そのビューはあらゆるプロセスが全く自らのメモリ空間であるかのように扱うことができるようになるのです。

なんだ、そんな手段があるのならNT系でも……というわけにはいきません。メモリマップトファイルを使用することで各プロセスから共有できるメモリ領域を確保することはできます。しかし、NT系ではメモリ空間の管理が厳しいため、どのプロセスからでもアクセスできる一意のアドレスなんてものは認めません。必ずそのプロセス独自のビューを使用する必要があります。これでは同じアドレスが同じものを指すと言う状態にはできません。両者が「この名前の共有メモリのこれだけの領域を使うよ」という合意をした上でようやく使用できるものと考えるべきでしょう。

メモリマップトファイルは作成時に名前を付けることができます。で、他のプロセスはその名前でもって作成済みのメモリマップトファイルにアクセスすることが可能です。Mutexなんかと同じです(使用する名前はMutexなどと同じ機構を使用するため、Mutexとメモリマップトファイルの間でも名前がバッティングしたら後から作ろうとした方が失敗します)。

簡単に手順だけ書くと、CreateFileMapping関数を、ファイルハンドルにINVALID_HANDLE_VALUEを指定して呼び出すことでページングファイルに対してファイルマッピングオブジェクトを作成します。要は全てのプロセスから参照可能なメモリ領域を確保するって事ですね。で、これだけではアドレスがまだ不明のままです。ここで確保した領域のアドレスを取得するのがMapViewOfFile関数です。つまり、どこか適当に確保した領域を、ローカルで扱えるように仮想アドレス空間にマッピングするってわけですね。前述の通り、9x系ではこれでマッピングされたアドレスはグローバルに扱うことが可能な特殊なアドレスになります。グローバルに扱えますから、マッピングしたアドレス(ビュー)は他のプロセスへのSendMessageなんかにもそのまま使用できるというわけです。使い終わったらMapViewOfFileしたアドレスはUnmapViewOfFile、CreateFileMappigしたファイルマッピングオブジェクトはCloseHandleするのを忘れないように。

posted by Hongliang at 00:35| Comment(3) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年10月10日

TextBoxと未実装メッセージ(3)

今回は久し振りにTextBoxのメッセージ実装をやりましょう。ターゲットは、EM_SETRECTです。ところでEM_SETMARGINSとEM_SETRECTの使い分けが微妙に良く分かりません。単一行エディットコントロールにはEM_SETMARGINSを使い、複数行にはEM_SETRECTを使うという方向で良いんですかね? 

EM_SETRECTは、フォーマット領域を設定するメッセージです。フォーマット領域ってのは、まあ分かりやすく言えば実際に文字を描画する領域です。つまりこのメッセージを使用することで、本来きちきちに表示されるテキストボックスが、上下左右にスペースを持たせて表示できるようになるって訳ですね。それだけなら見やすくなるなで済む話ですが、これはその後の自作エディタで役に立つテクニックです(私は作るつもりはありませんけど)。つまり、左にスペースを持たせてやれば、そこに行番号を表示させたりもできるようになるわけです。

さてそんな将来性を感じさせるEM_SETRECTですが、実装は割と簡単です。注意点として、

  • RECT構造体が要求されますが、これはSystem.Drawig.Rectangle構造体とは名前は似てますが別の構造です。ですから自前で定義してやらなければなりません。私はつい趣味に走ってRectangleと相互変換できるようにしたりします。
  • EM_SETRECTで設定したフォーマット領域は、テキストボックスのサイズが変更されるとクリアされてしまいます。そのためResizeイベントで再設定してやる必要があります。
  • EM_SETRECTはMultilineがfalseに設定されているTextBox/RichTextBoxには無効です。サンプルでは基底クラスのMultilineプロパティを隠蔽し、getアクセサのみにしています。ところでこれ、オーバーライドしてsetアクセサでNotSupportedException投げるのとどっちがより優れた手段なんでしょう? インターフェイスのメンバのように基底クラスのメンバの明示的実装/再実装とかできればいいんですけど。
  • EM_SETRECTはその名の通り描画領域を四角形で指定します。しかしこれはユーザにとって直感的ではないので、サンプルではPaddingという形でユーザに提供しています。つまり各辺それぞれの端からの距離ですね。内部でそれを四角形(Rect構造体)に変換しています。左辺と上辺はそのまま距離を座標に置き換えられますので、右辺と下辺だけ計算が必要です。クライアント領域の幅と高さからならそのまま差し引くだけで求められます。
  • サンプルではEM_GETRECTも一応実装していますが、機能させていません。必要が感じられないので。内部でフィールドに保持しておけばそれでいいやー、みたいな。
  • もしクライアント領域の幅か高さがEM_SETRECTで設定した値よりも小さくなってしまった場合、EM_SETRECTの設定は無効になり、クライアント領域全体に文字列が描画されるようになります。サンプルはこの場合を考慮していないので、実際に使うときは注意してください。
といった辺りを挙げておきます。

ところでちょうど「TextBoxでOwnerDraw VB.NET」なんてキーワードでいらっしゃった方がいました。今更なので多分見ていないでしょうが、TextBoxで自前描画をしたいって言うのは、恐らく一部だけ書き換えたいとか追加したいとかそう言う要求なんでしょう。一から書くならテキストボックス使わなきゃ良いんだし。で、TextBoxの場合Windowsに描画を任せているのでPaintイベントも発生しません。そう言うときはWndProcをオーバーライドして、WM_PAINTメッセージを処理します。一旦基底クラスのWndProcを呼んで一通り描画させた後、おもむろにCreateGraphicsでGraphicsオブジェクトを作って描画します。

XMLドキュメント、イベント部分を含む完全版コードはこちら。

さっさとサンプル
posted by Hongliang at 23:18| Comment(1) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年10月06日

ToolBarのあるボタン

DDEML関数のラッパを作っているはずが、何故か今Managed C++でSusieプラグインのラッパを作っています。本当に何故なんでしょうね。意外に構造が単純なので不慣れなC++でもどうとでもなるもんですね。しかしWin64時代に残るんだろうか、Susieプラグイン。

さて、TextBoxのも放置していますが、今回はToolBar。こいつの未実装メッセージを一つメソッドに仕立てましょう。ターゲットはTB_HITTESTです。

TB_HITTESTは、指定したクライアント座標に一番近いボタンのインデックスを返すメッセージ。単純なクリックならButtonClickイベントのハンドラ引数でToolBarButtonが取得できますから不要なんですが、D&D操作やダブルクリックの処理には対応できません。

TB_HITTESTは、SendMessageのlParamに確認するPoint構造体の参照を入れ、返値でボタンのインデックスを取得します。返値にはクセがあって、普通にボタン上ではボタンのインデックスが返るんですが、StyleがToolBarButtonStyle.Separatorになっているものはその右隣のボタンのインデックスを負数にしたものが返ります。例えば左から3つ目がセパレータの場合それをテストすると-3(右隣のボタンが3なので)になります。またボタンの範囲外だった場合、Yが0よりも小さければボタンの数の負数にしたもの(4つボタンがあるのなら-4。セパレータもボタン扱いです)が、0よりも大きければ更にその-1(4つボタンなら-5)と言うことになります。

実装する際には、ToolBarButtonClick.Buttonと同じようにToolBarButtonを返すのが自然でしょう。範囲外の場合はどうせ範囲外なのですからnullで問題ないかと思います。そうすると注意するのはセパレータの場合のみですね。

ところで、SendMessageは当然Win32APIなわけで、DllImport/Declareする必要があります。これがイヤな人も多いでしょう。今回や過去TextBoxで扱ったようなのは、自身にメッセージを送信しているわけですから、つまりは自身のWndProcで処理していると言うことになります。そしてWndProcはprotectedですから、(派生クラスを作りさえすれば)呼ぼうと思えば自由に呼べると言うことになります。難点は構造体の参照を渡す必要があるときに自前でマーシャリングしなければならないという点辺りでしょうか。そしてそれによって得られるものはDllImport/Declareを一応しなくなったと言うだけで、あまりメリットはなさそうです。セキュリティ的にも、確かWndProcを呼ぶのにUnmanagedCodeの実行許可が必要なはずですから利点にはなり得ないでしょう。

今回はメソッドが実質一つな簡単なクラスなので、XMLドキュメントは省略。

能書きは終わった? じゃ、コードを。
posted by Hongliang at 23:45| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月28日

Array In the Structure

今回は、某掲示板にあったネットワーク上のサーバの列挙とかを取り上げようかと思いましたが、その前にGotDotNetの掲示板で上がっていた構造体アンマネージ宣言の仕方と言うスレッドをネタにしましょう。.NET Passportとやらを取るのが面倒なので投稿できませんが(笑)。

さて、このスレッドに上がった問題点を端的に言うと、「アンマネージドDLLとの相互運用に使用する構造体のメンバに構造体の固定長配列をどうやって定義するのか?」ということです。

念のために確認しますが、C言語などにおける構造体内の固定長配列は、動的配列と違ってポインタが一つあるだけではありません。展開され直接ずらずらと並べられます。指定した要素数だけの同名メンバが並んでいると考えると分かりやすいでしょう。また、構造体の中の構造体も、ポインタでない限り直接バイトデータが並びます。DWORDのメンバを3つ持つ構造体Aを構造体Bのメンバにした場合、構造体Bは12バイトだけサイズが増えるわけです。

.NET 2.0ではマーシャラが強化され、この問題をプリミティブ型の配列と同様にUnmanagedType.ByValArray/SizeConstをMarshalAs属性に定義するだけで済むようになります。.NET 2.0では、ということはつまり.NET 1.0/1.1ではそれではできません。

ではどうするか。

取りあえず思いついたのは、構造体を並べるか、Byte配列でやりとりするか、です。

構造体を並べるというのは、上の確認で述べたように、要は要素数だけ構造体が、更に言ってしまえばバイトの列が並んでいるのですから、それと同じように構造体を並べてやれと言うことです。例えばC言語で

typedef struct {
    DWORD a;
    DWORD b;
} SUB;
typedef struct {
    SUB subs[3];
} PARENT;
となっている構造体PARENTを、C#で次のように定義し直します。
public sturct Sub {
    public int A; // この辺はprivateフィールドにしてプロパティを定義すべきですけど、
    public int B; // P/Invokeに使う場合はフィールドを直接publicにすることが多いです。
}
public struct Parent {
    public Sub Subs0;
    public Sub Subs1;
    public Sub Subs2;
    public Sub[] Subs {  // 使いやすく配列を返してみる
        get {return new Sub[]{Subs0, Subs1, Subs2};}
        set {Subs0 = value[0]; Subs1 = value[1]; Subs2 = value[2];}
    }
}
難点は、parent.Subs[0] = new Sub();なんてのが使えないことでしょうか。しかしこの手法はこれで直感的に分かりやすいですね。更に発展させて、Subs構造体を独自に定義してみるなんてのも有りです。
public struct Subs {
    private Sub subs0;
    private Sub subs1;
    private Sub subs2;
    public Sub this[int index] {
        get {
            switch (index) {
                case 0 : return subs0;
                case 1 : return subs1;
                case 2 : return subs2;
                default: throw new ArgumentOutOfRangeException("index");
            }
        }
        set {
            switch (index) {
                case 0 : subs0 = value; break;
                case 1 : subs1 = value; break;
                case 2 : subs2 = value; break;
                default: throw new ArgumentOutOfRangeException("index");
            }
        }
    }
}
public struct Parent {
    public Subs Subs;
}
インデクサ(VB.NETではデフォルトプロパティ)がキーポイントです。これならparent.Subs[0] = new Sub();も問題ありません。それなりにsubsXXの数が増えた場合、一々switchするのは面倒ですからリフレクションを取り入れたりするのも面白いかも知れません。SubsにSub3つを引数に取るコンストラクタを定義するとか。

Byte配列でやりとりするのは余りお薦めできません。配列のインスタンスを呼出側が自分で作成しなければならずバグを作りやすい、Marshalクラスのメソッドにずっぽり浸かるかC#のunsafeコンテキストを使用してポインタごしにアクセスするかしないと構造体との相互変換ができない、などデメリットが大きいためです。とはいえ、要素が非プリミティブな構造体数十に及ぶようなものなら、前述のやり方は難しいですからこちらを使わざるを得ないかも知れません。まあサンプルを書いてみましょう。一応(大した慰めにもなりませんが)unsafeコンテキストは使わずに書いてみます。

public struct Sub {
    public int A;
    public int B;
    public Sub(int a, int b) {A = a; B = b;}
}
public struct Parent {
    //配列をポインタではなくバイト列として持つための属性
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=(8*3))]
    public byte[] Subs;
    public void Initialize() {
        Subs = new byte[24];
    }
    public void SetSubs(Sub[] subs) {
        if (subs == null)
            throw new ArgumentNullException("subs");
        if (subs.Length != 4)
            throw new ArgumentOutOfRangeException("subs");
        GCHandle handle = GCHandle.Alloc(this.Subs, GCHandleType.Pinned);
        try {
            int size = Marshal.SizeOf(typeof(Sub));
            IntPtr addr = handle.AddrOfPinnedObject();
            for (int i = 0; i < 4; i++) {
                IntPtr ptr = new IntPtr(addr.ToInt64() + (i * size));
                Marshal.StructureToPtr(new Sub(i, i * 2), ptr, true);
            }
        }
        finally {
            handle.Free();
        }
    }
    public Sub[] GetSubs() {
        if (Subs == null || Subs.Length != 24)
            return null;
        Sub[] subs = new Sub[4];
        GCHandle handle = GCHandle.Alloc(this.Subs, GCHandleType.Pinned);
        try {
            int size = Marshal.SizeOf(typeof(Sub));
            IntPtr addr = handle.AddrOfPinnedObject();
            for (int i = 0; i < 4; i++) {
                IntPtr ptr = new IntPtr(addr.ToInt64() + (i * size));
                subs[i] = (Sub)Marshal.PtrToStructure(ptr, typeof(Sub));
            }
            return subs;
        }
        finally {
            handle.Free();
        }
    }
}
まあこんな感じです。書いておいて何ですが、このままでは使い勝手は相当悪いでしょう。適当に改造してみてください。

つまりは「私は使わない」宣言です(笑)。

さて、取りあえず今回の記事はここまで。ちょこちょこいじってるうちに何故かP/Invokeの知識ばかり増えて鬱なこの頃。WinFXがくればこの知識も無意味なものになるんだろうなぁと思うと鬱も倍増です。

VB.NETで書けよ
posted by Hongliang at 04:52| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月23日

WTSEnumerateSessionsとP/Invoke

さて今日は、過去にInsider .NETに投稿した奴をきっちりとした形で書き直します。いつかはやらなきゃと思っていただけであって手抜きじゃないですよ?

発端はVB.netでのCopyMemoryを利用するにはと言うスレッドで、GUI登録されたタスクの編集スレッドでもコードが流用されています。リンク先をご覧になって頂ければ分かりますが、要はいかにP/Invokeをコーディングするか、です。

メインターゲットはWin32APIのWTSEnumerateSessions関数。指定したターミナルサーバー上のセッションのリストを取得するという関数です。この関数の特徴は、関数が自動的に独自にメモリを確保して、そのメモリに結果の配列を並べていくという点です。普通に配列(ポインタ)を返してくるだけなら、OutAttributeをつけた配列(の参照)を渡せば済む話なのですが、関数が独自にメモリを確保する以上、プログラマは明示的に関数コールでメモリを解放してやらなければなりません。これでは前述のようなお手軽手段は使えません。ポインタはポインタとして受け取って、なんとか自前で構造体配列に変換してやる必要があります。

こういうときにお世話になるのがSystem.Runtime.InteropServices名前空間のMarshalクラスです。まああんまりお世話になりたくないクラスでもありますがね。

基本的な考え方はこうです。まずC言語で考えます。MSDNではこの部分のパラメータは

PWTS_SESSION_INFO *ppProcessInfo
となっていますから、C言語から呼ぶ場合は
PWTS_SESSION_INFO infos;
WTSEnumerateSessions(...., &infos, ...);
という呼び方となります。そして実際に返ってきたのに対して、
for (i = 0; i < count; i++) {
    printf("%d", (*infos).SessionId)
    infos++;
}
といった処理を行うでしょう。

いや、普通は素直にinfos[i].SessionIdと書く(infosは動かさない)か、あるいはinfos->SessionId(わざわざ構造体にしないで直接参照する)でしょう。ここでは.NETへの翻訳に楽なように書いてます。

翻って.NETでは、まずWTSEnumerateSessionsを呼ぶと、&infosに当たるポインタは配列先頭の構造体を指します。まず一つ目、Marshal.PtrToStructureでWTSSessionInfo構造体を取得します。さて次はどうするか。C言語ではinfosをインクリメントしています。C言語において、ある型のポインタをインクリメントすると、その型が占ているバイトデータ分ポインタをずらします。例えばint(32bit)なら4バイトずらしますし、charなら1バイトだけずらします。そして構造体の場合、その構造体の各メンバが占める合計だけずらします。WTS_SESSION_INFO構造体なら、DWORD、LPSTR、列挙型の3つのメンバですから(32bitPCなら)12バイトという事になります。つまり、初め指しているところから12バイトずらせば次のWTS_SESSION_INFO構造体になるわけです。あとはその繰り返しですね。

今回の完全なC#のコードです。

P/Invokeの構文は非常に柔軟に表現できます。私はある程度慣れましたが、その結果私なりの表現に固まってしまったため、もっと便利な使い方に気づけない可能性もあります。そう言う意味でPINVOKE.NETなんかは時々見ると意外な発見があったりしますね。

巧く使用すれば.NETでは扱えないことがさらりと扱えるP/Invoke。CLIからすれば美しくはありませんが、巧く付き合っていきたいものです。

ところで一回使ったDllImportはXMLドキュメントをつけて残しているのですが、なんか250kBほどになってるんですがどうしたら。

コードだ、コードを出せ
posted by Hongliang at 23:37| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月21日

TextBoxと未実装メッセージ(2)

さて、今回から各メッセージの実装に入りましょう。

まずは小手調べと言うことで、.NET 2.0で実装される各種メッセージをメソッドにしていきたいと思います。対象は、EM_CHARFROMPOS、EM_LINEFROMCHAR、EM_LINEINDEX、EM_POSFROMCHAR、の4つです。この内、EM_LINEINDEXを除いて.NET 1.0の時点でRichTextBoxには既に実装されていました。MicrosoftはそんなにTextBoxがどうでもいいんでしょうか。.NET 2.0ではTextBoxBaseに用意されていることから考えて別に技術的には問題なさそうなのに。

これらのメッセージは、単純に32ビット値を送って返値を受け取るだけなので、実装も簡単なものです。

ちなみに、私はWin32APIをDllImportする際、使用する構造体名やそのメンバ名を徹底的に.NET風の命名規則に従って置き換えるので、PINVOKE.NETなどにはあまり協力できませんw また積極的に定数を列挙体などに置き換えたりもします。

できる限りメッセージの挙動をそのまま再現する方向でコーディングしたため、.NET 2.0で用意されているメソッドとは若干動作が異なるところがあります。

GetFirstCharIndexFromLine
負数を指定した場合、.NET 2.0では例外が発生するが、こちらは-1を返す。また特に-1の場合にGetFirstCharIndexOfCurrentLineと同等の結果を返す。
GetPositionFromCharIndex
取得した座標がクライアント領域外になった場合もサポート。
GetCharIndexFromPosition
XYいずれかにクライアント領域から外れた座標を指定した場合、.NET 2.0ではTextLengthの値を返すが、こちらは-1を返す。

今回の完全なC#のコードはこちらです。色々メソッドにおける注意点などもXMLドキュメント内に書かれているので、VB.NETの人も目を通した方が良いと思います。

所で実のところ私はあまりVB.NETというかVBには詳しくないのですが、Int32での&HFFFF=65536をInt16としての&HFFFF=-1として変換したりできるような機構はないんでしょうか? 下のコードではえらく遠回りな事をやっちゃってますが。

ご託はいい、コードを見せろ
posted by Hongliang at 22:57| Comment(1) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月20日

ListBoxとTextAlign

今日はListBoxです。

TextBoxなどにあるTextAlignプロパティが、ListBoxには存在しません。これはいささか不便です。そうでもないかも知れませんが。……いや私自身ListViewばかり使ってListBoxはほとんど使わないので重要度は低かったり。

まあそんなわけで今回はListBoxにTextAlignプロパティを実装させてみましょう。ただ右詰するだけなら実のところ多少のイベントを処理するだけで済みますが、私の趣味と再利用のしやすさから派生コントロールを作成します。名付けてAlignableListBox。

さてではどうやって実装しましょう。適当に考えて、文字を自前で描画できればそれで済みそうです。それで済みそう、なんてあっさり片づけられるかどうかは謎ですが。

それではどうやって自前描画しましょうか。究極的にはWndProcでWM_PAINTメッセージを受け取ってBeginPaint関数などを使って描画する手法ですが、泥臭い面倒くさいでやってられません。できればコントロール全体を描画するのではなく、アイテムごとに描画していきたいものです。こういうときまず考えるのが、オーナードローというキーワードです。言葉通り、Windowsに描画させるのではなく、所有者=プラグラム側が自ら描くってわけですね。

オーナードローには全てのコントロールが対応しているわけではありません。主にアイテムを大量に持ち、それを並べて表示するようなコントロールにのみ実装されています。丁度ListBoxはそんな感じなので対応に期待が持てます。オーナードローするように設定するには、コントロールによってOwnerDrawプロパティかDrawModeプロパティのいずれかを該当する値にセットするだけです。ListBoxのプロパティ一覧を見れば一目瞭然、DrawModeプロパティが燦然と存在を輝かせています。オーナードローはできるようですね。第一関門突破。

bool値のOwnerDrawプロパティと違って、ListBox.DrawModeはDrawMode列挙値です。メンバにはNormal、OwnerDrawFixed、OwnerDrawVariableが存在します。Normalはオーナードローせず、Windowsに描画を任せます。OwnerDrawFixedは全ての要素を同じサイズで描画します。OwnerDrawVariableは要素ごとに異なるサイズで描画します。これを使えば、例えばそれぞれの要素ごとにフォントを変更し、そのフォントにマッチする高さで描画したりもできるようになるわけです。今回の目的は文字列の右寄せや中央揃えですから、OwnerDrawFixed限定にしておきます。

さて、DrawModeを変更するとどうなるのか。ListBoxは、OwnerDrawFixedが設定されている場合、それぞれのアイテムを描画するのにprotectedなOnDrawItemメソッドを呼び出します。プログラム側はこのOnDrawItemをオーバーライドして実装することで、それぞれのアイテムを描画することになります。

ところで、継承コントロールの場合はOnDrawItemをオーバーライドすれば良いんですが、継承させずに直接いじる場合は当然ながらOnDrawItemメソッドをオーバーライドできません。このときは、OnDrawItemがDrawItemイベントを発生させるので、このDrawItemイベントにアイテム描画の実装を書くことでOnDrawItemのオーバーライドと同等の結果になります。

ちなみにOwnerDrawVariableの場合、OnDrawItemメソッド/DrawItemイベントの前にOnMeasureItemメソッド/MeasureItemイベントが走ります。これらは各アイテムのサイズを設定するために呼ばれます。しかしListBoxではListViewと違ってそれぞれのアイテムごとに状態を持つわけではないため、こういうのの処理を書くのに微妙に向いてない気がします。

実際のそれぞれのアイテムの描画は、コード量こそ多少ありますが単純なもので、大雑把に言えば背景を塗りつぶして文字を書くだけです。初めフォーカスが当たってるものの枠の描画を忘れていて、妙にのっぺりしてたりしました。Windowsの描画を完全にエミュレートするのはなかなか難しいですね。

ここまで書いてふと思ったんですが、グダグダ書くよりも@IT:.NET TIPS ListBoxコントロールのオーナー描画にリンクを張った方が早かったんではないんでしょうか。私の文章はアルファベット多くて読みづらいし。

それでは実際のコードを書いていきましょう。今回の記事の完全なコードはAlignableListBox.csにあります。完全版コードでは、プロパティ変更イベントも定義してあります。

一つにまとめた方が一覧性に優れてるかと思って続きに両方のコードを書いてみました。スクロールが疲れますね。おかげで記事カテゴリを.NETにせざるを得ないので、C#/VB.NETのカテゴリがさびしそうです。

コードを見せろ
posted by Hongliang at 21:14| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月19日

Componentとイベント

コントロールのイベントを定義する際、

public event EventHandler Something;
protected virtual void OnSomething(EventArgs e) {
    if (Something != null)
        Something(this, e);
}
と書いてました。ごく自然ですね。

しかし最近リフレクションを使ってコントロールのイベントを漁ってた所、どうもこういう単純な構成ではなく、Component.Eventsプロパティを使っているらしいことが分かってきました。そういえばあったねこんなの、程度の認識でしたが、改めてイベントの使用方法のガイドラインに目を通してみると、これを使った例が挙げられていました。イベントが多すぎて一々フィールド(イベントの実体はデリゲートインスタンスフィールドです)を用意するのが大変な場合に使用するイベントプロパティという構成だそうで。

それに従った場合、上のコードはこう書き直されます。

private static readonly object EventSomething = new object();
public event EventHandler Something {
    add { base.Events.AddHandler(EventSomething, value); }
    remove {base.Events.RemoveHandler(EventSomething, value); }
}
protected vitual void OnSomething(EventArgs e) {
    EventHandler handler = base.Events[EventSomething] as EventHandler;
    if (handler != null)
        handler(this, e);
}

率直に言って今一つ利点が見えないのですが。まあイベント一覧を取得するのには便利なのかな……? こうやった場合、そのクラス内でSomethingをデリゲートのように扱うこともできなくなるので、必ずOnSomethingを通して呼び出す必要がありますし。

そこで問題になるのがVB.NET。VB.NETではadd/removeアクセサを書くことができません。VB2005で追加されましたが。まあVB.NETではこれまで通りで、VB2005用にはC#と同等の記述をすると言うことで。ちなみにVB2005で上のコードを書くとこうなります。AddHandler/RemoveHandlerアクセサの他にRaiseEventアクセサを記述する必要があるのが特徴ですね。代わりにイベントを起こす側はRaiseEventを書くだけで済むので楽と言えば楽です。

Private Shared ReadOnly EventSomething As Object = New Object()
Public Custom Event Something As EventHandler
    AddHandler(ByVal Value As EventHandler)
        MyBase.Events.AddHandler(EventSomething, Value)
    End AddHandler
    RemoveHandler(ByVal Value As EventHandler)
        MyBase.Events.RemoveHandler(EventSomething, Value)
    End RemoveHandler
    RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
        Dim handler As EventHandler = _
            TryCast(MyBase.Events(EventSomething), EventHandler)
        If Not(handler Is Nothing) Then handler(Me, e)
    End RaiseEvent
End Event
Protected Overridable Sub OnSomething(ByVal e As EventArgs)
    RaiseEvent Something(Me, e)
End Sub

さてここで、C#とVB2005のコードで決定的な差が出ています。すなわち、実行するハンドラを呼出側が決定するかイベント側が決定するかです。

C#では呼出側、つまりOnSomethingがどのイベントハンドラを呼び出すかを決定します。OnSomethingはvirtualですから、これがオーバーライドされてかつbase.OnSomethingが呼ばれないと、他のメソッドからOnSomethingが呼ばれてもSomethingイベントハンドラは永遠に呼ばれないと言うことになります。

VB2005では、イベント側がイベントハンドラを規定します。呼び出す側はRaiseEventを使ってイベントを発生させます。そうすると、他のメソッドからはOnSomethingを通さず直接RaiseEventを使用するでしょう。ですからOnSomethingをオーバーライドされても他のメソッドには何の影響も出ないことになります。

どちらが優れているかは一概には言えません。また、C#にせよVB2005にせよ、お互いの基本コードを実装できます。

C#なら、privateなraise_Somethingメソッドを書き、ここに(上で書いた)OnSomethingの実装を移し、OnSomethingからはraise_Somethingメソッドを呼ぶだけにして、他のメソッドもOnSomethingではなくraise_Somethingを呼ぶようにすればOnSomethingのオーバーライドは何の影響もなくなります。

逆にVB2005でも、OnSomethingメソッドにRaiseEventステートメントではなくCustom Event内で書いたRaiseEventブロック部分を実装させ、他メソッドからもRaiseEventではなくOnSomethingを呼ぶようにすれば、OnSomethingのオーバーライドによってSomethingイベントの発生をさせなくさせることが可能になります。

ちなみに私はComponent派生クラスのイベントはadd/removeアクセサを使って書くようにしています。というかしました。

posted by Hongliang at 16:20| Comment(4) | TrackBack(1) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月18日

TextBoxと未実装メッセージ(1)

TextBoxはScrollBarsプロパティを操作することでスクロールバーを表示することができます。

んが、表示はできても任意の所に動かしたり今どこを表示しているのか取得したりというメソッドがほとんど用意されていません。唯一ScrollToCaretメソッドがあるくらいです。

これは不便だがどうにかならないかと考えるに、そもそもTextBoxはWindowsのEditコントロールの薄いラッパークラス。メッセージの類はそのまま流用できたりします。そこでEditコントロールのメッセージを色々調べてみましょう。

あ、今更ですがちょっと解説。こういうWindows標準コントロールは普通、そのコントロールにSendMessage関数などを使用して特定のメッセージを送ることで色々な値を取得したり設定したりします。ほんとに今更ですね。

さてMSDNのEditコントロールです。メッセージが色々並んでますねー。しかしMicrosoftはこの辺(メッセージや構造体)和訳してくれないので解読も一苦労とは言いませんが一手間増えてやな感じです。

基礎のお勉強として、この未実装のメッセージをメソッドやプロパティとして実装していこうかと思います。

第一回目の今回は、まずそれぞれのメッセージを一つ一つ見ていき、どれが実装されていないのか、されていないので使えそうなのはあるのか、検証していくことにしましょう。

続きを読む
posted by Hongliang at 14:25| Comment(0) | TrackBack(1) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月16日

ListViewの限界

とある掲示板にあがった質問より。
ListViewの一つのカラムに表示できる文字数が260字に制限されているとのこと。
へーと思って早速実験。早速ばっちり。
ていうかこれだけ表示させたらListViewが重くてやってられませんが。

てことで早速ちょちょいとぐぐってみました。ListViewじゃなくてリストビューなのは、恐らく.NETとは無関係にWindowsの制限だろうと踏んだからです。
リストビュー 文字列の長さ 260 - Google 検索
で、そこで見つけた掲示板のやりとりに英語版MSDNのLVITEM Structureへのリンクがあったので飛んでみたら、

Note that although the list-view control allows any length string to be stored as item text, only the first 260 characters are displayed.

拙訳:ListViewコントロールのアイテムのテキストには任意の長さの文字列を入れられるけれど、始めの260字までしか表示しないわよん。


とちゃんと書かれていました。

ここまでは「ふーんそうなんだー」で済む話なんですが。
SDK者として、VS2002に付いていたドキュメントエクスプローラを愛用していて、当然まずそれで調べたんですよ。Googleの前に。
LVM_SETITEMTEXTからLVITEM構造体の解説まで行ったんですよ。

丁度Noteの所だけ抜けてやがる……orz
posted by Hongliang at 22:00| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

2005年09月15日

テスト代わりの注意書き

私、VSを使ってません。
VS2002は持ってますが基本的に動作確認というか掲示板の質問の答え探し用。
.NET 2.0に至ってはVSのダウンロードすらしてません。
ひたすらSDKとエディタでがりがり。
エディタはxyzzyと言うものを使ってまして、デフォルトで付いてるC#-modeにちょこちょこ手を加えてます。今では(というか割と初期の頃からですが)F5を押すだけでコンパイル&実行してくれます。
ので、ここに書かれるコードには多少クセがあるでしょう。
特に目立つのが、バージョンを表すプリプロセス用のシンボルです。
私的C#-modeではソースファイルの一行目にコマンドラインオプションを書く仕様にしてるんですが、//v:という独自オプションを用意してコンパイラバージョンを切り替えることができるようにしています。このとき自動的にV10/V11/V20とコンパイラに合わせてシンボルをつけます。
そのため、コード内には
#if !V10 && !V11    //.NET 1.0/1.1でなければこっち
....
#else //.NET 1.0/1.1ならこっち
....
#endif

なんてのが散見されます。割と便利ですよ?
posted by Hongliang at 12:21| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。