2006年03月27日

意義は知らないけれど

某スレ主氏に捧ぐ(でもとても奨められたもんではないので見せられない)コード。

Module FormManager
  Private forms As New Dictionary(Of Type, Form)
  Public Function GetForm(Of TForm As Form)( _
           ParamArray args As Object()) As TForm
    Dim t As Type = GetType(TForm)
    If (Not(forms.ContainsKey(t)) _
        OrElse forms(t).IsDisposed) Then
      Dim form As TForm _
          = DirectCast(Activator.CreateInstance(t, args), _
                       TForm)
      forms(t) = form
      Return form
    End If
    Return DirectCast(forms(t), TForm)
  End Function
End Module
posted by Hongliang at 19:50| Comment(0) | TrackBack(0) | VB.NET | このブログの読者になる | 更新情報をチェックする

2006年03月13日

CInt とオーバーフロー

今回はちょっとシリーズを離れてオーバーフローのお話です。なんか似た話題を以前にやった気もしますが。

アンマネージドとやりとりしていると、異なる数値型間の変換が必要になる場面が出てきます。今回 Task クラスの実装を書いている場面でも、それが必要になる場面がありました。

アンマネージドとは DWORD つまり UInt32 でやりとりしますが、そのままでは意味を取りづらいので TimeSpan 型としてプロパティを定義し、内部で Int32 に変換するというやり方です。DWORD を あえて Int32 で書くのはまあ CLS 準拠を目指すための癖みたいなもんです。今考えるとこれが悩みの元になったわけなんですが。

そのアンマネージドのメソッドは 0 から 0xFFFFFFFFL まで受け入れるため、TimeSpan.TotalMilliseconds を 一旦 Int64 にキャストし、最大値チェックを行った後 Int32 にキャストし直そうという算段でした。

unchecked を使える C# はともかく、VB.NET での Int64 から Int32 へのキャストは CInt か Convert.ToInt32 になりますが、この二つはいずれもオーバーフローを抑止するオプションが存在しないため、変換元の値を確認して &H80000000L 以上だったら &H100000000L を引く、そして変換ってのがわかりやすい手段です。

この過程で、何を思ったか Int64 に And &HFFFFFFFFL の演算をしてやればどうだろう? という思考にたどり着きました。確かに 32bit にはなりますがだからって CInt したときオーバーフローするのには変わらないのに。

ところがどっこい、これが何故かエラーが出なかったのです。もとの Int64 に &HFFFFFFFEL が入っていた場合、CInt した結果普通に -2 になります。気持ち悪。ちなみに C# でも checked ステートメント内で同じ結果になります。

IL レベルでも問題なく conv.ovf.i4 が発行されているため、さっぱり原因がつかめません。いい加減煮詰まったので 2ch で聞いてみたら、いくつかやりとりがあった末に こういう結論に。つまり定数が 32bit で収まること、また And 演算のため計算結果も必ず 32 bit 以下になることから、定数の下位 32bit で演算するように最適化する。そのとき正しくは通るべきオーバーフローチェックがパスされてしまう、と。また Mono では普通にオーバーフローするらしいです。つきあってくださった方々、改めてありがとうございました。

And &HFFFFFFFFL 後の CInt が決してオーバーフローしないのなら利用価値はありますが、x64 版でどうなるか知れたものじゃないし Mono でも普通にオーバーフローするとなると使ってもバグの元になるだけですので封印封印。

まあそう言う顛末の話でした。

しかし VB.NET にもオーバーフローの制御とか三項演算子とか欲しいんですが。IIf は Object でのやりとりになるから IIf(Of T) があればいいんだけどなー。

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

2006年03月05日

アンマネージドメモリへの手抜き VB.NET編

もっと早く更新するつもりがいつのまにやら。

では前回のコードの VB.NET 編です。キーワードの色つけはやってません(甚だしい手抜き)。

それと同時に、XML コメント付きの C# ソースへのリンクも。但しこちらは名前空間付いてますのでちょこっとだけ注意。

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

2005年11月09日

共有メモリクラス for VB.NET

リファラを見ているとですね、大体今まで書いた中でどんな記事に人気があるのか見えてくるんですね。で、最近多いなというのがVB.NETと共有メモリの組み合わせ。……折角来て頂いたのにVB.NETのコード書いて無くてすいません。

いや、ツールを使えばいいのかも知れませんが、詳しくないので移植がめんどくさいんですよ。で、Lispでコンバータでもちょちょいと作ろうかと……と、そちらの方にずっぽりはまってしまっていけません。本末転倒というか。私は元々完成品よりも部品を作る方が好きであるというのは自覚もしているのですが。

まあいつまでもそんなこと言ってお客さんを失望させ続けるのも何ですので、今回VB.NETに移植しました。VSがこういうときは大活躍です。

