【问题标题】:How do I send a struct from C# to VB6, and from VB6 to C#?如何将结构从 C# 发送到 VB6,从 VB6 发送到 C#?
【发布时间】:2009-11-24 14:49:10
【问题描述】:

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

我能够使用 PostMessage 来回发送基本整数(使用 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

【问题讨论】:

  • 你能展示一个你在VB6中使用的结构的例子吗?它在一个dll中吗?请提供更多信息。
  • 您在任务的哪一部分遇到了问题? C# 中的 Windows 消息或 VB6 中的 Windows 消息? “Windows Messaging”是必须的还是替代解决方案(例如通过 C# dll 进行互操作)?
  • 为问题添加了更多细节,见上文。我可以来回发送基本的 PostMessage,但发送结构化数据时遇到问题。
  • 顺便说一句,没有人问过非常重要的问题:这是 InterProcessCommunication(2 个或多个进程通信)还是 SingleProcessCommunication(所有 VB6 代码和 C# 代码都在单个进程中)?如果以后(SingleProcessCommunication)使用 COM!但是,如果这是 InterProcessCommunication,您需要 MemoryMappedFile 在进程之间共享数据以及何时读取/写入数据的同步机制(并且不确定 VB6 示例中是否有 MemoryMappedFile,但是有它的 c# 代码。所以你可以编写 c# COM完成所有这些并在 VB6 中引用它)。

标签: c# vb6 struct


【解决方案1】:

警告:

在我开始之前,人们可能会对您的其他问题中的项目感兴趣。

REF: How do I send/receive windows messages from VB6 and C#?

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

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

解决方案:

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

首先,在 C# 方面,您应该声明您的结构。在 C# 中封装结构实际上还不错。这是一个 COM 可见的 C# 示例类,它演示了如何包装结构。关键是在通话的对面使用 Marshal.StructureToPtr 和 Marshal.DestroyStructure。根据所涉及的类型,您甚至可能不必编写代码来映射类型。使用 MarshalAs 属性来标记 VB6 的正确映射。 MarshalAs 中使用的枚举中的大多数项对应于 VARIANT 和 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);
                // 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 端展开结构,您需要按字段执行此操作,因为没有现成的机制可以取消引用指针值并将其转换为结构。幸运的是,您可以期望在 VB6 中字段成员在 4 字节边界上对齐,前提是您没有在 C# 中另外指定。这允许我们逐个字段地工作,将项目从一种表示映射到另一种表示。

首先,一些模块代码来完成所有的支持工作。以下是功能和注意事项。

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中的字符串。
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。

GlobalAlloc Function (Windows) @ MSDN
GlobalFree Function (Windows) @ MSDN

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

Marshal.PtrToStructure Method @ MSDN

【讨论】:

【解决方案2】:

您必须分配 GUID 并使用 MarshalAs 属性。 .NET COM Interop 处理翻译。和一堂课没有太大区别。 This series 的帖子说明了您需要做什么。

【讨论】:

  • 不必通过 COM 完成。您可以使用常规的“win32 pinvoke”样式互操作。这仍然需要 MarshalAs 东西,但不需要 GUID 或任何 COM 东西。
  • @Cheeso - 不,您不能使用常规的“win32 pinvoke”与 VB6 通信,您必须使用 COM。
  • 我可以使用 PostMessage 将基本消息从 c# 发送到 vb6,但现在我只知道如何发送整数。
  • 当然,但如果你想发送丰富的数据(类和结构),你应该使用 com. VB6 在这方面并不宽容。
  • 我专注于倒数第二句话“什么是最简单的......”
【解决方案3】:

通过在 .NET 上使用 P/Invoke 并在 VB6 中导入 CopyMemory,您可以完成这项工作,但我建议从这样的任何地方运行这样的维护灾难。

【讨论】:

    猜你喜欢
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    相关资源
    最近更新 更多