代码之家  ›  专栏  ›  技术社区  ›  cabgef

如何将结构从c_发送到vb6,从vb6发送到c_?

  •  0
  • cabgef  · 技术社区  · 15 年前

    我需要将一个结构从C_发送到一个vb6应用程序,修改vb6中的数据,并通过Windows消息发送返回结果。我该怎么做?

    我可以在邮件中来回发送基本的ints(使用c中的dllimport并在Windows消息中注册vb6应用程序),但需要发送更结构化的数据,包括字符串、ints和小数。

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

    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
    
    3 回复  |  直到 15 年前
        1
  •  1
  •   Community CDub    7 年前

    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);
                    // 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字节边界上对齐。这允许我们逐个字段地工作,将项目从一个表示映射到另一个表示。

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

    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

        2
  •  1
  •   RS Conley    15 年前

    您必须分配guid并使用marshalas属性。.NET COM互操作处理转换。和一个班没什么不同。 This series of posts说明了您需要做什么。

        3
  •  1
  •   Joshua    15 年前

    通过在.NET上使用p/invoke并在vb6中导入copymemory,您可以做到这一点,但这是一个非常严重的维护灾难,我建议您从类似的任何地方运行。