Caveat:
在我开始之前,人们可能有兴趣从你的其他问题中注意到一些问题。
REF: How do I send/receive windows messages from VB6 and C#?
正如这里和你的另一篇文章所提到的,你真的应该重新考虑如何使这项工作发挥作用。尤其是,即使您对这里的技术很好,您的同事或其他可能需要维护您的代码的人也会遇到非常糟糕的情况。注意调试过程在vb6中也非常困难-经常保存并使用很多断点!如果代码中存在错误,则会出现大量崩溃。
此外,不应将邮件与此技术一起使用。这需要更多的清理工作。
解决方案:
我附带了一个示例,它可以返回只包含字符串和整数类型的结构。这项工作需要大量的运动部件。我们将深入讨论从C到VB的过程,因为这更为棘手。一旦你知道如何做到这一点,反过来就不难了。
首先,在C面,您应该声明您的结构。结构的包装在C中实际上并不坏。下面是一个C示例类,它是COM可见的,演示了如何包装结构。关键是在您的呼叫的对方使用marshal.structureToptr和marshal.destroystructure。根据所涉及的类型,您甚至不必编写代码来映射类型。使用marshalas属性为vb6标记正确的映射。marshalas中使用的枚举中的大多数项对应于variants和com自动化中使用的不同变量类型。
这里使用的缓冲区由hglobal支持,调用结束后需要释放该缓冲区。这里也可以使用gchandle,这也需要类似的清理。
MarshalAsAttribute Class @ MSDN
Marshal.StructureToPtr Method @ MSDN
Marshal.DestroyStructure Method @ MSDN
Marshal.AllocHGlobal Method @ MSDN
Marshal.FreeHGlobal Method @ MSDN
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace HostLibrary
{
public struct TestInfo
{
[MarshalAs(UnmanagedType.BStr)]
public string label;
[MarshalAs(UnmanagedType.I4)]
public int count;
}
[ComVisible(true)]
public interface ITestSender
{
int hostwindow {get; set;}
void DoTest(string someParameter);
}
[ComVisible(true)]
public class TestSender : ITestSender
{
public TestSender()
{
m_HostWindow = IntPtr.Zero;
m_count = 0;
}
IntPtr m_HostWindow;
int m_count;
#region ITestSender Members
public int hostwindow {
get { return (int)m_HostWindow; }
set { m_HostWindow = (IntPtr)value; } }
public void DoTest(string strParameter)
{
m_count++;
TestInfo inf;
inf.label = strParameter;
inf.count = m_count;
IntPtr lparam = IntPtr.Zero;
try
{
lparam = Marshal.AllocHGlobal(Marshal.SizeOf(inf));
Marshal.StructureToPtr(inf, lparam, false);
IntPtr retval = SendMessage(
m_HostWindow, 0x8000, IntPtr.Zero, lparam);
}
finally
{
if (lparam != IntPtr.Zero)
{
Marshal.DestroyStructure(lparam, typeof(TestInfo));
Marshal.FreeHGlobal(lparam);
}
}
}
#endregion
[DllImport("user32.dll", CharSet = CharSet.Auto)]
extern public static IntPtr SendMessage(
IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);
}
}
在vb6端,您需要设置一个机制来拦截消息。由于细节在您的其他问题和其他地方都有介绍,我将跳过子类化的主题。
要在vb6端解包结构,需要对每个字段执行此操作,因为没有现成的机制可以取消对指针值的引用并将其转换为结构。幸运的是,如果您没有在C中另外指定字段成员,您可以期望字段成员在VB6的4字节边界上对齐。这允许我们逐个字段地工作,将项目从一个表示映射到另一个表示。
首先,一些模块代码来完成所有的支持工作。以下是功能和注意事项。
testinfo类型-两边使用的结构的镜像定义。
copymemory-一个可用于复制字节的win32函数。
ZeroMemory-将内存重置为零字节值的Win32函数。
除了这些项之外,我们还使用了vb6中未记录的varptr()函数来获取项的地址。我们可以用它来索引到vb6端的结构中。有关此功能的详细信息,请参阅以下链接。
How to get the Address of Variables in Visual Basic @ support.microsoft.com
Public Const WM_APP As Long = 32768
Private Const GWL_WNDPROC = (-4)
Private procOld As Long
Type TestInfo
label As String
count As Integer
End Type
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
Private Declare Sub CopyMemory Lib "KERNEL32.DLL" Alias "RtlMoveMemory" _
(ByVal pDst As Long, ByVal pSrc As Long, ByVal ByteLen As Integer)
Private Declare Sub ZeroMemory Lib "KERNEL32.DLL" Alias "RtlZeroMemory" _
(ByVal pDst As Long, ByVal ByteLen As Integer)
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 inf As TestInfo
' Copy First Field (label)
Call CopyMemory(VarPtr(inf), lParam, 4)
' Copy Second Field (count)
Call CopyMemory(VarPtr(inf) + 4, lParam + 4, 4)
Dim strInfo As String
strInfo = "label: " & inf.label & vbCrLf & "count: " & CStr(inf.count)
Call MsgBox(strInfo, vbOKOnly, "WM_APP Received!")
' Clear the First Field (label) because it is a string
Call ZeroMemory(VarPtr(inf), 4)
' Do not have to clear the 2nd field because it is an integer
SubWndProc = True
Exit Function
End If
End If
SubWndProc = CallWindowProc(procOld, hWnd, iMsg, wParam, lParam)
End Function
请注意,此解决方案需要发送方和接收方的合作。因为我们不希望两次释放字符串字段,所以在返回控件之前,我们先清空在vb6端所做的复制。如果您试图为字段成员分配一个新值,这里将发生什么是未定义的,因此请避免编辑结构中的字段。
在映射字段中,C中的UnmanagedType.bstr与VB6中的字符串直接类似。
在vb6中,unmanagedType.i4映射到integer和long。您在UDT中指定的其他字段也有等价的字段,尽管我不确定VB6中的日期时间。
VB6应用程序(表单源代码)的其余部分很简单。
Dim CSharpClient As New HostLibrary.TestSender
Private Sub Command1_Click()
CSharpClient.DoTest ("Hello World from VB!")
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
现在,在将结构从vb6发送到c_时,需要执行相反的操作。对于一些简单的结构,您甚至可以只发送结构本身的地址。如果需要memberwise控件,可以使用globalalloc获得合适的缓冲区内存,然后使用globalfree释放它。对于每个字段,memberwise复制的执行方式与参数从C展开的方式相同。但是,调用后清理更简单。如果使用了缓冲区,则只需在将缓冲区中的内存交给globalfree之前将其清零即可。
GlobalAlloc Function (Windows) @ MSDN
GlobalFree Function (Windows) @ MSDN
当消息到达C端时,使用marshal.ptrtostructure()将intptr映射到.NET结构中。
Marshal.PtrToStructure Method @ MSDN