如何在VB6和c#之间发送/接收windows消息?
我知道我可以用下面的代码在c#中接收消息,如何发送到vb6,如何在vb6中接收,如何从vb6发送如何在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 = (
[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
)。