SendMessageでプロセス間通信をしたい
プログラムA(送信側)からプログラムB(受信側)にWM_COPYDATAを使い、
3秒(Timer1)に1回文字列を送りたい。
環境:Windows7 Pro , VB 2010 Express
下記プログラムAにおいて ret = SendMessage・・・ の所で
「PInvokeStackImbalance が検出されました。
Message: PInvoke 関数 'Test_WatchDog!Test_WatchDog.Form1::SendMessage' がスタックを不安定にしています。
PInvoke シグネチャがアンマネージ ターゲット シグネチャに一致していないことが原因として考えられます。
呼び出し規約、および PInvoke シグネチャのパラメーターがターゲットのアンマネージ シグネチャに
一致していることを確認してください。」
のエラーが発生する。
Public Class Form1
Private Const WM_COPYDATA As Integer = &H4A
Private WH_WatchDog As IntPtr
Public Structure COPYDATASTRUCT
Public dwData As Integer
Public cbData As Integer
Public lpData As String
End Structure
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
Private Declare Ansi Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As IntPtr, _
ByVal msg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As COPYDATASTRUCT) As Integer
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim txt As String = "Dog"
Dim ret As Integer
WH_WatchDog = FindWindow(vbNullString, "WatchDog") '受信側のハンドル取得
If WH_WatchDog <> 0 Then
Dim bytearry() As Byte = _
System.Text.Encoding.Default.GetBytes(txt)
Dim len As Integer = bytearry.Length
Dim cds As COPYDATASTRUCT
cds.dwData = 0
cds.cbData = len + 1
cds.lpData = txt
ret = SendMessage(WH_WatchDog, WM_COPYDATA, 0, cds) 'ここでエラー発生
End If
End Sub
End Class
解決のアドバイスをよろしくお願いいたします。
ByVal lParam As COPYDATASTRUCT
ByRef じゃ〜ないの?
http://www.geocities.jp/hatanero/sendmessage1.html
因みに、コード自体は酷似していますが、そこのコードを
コピーしたわけではない?
以上。
> Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
SendMessageA や FindWindowA ではなく、Declare Unicode Function で
SendMessageW や FindWindowW を用いることをお奨めします。
(あるいは、Auto 指定を使う手もあります)
Ansi 指定でも間違いでは無いのですが、Win98 等で動かすのでもなければ、
今更 A 系 API を呼び出す利点は無い気がします。
> ByVal lParam As COPYDATASTRUCT) As Integer
WM_COPYDATA が求める lParam の型は、COPYDATASTRUCT のポインタです。すなわち、
(a案) COPYDATASTRUCT を Class として宣言し、ByVal で渡す。
(b案) COPYDATASTRUCT を Structure として宣言し、ByRef で渡す。
(c案) COPYDATASTRUCT 相当のバイナリの先頭アドレスを渡す。
などが求められるわけですが、現在のコードはいずれにも合致していません。
> Public Structure COPYDATASTRUCT
API に渡す Class や Structure には、StructureLayout 属性を付与した方が安全です。
特に String をメンバーに含む場合には、Charset も一緒に指定しましょう。
> Public Structure COPYDATASTRUCT
> Public dwData As Integer
> Public cbData As Integer
> Public lpData As String
> End Structure
COPYDATASTRUCT の本来の宣言は、下記のようになっています。
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
dwData は Integer ではなく、IntPtr とするのが正しいです。
dwData のサイズは、Win32 と Win64 で異なることに注意してください。
また、lpData に渡せるデータは、文字列とは限りません。
汎用的にするなら、lpData As IntPtr として宣言しておき、
そこに文字列を渡す場合には、Marshal.StringToHGlobal〜 と
Marshal.FreeHGlobal を使うようにします。
文字列しか渡さない場合は、lpData As String にすることも可能ですが、
その場合は、lpData に MarshalAs 属性を付与しておいた方が良いでしょう。
下記のスレッドも参照してみてください。
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+201205/12050001.txt
オショウ様、魔界の仮面弁士様
早速のアドバイスありがとうございました。
ご指摘の箇所を修正することにより無事動作するようになりました。
詳細は再度報告いたしますが、とりあえず途中結果報告まで。
その後の結果報告です
オショウ様
>ByRef じゃ〜ないの?
間違えました。ByRefにすることによりエラーはでなくなり希望の動作になりました。
>因みに、コード自体は酷似していますが、そこのコードを
>コピーしたわけではない?
その通りです。コピーし元にしましたが、他の場所でエラーが出たため色々変更し
間違えたようです。
魔界の仮面弁士様
>SendMessageW や FindWindowW を用いることをお奨めします。
今までSendMessageA や FindWindowAしか使用したことがありませんでしたが、
SendMessageW,FindWindowWを使用して希望の動作になることを確認しました。
>汎用的にするなら、lpData As IntPtr として宣言しておき、
>そこに文字列を渡す場合には、Marshal.StringToHGlobal〜 と
>Marshal.FreeHGlobal を使うようにします。
Marshalの使い方がよくわからないため、もう少し勉強しようと思います。
最終的に以下のようにして正常動作しています。
お二方、本当にありがとうございました。
Public Class Form1
Private Const WM_COPYDATA As Integer = &H4A
Private WH_WatchDog As IntPtr
Public Structure COPYDATASTRUCT
Public dwData As IntPtr
Public cbData As Integer
Public lpData As String
End Structure
Private Declare Unicode Function FindWindow Lib "user32" Alias "FindWindowW" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
Private Declare Unicode Function SendMessage Lib "user32" Alias "SendMessageW" _
(ByVal hwnd As IntPtr, _
ByVal msg As Integer, _
ByVal wParam As Integer, _
ByRef lParam As COPYDATASTRUCT) As Integer
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim txt As String = "Dog"
Dim ret As Integer
WH_WatchDog = FindWindow(vbNullString, "WatchDog") '受信側のハンドル取得
If WH_WatchDog <> 0 Then
Dim bytearry() As Byte = _
System.Text.Encoding.Default.GetBytes(txt)
Dim len As Integer = bytearry.Length
Dim cds As COPYDATASTRUCT
cds.dwData = 0
cds.cbData = len + 1
cds.lpData = txt
ret = SendMessage(WH_WatchDog, WM_COPYDATA, 0, cds)
End If
End Sub
End Class
> 今までSendMessageA や FindWindowAしか使用したことがありませんでしたが、
> SendMessageW,FindWindowWを使用して希望の動作になることを確認しました。
FindWindowA の場合、Shift_JIS で表現できない文字列、たとえば
Dim nihao As String = ChrW(&H4F60) & "好"
Dim cubicMeter As String = ChrW(&H33A5)
などが使われているウィンドウを処理することができません。
今回は A でも W でも動作させれらるとは思いますが、Win98 系向けの
VB.NET アプリを作るわけでは無いのなら、A 系を採用するメリットは
あまり無いと思います。
> ByVal wParam As Integer, _
Int32 型を採用されているようですが、WPARAM のサイズは
4バイト固定ではなく、Win16/Win32/Win64 環境で異なる事に注意してください。
WPARAM/LPARAM のサイズはポインターの幅であるため、本来は
wParam/lParam 共に IntPtr で宣言されるべきものです。
さらに言えば、SendMessage の戻り値である LRESULT 型についても、
本来は IntPtr とすることが求められます。
ただし 32bit 環境でしか実行されない場合に限っては、
ByVal wParam As Integer を用いることもできます。
4 バイト幅の環境で動作することを保証したいのであれば、今回のコードを
x86 ビルドにてコンパイルするようにしてください。AnyCPU でビルドするなら
IntPtr 型で宣言する必要があります。
> If WH_WatchDog <> 0 Then
これだと、Option Strict On モードでコンパイルが通らなくなります。
WH_WatchDog は Integer ではなく IntPtr なので、
比較するべきは 0 ではなく、IntPtr.Zero となります。
> cds.dwData = 0
これも同様の理由から、「cds.dwData = IntPtr.Zero」とすべきです。
> cds.cbData = len + 1
+1 している理由は何ですか?
> Dim bytearry() As Byte = System.Text.Encoding.Default.GetBytes(txt)
> Dim len As Integer = bytearry.Length
String 型として lpData を用意するのであれば、COPYDATASTRUCT に
<StructLayout(…)> で Charset 指定を明示した方が良いでしょう。
Encoding.Default なら Charset.Ansi を指定しておくようにします。
なお、COPYDATASTRUCT.lpData に格納するデータを Unicode 文字列とするか
Shift_JIS 文字列とするかは、送信側と受信側で一致させておいてください。
ツイート | ![]() |