ところでVSに一旦コピーするに当たって大量に出てきたのが、Overloadsキーワードをつけろって警告。C#畑の者として、このキーワードの必然性が良く分からないんですが、一体何で必要なんですかね? オーバーロードは引数リストを見れば一目瞭然だし(Partialクラスならともかく)、派生クラスにしてもOverridesキーワードを付けてるんだからわざわざOverloadsをつけるのも……。Shadowsするんならともかく。

そもそもVB.NETってキーワードが多いんですよ、やたらと。おかげで命名に苦労するところがある。列挙体のメンバにReadOnlyなんて名前付けるのは良くあることだと思うんですが、これすら[と]でくくらないと使えない。C#のgetキーワードのように、その場面以外では普通に使えるとか……もややこしさの一因になるかな、VB.NETの場合。

まあそんなわけです。どんなわけか知りませんが。

クラスの解説は05/10/22の記事を参照してください。何故かコードはないくせにVB.NETの解説が含まれています。

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

2005年10月07日

他プロセスのメモリへのちょっかい@タブコントロール

さて今回は毎度おなじみ@ITの会議室より、タスクバーのタスクボタンの情報を取得したいスレッドをネタに話を進めましょう。

私も以前タスクバーから特定アプリのタスクボタンを隠したいとか思って色々調べてみたことがあります。で、まず初めに知ったのがWindows XPからはタスクバーで使用されるコモンコントロールが変更され、もともとTabControlが使われていたのが代わりにToolBarを使われるようになったこと。この変更の波及は大きく、2000までだったらTabControlにTCM_GETITEMをTCIF_PARAMつきで投げてやれば、そのタブが扱うアプリのウィンドウハンドルが手に入ったんですが、XPではToolBarにTB_BUTTONINFOをTBIF_LPARAMつきで投げても良く分からない値しか取得できなくなったんです。おかげでウィンドウタイトルからFindWindowするハメになると言う……。

ま、そんな益体もない話はおいておいて、今回の話に入りましょう。

今まで、SendMessage関数をごく何気なく使っていました。構造体でもref(ByRef)やMarshal.StructureToPtrで普通に参照(アドレス)を渡していました。自分のプロセスのことならメモリアドレスは一意ですから何も問題はなかったんですね。しかし、他のプロセスということになると話は変わってきます。仮想アドレス空間はプロセスごとに用意され、これにプログラムはそれぞれ関数や変数を割り当てて、そのアドレスを使用します。Marshal.AllocCoTaskMemなどで返される値もこの仮想アドレス空間におけるアドレスです。

仮想アドレス空間はプロセスごとと言いました。つまりプロセスが異なれば、同じアドレスを違うものが指していることになるわけです。もちろんそんなの認めるわけにはいきませんから(あっさり暴走しちゃいます)、OSはあるプロセスが扱う仮想アドレス空間には他のプロセスからはアクセスできないようにしています。

ですから、例えば他のプロセスのあるタブコントロールにTCM_GETITEM送信したい場合、単純にTCITEMへの参照を渡すわけにはいきません。こちらのプロセスで作成した構造体の参照(アドレス)は、当然こちらのプロセスの仮想アドレス空間上のアドレスを指すわけで、それをSendMessageで送信してもあちらのプロセスはそのアドレスを処理できません。なんとかあちらのプロセスの仮想アドレス空間を扱う必要があります。

そこで登場するのが、今回のVirtualAllocEx、WriteProcessMemory、ReadProcessMemoryの各Win32API関数です。VirtualAllocExは、指定したプロセスの仮想アドレス空間にメモリ領域を確保する関数。Write/ReadProcessMemoryがその確保した領域とこちらのプロセスの間でデータをコピーする関数です。

大雑把な流れはこうです。

  1. 対象のプロセスのハンドルを、OpenProcess関数などで取得。
  2. VirtualAllocExで、対象のプロセスの仮想アドレス空間に構造体のメモリ領域を確保。
    • TCM_GETITEMでTCIF_TEXTを使用する場合、その文字列分も確保する必要がある。
  3. WriteProcessMemoryで、VirtualAllocExで確保した領域に必要な構造体をコピー。
  4. SendMessageで目的のコントロールに送信。
    • 勿論この際構造体アドレスに使用するのはVirtualAllocExで確保したアドレス。
  5. ReadProcessMemoryで結果をこちらのプロセスのメモリ空間にコピー。

