【问题标题】:How do I send/receive windows messages between VB6 and c#?如何在 VB6 和 c# 之间发送/接收 windows 消息?
【发布时间】:2009-10-29 17:07:11
【问题描述】:

我知道我可以在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);
    }

【问题讨论】:

    标签: c# vb6 wndproc


    【解决方案1】:

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

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

    无论如何,从任何地方向 C# 窗口发送 windows 消息只需要您知道要接收消息的 C# 窗口的 HWND。您的 sn-p 作为处理程序看起来是正确的,除了 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# 窗体、窗口或控件中检索窗口句柄。

    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 中处理自定义消息标识符有额外的步骤。大多数人通过子类化表单窗口并使用子类过程来过滤这些消息来处理这个问题。因为在 VB6 中处理自定义消息比较棘手,所以我已经包含了示例代码来演示 C# 到 VB6。

    这里是这对测试程序、一个 C# 库和一个 VB6 Forms 项目的源代码。 C# 库应在项目设置中使用“Register for COM Interop”和“Make Assembly COM-Visible”进行配置。

    首先,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_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: How do I send a struct from C# to VB6, and from VB6 to C#?

    【讨论】:

    • +1。尽管visualstudiomagazine.com/articles/2009/07/16/… 有一种更“客观”的方式来进行 VB6 子类化
    • 我采用这种方法只是为了让样本更短。不过,该链接的技术绝对优越。
    • 为什么我会收到编译错误“AddressOf 运算符的无效使用”?我不使用模块,而是直接使用Form1。这是否允许?
    • @ufo 自从我使用 VB 以来已经有一段时间了,但我相信你不能避免使用 Module 因为表单上的成员函数没有正确的函数签名.在这种情况下会有一个隐含的额外参数(就像非静态类成员函数如何在 C++ 中隐式声明 this)。
    【解决方案2】:

    要在 VB6 中发送,您需要使用 API 调用(SendMessage 或 PostMessage)。要在 VB6 中接收,您需要使用子类化(复杂 - 这里是 best way I know)。

    您是否考虑过改用COM Interop?这比 windows 消息更容易在 VB6 和 C# 之间进行通信。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2011-03-09
        • 2016-08-01
        • 2013-08-13
        • 2016-05-20
        • 1970-01-01
        • 2012-08-03
        • 1970-01-01
        • 2011-04-23
        • 1970-01-01
        相关资源
        最近更新 更多