VB.netとVB6間のSendMessageについて(移管)

解決


クロウ  2012-05-17 09:14:51  No: 143286  IP: 192.*.*.*

Vista/VB2008/VB6環境で開発しています。

VB.netからSendMessageで文字列を送り、VB6で受信させるのが目的です。

最初にネットでサンプルを見つけ、試験的にVB6同時で送信側、受信側のソフトを作り成功しました。
その後、送信側のみVB.net(2008)で別途作りました。
その結果受信側では何の反応も無く、受信できません。
UAC関連の問題かも?とは思いましたが、VB6同士では問題ないのでどうなのでしょうか・・?
問題点を教えていただけますか?


送信側VB.net
Public Class Form1
    Structure COPYDATASTRUCT
        Dim dwData As Integer
        Dim cbData As Integer
        Dim lpData As String 
    End Structure

    Public Const WM_COPYDATA As Int32 = &H4A

    Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
        (ByVal hwnd As Integer, ByVal msg As Integer, _
         ByVal wParam As Integer, ByVal lParam As COPYDATASTRUCT) As Integer

    Private Declare Function FindWindow Lib "user32.dll" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim sdtCOPYDATASTRUCT As COPYDATASTRUCT
        sdtCOPYDATASTRUCT.dwData = 0
        sdtCOPYDATASTRUCT.lpData = TextBox1.Text
        sdtCOPYDATASTRUCT.cbData = TextBox1.Text.Length + 1

        Dim hWndTo As Integer
        Dim l As Long
        hWndTo = FindWindow(vbNullString, "受信側フォーム名")
        If hWndTo <> 0 Then
            l = SendMessage(hWndTo, WM_COPYDATA, 0, sdtCOPYDATASTRUCT)
        End If
    End Sub
End Class


受信側VB6
'フォーム
Private Sub Form_Activate()
    Call Hook(Me.hwnd)
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Call UnHook(Me.hwnd)
End Sub

'モジュール
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Const GWL_WNDPROC = (-4)

Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Public Const WM_COPYDATA = &H4A

Public phWnd As Long

Public Declare Sub memcpy Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal iSize&)

Type COPYDATASTRUCT
        dwData As Long
        cbData As Long
        lpData As Long
End Type

'-------------------------------------------------------
'
'-------------------------------------------------------
Public Function WndProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Static bolWndProcCheck As Boolean
    Dim str As String
    Dim lngret As Long
    Dim sdtCOPYDATASTRUCT As COPYDATASTRUCT
    
    If Not bolWndProcCheck Then
        bolWndProcCheck = True
        Select Case uMsg
        Case WM_COPYDATA
            Call memcpy(ByVal VarPtr(sdtCOPYDATASTRUCT), ByVal lParam&, LenB(sdtCOPYDATASTRUCT))
            str = String$(sdtCOPYDATASTRUCT.cbData, vbNullChar)
            Call memcpy(ByVal str, ByVal sdtCOPYDATASTRUCT.lpData, sdtCOPYDATASTRUCT.cbData)
            Form1.Text1.Text = str
        End Select
        bolWndProcCheck = False
    End If
    WndProc = CallWindowProc(phWnd, hwnd, uMsg, wParam, lParam)
End Function

Public Sub Hook(hwnd As Long)
    phWnd = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WndProc)
End Sub

Public Sub UnHook(hwnd As Long)
    Dim lngret As Long
    If phWnd <> 0 Then
        lngret = SetWindowLong(hwnd, GWL_WNDPROC, phWnd)
    End If
End Sub

編集 削除
魔界の仮面弁士  2012-05-17 09:52:00  No: 143287  IP: 192.*.*.*

既にサンプルコード付きで回答していますが、それでもだめだったのでしょうか?
http://madia.world.coocan.jp/cgi-bin/VBBBS/wwwlng.cgi?print+201205/12050003.txt

編集 削除
クロウ  2012-05-17 10:15:57  No: 143288  IP: 192.*.*.*

魔界の仮面弁士様
サンプルありがとうございます。
丸写しで試してみましたが、やはり反応なしでした。
XPの環境下では私の作った.net版送信ソフトも教えて頂いたサンプルの送信ソフトもどちらも
ばっちり動いています。
やはりUAC絡みなのでしょうか・・?

編集 削除
クロウ  2012-05-17 11:50:30  No: 143289  IP: 192.*.*.*

基本的な質問ですが・・
前回のご指摘の
>最大の問題は、COPYDATASTRUCT が値渡しされている点かと。
の件ですが、教えていただいたサンプルのCOPYDATASTRUCT についてもByVal のままですが、これでOKなのでしょうか?


Private Declare Ansi Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal hwnd As IntPtr, _
         ByVal msg As Integer, _
         ByVal wParam As IntPtr, _
         ByVal lParam As COPYDATASTRUCT) As Integer