これだけ分かれば後は細々としたプログラミング技術の話になるでしょう。

  • TCM_GETITEMでTCIF_TEXTを取得する場合、自プロセスの話であれば、適当にmallocしたアドレスをTCITEMのpszTextに割り当て、それをSendMessageしてやればそのアドレスにタブの文字が入っていると言うことになります。別プロセスと言うことになると、やはり当然ながらpszTextに指定するのもその別プロセスの仮想アドレス空間のアドレスと言うことになりますから、VirtualAllocExが必要になります。二回も同じ関数を呼ぶのは馬鹿馬鹿しいですから、一回でまとめて構造体と文字列バッファを確保し、前半をTCITEM用、後半を文字列バッファ用として扱うのが便利です。このとき、文字列バッファの先頭アドレスは、VirtualAllocExで確保した全体のアドレスからTCITEM構造体のサイズ分進んだところと言うことになります。
  • WriteProcessMemoryの第三引数は書き込むデータの先頭のポインタですが、これにByRef As TCITEM構造体を指定してやれば自動的にマーシャリングしてくれるので、TCITEMをそのまま渡すだけでOKになり、構造体ポインタを考える必要が無くなります。
  • ReadProcessMemoryで受け取るには、下記のサンプルではIntPtr(=ポインタ)で受け取るようにし、こちら側のメモリ領域をMarshal.AllocCoTaskMemで確保しています。もう一つの手段として、IntPtrの代わりにByte配列を使用する方法があります。この場合、配列要素をアンマネージド関数から変更可能であることを指定するためにOut属性をbufferパラメータに指定する必要があります。
    <Out> ByVal buffer As Byte()
    後は
    Dim textBuffer As Byte() = New Byte(textSize){}
    とバッファを確保してReadProcessMemoryを呼ぶだけです。この場合Byte配列から文字列に変更するのにはEncodingクラス(Encoding.DefaultがANSIコードページのEncodingインスタンスを指します)を普通使用するでしょうが、そのままGetStringするだけでは恐らく後ろに大量のNULL文字が付加されるでしょう(Marshal.PtrToStringAnsiを使用した場合はNULL文字を発見次第変換を終了します)。TrimEndを使用するなどして巧く取り除いてください。
  • System.Diagnostics名前空間のProcessクラスを使用すれば、メインウィンドウのウィンドウハンドルやプロセスIDを取得できます。が、巧く使うのは多少難しいかも知れません。またHandleプロパティは十分なアクセス許可がされているのかどうか不明です(PROCESS_VM_OPERATION、PROCESS_VM_READ、PROCESS_VM_WRITEの3つが必要です。一応Process.Handleの解説を読む限りフルアクセスの権限で取得しているっぽいのですが)。

今回は元スレッドに乗っかってVB.NETオンリーの記事です。尤も下調べはC#でしたのですが。

ところでこれ、元スレッドのそのまま答えというかそのままコードなんですが、こういうのいつもポストバックするかどうか悩むんですよねー。そのままコードは余り質問の答えにはしたくないので。とは言えまあこんな記事書いてる時点で結局書いたのを見せたいという願望があるのは事実ですが。

追記(2005/10/13)
フォロー記事を書きましたので、そちらも参照してください。
ではコードを。
posted by Hongliang at 23:11| Comment(36) | TrackBack(0) | VB.NET | このブログの読者になる | 更新情報をチェックする

2005年09月26日

キャストとオーバーフロー

今回はちょっとVB.NETの不満点の一つを。まあここでC#のコードを移植する以外直接書くことはないから大した問題ではないんですけど。

21日の記事だったと思いますが、そのときVB.NETにC#のコードを移植していて気付いたんですが、VB.NETはキャストする際Cなんとか関数(以下、型変換関数)を使用するんですよね(ところでこれはコンパイル時に解決されるから関数と言うよりはマクロかステートメントとでも呼ぶべきかも知れません)。IntegerにキャストするならCInt、2005から追加された符号無し系でもUIntegerにキャストするのにCUInt。

ま、それは構いません。(byte)4とやろうがCByte(4)とやろうが結果が同じなら。

問題は、結果が違いうると言うことです。具体的に言えば、型変換関数にはオーバーフローチェックが入っちゃうんですよね。しかもそれを無視して(型落ちを容認して)強制的にキャストする手段がなかなかない。

状況を限定すればまあ計算で求まるんですからそれでいいかもしれませんけど、ロジックを考える手間も(手間と言うほどではありませんが)考えれば、もうちょっと汎用的な手段がないものか。Convert.To関係のもオーバーフローチェックしちゃうんですよねぇ。オーバーフローチェックしないオーバーロードがあっても良いと思うんだけどなぁ。この部分はエンディアンには影響されないというか関係ないだろうし。

取りあえず思いついた手段をまあ9/21の記事で使ったんですが、論理和で必要桁だけマスクしてから、一旦ToString("X")を使って16進数の文字列にし、それをConvert.ToHogehoge(String, Integer)で必要な型に変換するというもの。途中で16進(または2進)を挟めば何故かオーバーフローチェックしなくなるんですよね。これで&H80008355なんてのがShortにもIntegerにもマイナス値に変換できるようになります。

がしかし、まあなんというか美しくない。なにかもっと良い手段はないもんですかね。どなたかVBに詳しい方いらっしゃいません?

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

2005年09月15日

DetailsListView・背景色/VB.NET版

前掲のDetailsListView・背景色のVB.NET版です。続きを読む
posted by Hongliang at 15:55| Comment(0) | TrackBack(0) | VB.NET | このブログの読者になる | 更新情報をチェックする

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

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

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

×

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