如何将结构从C#发送到VB6,从VB6发送到C#?

我需要将一个结构从C#发送到VB6应用程序,修改VB6中的数据,然后通过Windows消息传递结果。 我该怎么做呢?

我能够使用PostMessage来回发送基本的int(在C#中使用DllImport并使用Windows消息传递注册vb6应用程序),但需要发送更多结构化数据,包括字符串,整数和小数。

寻找最简单的解决方案来实现来回传递结构数据。

VB6类型的基本样本

Public Type udtSessionData SessionID As Integer SessionName As String MinVal As Currency PctComplete As Double NVal As Integer ProcessedFlag As Boolean ProcessedDate As Date Length As Integer End Type 

警告:

在开始之前,人们可能有兴趣注意您的其他问题中的项目。

REF:如何从VB6和C#发送/接收Windows消息?

正如在这里和你的其他post中所提到的,你应该重新考虑尝试使这项工作。 特别是,即使你对这里的技术很好,你的同事或其他可能需要维护你的代码的人也会陷入非常糟糕的时期。 请注意,调试过程在VB6中也非常困难 – 经常保存并使用大量断点! 如果您的代码中存在错误,预计会发生大量崩溃。

此外,PostMessage不应与此技术一起使用。 这将需要更多的清理。

解:

我附上了一个可以传回一个只包含字符串和整数类型的结构的示例。 完成这项工作需要大量的活动部件。 我们将深入讨论从C#到VB的过程,因为这更棘手。 一旦你知道如何做到这一点,反过来就不那么难了。

首先,在C#方面,您应该声明您的结构。 在C#中打包结构实际上并不坏。 这是一个COM-visible的C#示例类,演示了如何包装结构。 关键是在通话的两侧使用Marshal.StructureToPtr和Marshal.DestroyStructure。 根据所涉及的类型,您甚至可能不必编写代码来映射类型。 使用MarshalAs属性标记VB6的正确映射。 MarshalAs中使用的枚举中的大多数项对应于VARIANT和COM自动化中使用的不同变量类型。

这里使用的缓冲区由HGlobal支持,需要在调用结束后释放。 也可以在这里使用GCHandle,这也需要类似的清理。

MarshalAsAttribute Class @ MSDN

Marshal.StructureToPtr方法@ MSDN
Marshal.DestroyStructure方法@ 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); // WM_APP is 0x8000 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字节边界上对齐。 这允许我们逐字段地工作,将项目从一个表示映射到另一个表示。

首先,一些模块代码可以完成所有支持工作。 这是function和注意事项。

TestInfo类型 – 两侧使用的结构的镜像定义。
CopyMemory – 可用于复制字节的win32函数。
ZeroMemory – 一个win32函数,可将内存重置为零字节值。

除了这些项目,我们还使用VB6中未记录的VarPtr()函数来获取项目的地址。 我们可以用它来索引VB6端的结构。 有关此function的详细信息,请参阅以下链接。

如何在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中的字符串。
UnmanagedType.I4在VB6中映射到Integer和Long。 您在UDT中指定的其他字段也具有等效项,但我不确定VB6中的DateTime。

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#发送结构时,您需要执行相反的操作。 对于一些简单的结构,您甚至可以只发送结构本身的地址。 如果需要成员控制,可以使用GlobalAlloc获取合适的缓冲区内存,然后使用GlobalFree释放它。 对于每个字段,成员副本的执行方式与从C#中打开参数的方式相同。 但是,通话结束后清理工作更简单。 如果使用了缓冲区,则只需将缓冲区中的内存清零,然后再将其移交给GlobalFree。

GlobalAllocfunction(Windows)@ MSDN
GlobalFreefunction(Windows)@ MSDN

当消息到达C#端时,使用Marshal.PtrToStructure()将IntPtr映射到.NET结构。

Marshal.PtrToStructure Method @ MSDN

您必须分配GUID并使用MarshalAs属性。 .NET COM Interop处理转换。 与class级没有太大的不同。 这一系列post说明了您需要做的事情。

通过在.NET上使用P / Invoke并在VB6中导入CopyMemory,你可以使这个工作,但这是一个很大的维护灾难我建议从这样的任何东西运行。