編集 削除
魔界の仮面弁士  2012-05-17 11:56:51  No: 143290  IP: 192.*.*.*

> やはりUAC絡みなのでしょうか・・?
う〜ん。特に関係ないと思いますけれどね。

ところで、VB6 側の TextBox に WM_SETTEXT するというのは駄目でしょうか。

そうすれば VB6 側は、わざわざフック処理などせずとも、TextBox の
Change イベントだけでデータ受信を受け取れるかと思います。


> 丸写しで試してみましたが、
手元の Vista 機は Win8 に入れ替えてしまったので、Vista に近い環境として
  Win7 Enterprise(x64)/VB6 Enterprise/VB2010 Ultimate(x86ビルド/AnyCPUビルド/x64ビルド)
にて動作確認しています。


> 問題点を教えていただけますか?
以下の点を確認してみてください。

(1) VB6 側では、WM_COPYDATA 自体が受け取れないのでしょうか? それとも、
  メッセージは受信できたが、中のデータが不正という事でしょうか?

(2) VB2010 側で、Option Strict On を宣言した状態の EXE の場合、XP で動作しますか?

(3) FindWindow で取得した HWND は、目的の VB6 Form を指し示していましたか?
  → HWND が 0 になっているとか、あるいは同名の別ウィンドウに誤送信されているとか。

(4) WM_COPYDATA 送信時の SendMessage の戻り値は =0 でしたか? ≠0 でしたか?
  → 正常時は 0 です。

(5) COPYDATASTRUCT のメモリ内容を確認してみてください。Unicode テキストとして
  格納されていたり、サイズ指定が間違っていたりはしませんか?



> XPの環境下では私の作った.net版送信ソフトも
> 教えて頂いたサンプルの送信ソフトもどちらもばっちり動いています。
むしろ、何故あのコードで動いてしまうのかが疑問です。

当方で、『VBクロウWM_COPYDATA』という文字列を送信してみましたが、
提示されたコードの場合、SendMessage の時点で失敗してしまいましたし(x86 ビルド時)。
http://www.vb-user.net/junk/replySamples/2012.05.16.20.16/PInvokeStackImbalance.png


そもそも、WM_COPYDATA は lparam に「COPYDATASTRUCT のポインタ」を要求するはずです。

ゆえにこの場合の lparam というのは、VB6 においては
  「ByRef COPYDATASTRUCTユーザー定義型」
  「ByVal Long にして VarPtr(COPYDATASTRUCT変数)を指定」
  「ByRef Any にして COPYDATASTRUCTユーザー定義型を指定」
  「ByRef Any にして  VarPtr(COPYDATASTRUCT変数)を指定」
  「ByRef Byte にして COPYDATASTRUCT相当の Byte 配列の先頭要素 buf(0) を指定」
などという形で用いられ、VB.NET においては、
  「ByRef COPYDATASTRUCT構造体」
  「ByVal COPYDATASTRUCTクラス」
  「ByVal COPYDATASTRUCTを指し示すIntPtr」
のいずれかを渡すことになるはずです。


しかし、クロウさんの VB2010 コードでは、COPYDATASTRUCT 構造体が ByVal で渡されています。
これでは仮に動いたとしても、全然別のデータが参照されてしまうはずなのですが…。
(ByVal/ByRef を省略した場合には、VB.NET では ByVal と解釈されます)


> VB6同士では問題ないのでどうなのでしょうか・・?
VB6 とはまったく違うコードですよね?

VB6 では、そもそも「ユーザー定義型を値渡しする」ことができませんので、
必然的に ByRef COPYDATASTRUCT として宣言されていたと思いますよ。
(ByVal/ByRef を省略した場合には、VB6 では ByRef と解釈されます)



それと、COPYDATASTRUCT.dwData の本来の型は ULONG_PTR なので、
VB2010 では As Integer ではなく、As IntPtr にするべきです。
(VB6 では、As Long もしくは As OLE_HANDLE です)

編集 削除
魔界の仮面弁士  2012-05-17 12:03:44  No: 143291  IP: 192.*.*.*

>> 最大の問題は、COPYDATASTRUCT が値渡しされている点かと。
> の件ですが、教えていただいたサンプルのCOPYDATASTRUCT についてもByVal のままですが、これでOKなのでしょうか?

「値渡しと参照渡し」だけでなく「値型と参照型」にも気を配ってみてください。

私のコードは、「Class COPYDATASTRUCT」ですが、
クロウさんは、「Structure COPYDATASTRUCT」です。

構造体は値型です。値型なら ByRef で渡すことになりますが、
クラスは参照型なので、この場合は ByVal が正解です。

ByRef クラス で渡した場合、ポインタのポインタが指定されることになります。

編集 削除
クロウ  2012-05-17 13:18:30  No: 143292  IP: 192.*.*.*

詳しい回答ありがとうございます。

>(1) VB6 側では、WM_COPYDATA 自体が受け取れないのでしょうか? 
はい、イベントすら発生しないです。

>(2) VB2010 側で、Option Strict On を宣言した状態の EXE の場合、XP で動作しますか?

宣言した時点で、SendMessage絡みであちこちエラーがでました。

>(3) FindWindow で取得した HWND は、目的の VB6 Form を指し示していましたか?

HWNDは受信ソフトが起動しているときのみ0以外の値がとれているのを確認しました。

>(4) WM_COPYDATA 送信時の SendMessage の戻り値は =0 でしたか? ≠0 でしたか?
  → 正常時は 0 です。
.net側のSendMessage の戻り値は =0 でした。

>(5) COPYDATASTRUCT のメモリ内容を確認してみてください。Unicode テキストとして
  格納されていたり、サイズ指定が間違っていたりはしませんか?
サイズも大丈夫そうでした。

ここまでいろいろ調べていたら、まず、サンプル版ソフトがVistaのUACを有効にすると動くようになりました。
自作の.net版は値渡しの件とIntPtr への修正等でサンプルに近づけてみます。まる写しでもいいのですが、動きを学習してみます。

VB6 側の TextBox に WM_SETTEXT のほうがむしろ理想なのでやってみます。

編集 削除
魔界の仮面弁士  2012-05-17 14:28:22  No: 143293  IP: 192.*.*.*

> 詳しい回答ありがとうございます。
COPYDATASTRUCT の ByVal/ByRef の説明、あれで分かりますか?


> 宣言した時点で、SendMessage絡みであちこちエラーがでました。
はい、そのままだとエラーが出るのは一応理解していますが、
それらの「暗黙の型変換」を修正した上で、XP で再確認できないか、という意味です。

これには、『何故 XP で ByVal COPYDATASTRUCT構造体のコードが動いていたのかが
理解できなかったので、もう一度検証してみて欲しい』という気持ちも含まれています。(^^;
強制はできませんけれど。


> HWNDは受信ソフトが起動しているときのみ0以外の値がとれているのを確認しました。
もちろん、0 でないことは当然として、その値が目的の Form のウィンドウハンドルと
同じ値であることを、念のために確認しておいてください、という意味です。
(Visual Studio 付属の spyxx.exe で確認するとか、VB6 側で Me.hWnd を表示させておくとか)


> サイズも大丈夫そうでした。
限定条件下では、元のコードのままでも動作しますが(ByVal/ByRef 件の件は別として)、
結構危なっかしいソースだとは思いますよ。

(1) 今回は x86 ビルドなので Integer でも通用しますが、64bit として実行された場合、
  dwData メンバーの箇所で 4 バイトのズレが生じることになります。

(2) cbData に TextBox1.Text.Length + 1 を指定するのも、本来は誤りです。
  指定するのは「文字数」ではなく「バイト数」ですし、もしも +1 するのであれば、
  文字列の末尾に、明示的に vbNullChar を付与しておかなければいけません。

(3) 構造体には StructLayout 属性を明示的に付与するようにしましょう。特に、
  メンバーに文字列型を含む場合には、CharSet 指定も重要になってきます。


> VistaのUACを有効にすると動くようになりました。
あれ? UAC のチェックボックスって、既定で on になっているはずでは…?

『ユーザー アカウント制御 (UAC) を使ってコンピュータの保護に役立たせる』
http://windows.microsoft.com/ja-JP/windows-vista/How-do-I-change-the-behavior-of-the-User-Account-Control-message


保護モードや一般ユーザーモードで実行されているプロセスから、
管理者モードのプロセスに対して SendMessage した場合、
SendMessage 関数自体は成功したように見えますが(0が返される)、
そのメッセージは実際には破棄されてしまい、相手には届きません。

その意味においては、UAC が与える影響というものはありますが、
受信側を管理者モードで起動していたわけでは無いでしょうし、
そもそも UAC は、今回は無効に設定されていたのですよね…?

編集 削除
名前なし  2012-05-17 23:44:36  No: 143294  IP: 192.*.*.*

試してみました。Win7でダイアログだけのWindowでspy+で見た限りだと
クロウさんの提示のコードだけだとダイアログのハンドルにたいして
WM_COPYDATAは検出せずでした。
WM_KEYDOWNは検出するようなのでので何か違いがあるのでは?
ハンドルが違うとか・・・。

編集 削除
クロウ  2012-05-29 19:26:57  No: 143295  IP: 192.*.*.*

追い込み時期で遅くなりました。

>『何故 XP で ByVal COPYDATASTRUCT構造体のコードが動いていたのかが
質問した時は気づかなかったのですがByref等を修正してから行っていたようです。
なので元のままでは動かずが正解でした。

UACは普段うっとおしいのでオフで使っていました。

結局・・・VB6 側の TextBox に WM_SETTEXT であっさりと動き、解決しました。
ありがとうございました。

編集 削除