簡體   English   中英

如何將結構從C#發送到VB6,以及從​​VB6發送到C#?

[英]How do I send a struct from C# to VB6, and from VB6 to C#?

我需要將結構從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

您必須分配GUID並使用MarshalAs屬性。 .NET COM Interop處理翻譯。 沒有什么區別比一個類。 本系列文章說明了您需要做什么。

通過在.NET上使用P / Invoke並在VB6中導入CopyMemory,您可以完成這項工作,但這對維護造成了很大的影響,我建議您使用類似的方法運行。

警告:

在我開始之前,人們可能會感興趣地注意到其他問題的內容。

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

正如這里和您在其他文章中提到的,您應該重新考慮嘗試使這項工作有效。 特別是,即使您對這里的技術很滿意,您的同事或其他可能需要維護您的代碼的人也將度過非常糟糕的時光。 請注意,在VB6-中調試過程也非常艱巨,請經常保存並使用大量斷點! 如果您的代碼中有錯誤,可能會導致大量崩潰。

此外,PostMessage不應與此技術一起使用。 這將需要更多的清理工作。

解:

我附帶了一個示例,該示例可以傳遞僅包含字符串和整數類型的結構。 進行這項工作需要大量的活動部件。 我們將深入介紹從C#到VB的過程,因為這比較棘手。 一旦您知道該怎么做,反向操作就不那么困難了。

首先,在C#端,您應該聲明您的結構。 在C#中打包結構實際上還不錯。 這是一個COM可見的C#示例類,演示了如何包裝結構。 關鍵是在呼叫的相對側使用Marshal.StructureToPtr和Marshal.DestroyStructure。 根據所涉及的類型,您甚至都不必編寫代碼來映射類型。 使用MarshalAs屬性來標記VB6的正確映射。 MarshalAs中使用的枚舉中的大多數項目對應於VARIANT和COM自動化中使用的不同變量類型。

HGlobal支持此處使用的緩沖區,呼叫結束后需要釋放該緩沖區。 在這里也可以使用GCHandle,這也需要類似的清理。

MSDN上的MarshalAsAttribute類

MSDN上的Marshal.StructureToPtr方法
MSDN上的Marshal.DestroyStructure方法
元帥.AllocHGlobal方法@ MSDN
MSDN上的Marshal.FreeHGlobal方法

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一側的結構。 有關此功能的詳細信息,請參見以下鏈接。

如何在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。 盡管我不確定VB6中的DateTime,但您在UDT中指定的其他字段也具有等效項。

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函數(Windows)@ MSDN
GlobalFree函數(Windows)@ MSDN

當消息到達C#端時,使用Marshal.PtrToStructure()將IntPtr映射到.NET結構。

元帥.PtrToStructure方法@ MSDN

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM