如何在VB6和C#之间发送/接收Windows消息?

4

我知道在C#中可以使用以下代码接收消息,那么如何在VB6中发送和接收消息呢?

    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
    protected override void WndProc(ref Message m)
    {

        int _iWParam = (int)m.WParam;
        int _iLParam = (int)m.LParam;
        switch ((ECGCardioCard.APIMessage)m.WParam)
        {
            // handling code goes here
        }
        base.WndProc(ref m);
    }
3个回答

6
在我开始之前,我想说我同意MarkJ的观点。COM互操作将使您的生活更加轻松,并且不需要您做太多工作。
使用SendMessage是通过Windows消息处理程序调用一侧或另一侧的首选方法。对于复杂类型,PostMessage很难使用,因为在.NET和VB6中与Windows消息相关联的数据的生命周期在消息排队时很难管理,并且除非您实现某种形式的回调机制,否则无法完成消息。
无论如何,从任何地方向C#窗口发送Windows消息只需要知道接收消息的C#窗口的HWND。您的片段作为处理程序看起来是正确的,只是switch语句应该首先检查Msg参数。
protected override void WndProc(ref Message m)
{

    int _iWParam = (int)m.WParam;
    int _iLParam = (int)m.LParam;
    switch ((ECGCardioCard.APIMessage)m.Msg)
    {
            // handling code goes here
    }
    base.WndProc(ref m);
}

从 C# 窗体、窗口或控件中获取窗口句柄可以通过 .Handle 属性完成。 Control.Handle Property @ MSDN 我们假设您已经有了一种将窗口句柄从 C# 传输到 VB6 的方法。
从 VB6 中,SendMessage 窗口的签名如下:
Private Declare Function SendMessage Lib "USER32.DLL" _
    (ByVal hWnd As Long, ByVal uMsg As Long, _
    ByVal wParam As Long, ByVal lParam As Long) As Long

要调用它,您需要执行类似以下的操作。为了简洁起见,uMsg是WM_APP(32768),wParam / lParam均为0:
Dim retval As Long
retval = SendMessage(hWnd, 32768, 0, 0)

同样地,从C#发送消息也是相似的。在VB6中获取窗口的HWND,请使用应该接收消息的VB6窗口的.hWnd属性。
由于您正在使用自己的一组消息标识符,因此在VB6中处理自定义消息标识符需要额外的步骤。大多数人通过子类化表单窗口来处理此问题,并使用子类过程来过滤这些消息。我已经包含了示例代码,以演示C#到VB6的转换,因为在VB6中处理自定义消息更加棘手。
这是测试程序对的源代码,一个C#库和一个VB6 Forms项目。C#库应该在项目设置中配置为“注册为COM互操作”和“使程序集COM可见”。
首先是C#库。该库包含一个单独的COM组件,将作为类型“CSMessageLibrary.TestSenderSimple”对VB6可见。请注意,您需要为SendMessage包括一个P/Invoke签名(就像VB6方法)。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace CSMessageLibrary
{
    [ComVisible(true)]
    public interface ITestSenderSimple
    {
        // NOTE: Can't use IntPtr because it isn't VB6-compatible
        int hostwindow { get; set;}
        void DoTest(int number);
    }

    [ComVisible(true)]
    public class TestSenderSimple : ITestSenderSimple
    {
        public TestSenderSimple()
        {
            m_HostWindow = IntPtr.Zero;
            m_count = 0;
        }

        IntPtr m_HostWindow;
        int m_count;

        #region ITestSenderSimple Members
        public int hostwindow 
        {
            get { return (int)m_HostWindow; } 
            set { m_HostWindow = (IntPtr)value; } 
        }

        public void DoTest(int number)
        {
            m_count++;

            // WM_APP is 0x8000 (32768 decimal)
            IntPtr retval = SendMessage(
                m_HostWindow, 0x8000, (IntPtr)m_count, (IntPtr)number);
        }
        #endregion

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        extern public static IntPtr SendMessage(
          IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);
    }
}

现在,在VB6端,您需要添加支持子类化窗口的功能。除了可以针对每个窗口应用更好的解决方案之外,我们将仅使用一些演示如何设置单个窗口的内容。
首先,要运行此示例,请确保您已构建了C#应用程序并正确地将其注册到COM中。然后,从VB6添加对与C#输出相邻的.tlb文件的引用。您会在C#项目的bin / Debug或bin / Release目录下找到这个文件。
以下代码应该放置在一个模块中。在我的测试项目中,我使用了一个名为“Module1”的模块。此模块中应注意以下定义。
WM_APP - 用作自定义消息标识符,不会受到干扰。
GWL_WNDPROC - SetWindowLong所用的常量,用于请求修改窗口处理程序。
SetWindowLong - Win32函数,可修改窗口上的特殊属性。
CallWindowProc - Win32函数,可将窗口消息转发到指定的窗口处理程序(函数)。
SubclassWindow - 模块函数,用于为指定的窗口设置子类化。
UnsubclassWindow - 模块函数,用于拆除指定窗口的子类化。
SubWndProc - 模块函数,将通过子类化插入,以允许我们拦截自定义窗口消息。
Public Const WM_APP As Long = 32768
Private Const GWL_WNDPROC = (-4)
Private procOld As Long

Private Declare Function CallWindowProc Lib "USER32.DLL" Alias "CallWindowProcA" _
    (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal uMsg As Long, _
    ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Function SetWindowLong Lib "USER32.DLL" Alias "SetWindowLongA" _
    (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Sub SubclassWindow(ByVal hWnd As Long)
    procOld = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf SubWndProc)
End Sub

Public Sub UnsubclassWindow(ByVal hWnd As Long)
    procOld = SetWindowLong(hWnd, GWL_WNDPROC, procOld)
End Sub

Private Function SubWndProc( _
        ByVal hWnd As Long, _
        ByVal iMsg As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long) As Long

    If hWnd = Form1.hWnd Then
        If iMsg = WM_APP Then
            Dim strInfo As String
            strInfo = "wParam: " & CStr(wParam) & vbCrLf & "lParam: " & CStr(lParam)

            Call MsgBox(strInfo, vbOKOnly, "WM_APP Received!")

            SubWndProc = True
            Exit Function
        End If
    End If

    SubWndProc = CallWindowProc(procOld, hWnd, iMsg, wParam, lParam)
End Function

在测试表单中,我已经将测试C#对象实例作为表单的成员进行了连接。该表单包括一个id为'Command1'的按钮。子类在表单加载时设置,然后在表单关闭时移除。
Dim CSharpClient As New CSMessageLibrary.TestSenderSimple

Private Sub Command1_Click()
    CSharpClient.DoTest (42)
End Sub

Private Sub Form_Load()
    CSharpClient.hostwindow = Form1.hWnd
    Module1.SubclassWindow (Form1.hWnd)
End Sub

Private Sub Form_Unload(Cancel As Integer)
    CSharpClient.hostwindow = 0
    Module1.UnsubclassWindow (Form1.hWnd)
End Sub

发送适合4字节的数字参数很简单,可以作为wParam或lParam。然而,发送复杂类型和字符串则更加困难。我看到您已经为此创建了一个单独的问题,所以我会在那里提供答案。 REF: 如何将结构体从C#发送到VB6,并从VB6发送到C#?

1
+1. 不过,有一种更“面向对象”的方法可以实现VB6的子类化。请参考http://visualstudiomagazine.com/articles/2009/07/16/subclassing-the-xp-way.aspx。 - MarkJ
我选择这种方法只是为了让示例更简短。然而,那个链接中的技术确实更优秀。 - meklarian
为什么我收到编译错误“AddressOf运算符的使用无效”?我没有使用模块,而是直接使用Form1。这样做可以吗? - ufo
1
@ufo,我已经有一段时间没有使用VB了,但我认为你无法避免使用模块,因为表单上的成员函数将不会具有正确的函数签名。在这种情况下,将存在一个隐式的额外参数(就像非静态类成员函数在C++中隐式声明this一样)。 - meklarian

3
要在VB6中发送消息,您需要使用API调用(SendMessage或PostMessage)。要在VB6中接收消息,您需要使用子类化(复杂 - 这是我所知道的最佳方法)。
您考虑过使用COM互操作性吗?这是一种比Windows消息更容易在VB6和C#之间通信的方式。

1

使用PostMessage Windows API函数。

在你的类开始处:

[DllImport("User32.dll", EntryPoint="PostMessage")]
private static extern int PostMessage(int hWnd, int Msg, int wParam, int lParam);

const int WM_USER = 0x0400;
const int CM_MARK = WM_USER + 1;

然后通过重写类的 WndProc 方法来处理消息。

protected override void WndProc(ref Message m)
{
  if (m.Msg == CM_MARK) {
    if (this.ActiveControl is TextBox) {
      ((TextBox)this.ActiveControl).SelectAll();
    }
  }
  base.WndProc(ref m);
} // end sub.

然后在你的 Enter 事件中:

private void txtMedia_Enter(object sender, EventArgs e)
{
  PostMessage(Handle.ToInt32(), CM_MARK, 0, 0);
} // end sub.

这是有效的,因为您强制使自定义处理在 Windows 执行其默认处理 Enter 事件及其关联的鼠标处理之后发生。您将请求放置在消息队列中,并按顺序在 WndProc 事件中处理它。当调用您的事件时,您确保当前窗口是文本框,并在必要时选择它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接