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

如何在VB6和c#之间发送/接收windows消息?,c#,vb6,wndproc,C#,Vb6,Wndproc,我知道我可以用下面的代码在c#中接收消息,如何发送到vb6,如何在vb6中接收,如何从vb6发送 [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] protected override void WndProc(ref Message m) { int _iWParam = (

我知道我可以用下面的代码在c#中接收消息,如何发送到vb6,如何在vb6中接收,如何从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);
    }

要在VB6中发送,需要使用API调用(或PostMessage)。要在VB6中接收,您需要使用子类化(复杂-下面是示例)


你有没有考虑改用它?与windows消息相比,在VB6和C之间进行通信要容易得多。

在开始之前,我想说我同意MarkJ。COM互操作将使您的生活更加轻松,并且不需要您做那么多工作

SendMessage是通过Windows消息处理程序调用一方或另一方的首选方式。PostMessage很难用于复杂类型,因为在消息排队时,与.NET和VB6中的windows消息相关联的数据的生存期很难管理,并且除非实现某种形式的回调机制,否则消息的完成情况是未知的

无论如何,从任何地方向C#window发送windows消息只需要知道接收消息的C#window的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);
}
可以通过.handle属性从C#窗体、窗口或控件检索窗口句柄

我们在这里假设您有某种方法可以将窗口句柄从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_应用程序(32768),wParam/lParam是0:

Dim retval As Long
retval = SendMessage(hWnd, 32768, 0, 0)
类似地,从C#发送消息也是如此。要获取VB6中窗口的HWND,请使用VB6中应接收消息的窗口的.HWND属性

由于您似乎正在使用自己的消息标识符集,因此在VB6中有额外的步骤来处理自定义消息标识符。大多数人通过对窗体窗口进行子类化,并使用子类过程过滤这些消息来处理此问题。由于在VB6中处理自定义消息更为棘手,因此我已包含示例代码来演示C#到VB6

这是这对测试程序、一个C#库和一个VB6表单项目的源代码。C#库应在项目设置中配置“为COM互操作注册”和“使程序集COM可见”

首先是C#图书馆。此库包含一个COM组件,VB6可以看到该组件的类型为“CSMessageLibrary.TestSenderSimple”。请注意,您需要为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_应用程序-用作无干扰的自定义消息标识符。
GWL_WNDPROC—用于SetWindowLong请求修改窗口处理程序的常量。
SetWindowLong—可以修改windows上特殊属性的Win32函数。
CallWindowProc-Win32函数,可将windows消息中继到指定的窗口处理程序(函数)。
子类窗口-用于为指定窗口设置子类的模块函数。
UnsubclassWindow-模块函数,用于分解指定窗口的子类。
SubWndProc-将通过子类化插入的模块函数,允许我们拦截自定义windows消息

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。但是,发送复杂类型和字符串要困难得多。我知道你已经为这个问题创建了一个单独的问题,所以我将在那里提供答案


使用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事件中依次处理。调用事件时,请确保当前窗口是一个文本框,如果是,请选择它。

+1。有一种更“objecty”的方法来完成VB6子类化,尽管我使用这种方法只是为了缩短示例。不过,该链接的技术绝对优越。为什么我会收到编译错误“AddressOf运算符的使用无效”?我不使用模块,而是直接使用Form1。这是允许的吗?@ufo我已经有一段时间没有使用VB了,但是我相信你不能避免使用Module,因为表单上的成员函数没有正确的函数签名。在这种情况下,将有一个隐式的额外参数(就像非静态类成员函数如何在C++中隐式声明
this
)。