Warning:
Before you begin, people may be interested in replacing items from your other question.
REF: How to send / receive Windows messages from VB6 and C #?
As mentioned here and in your other post, you should really reconsider trying to do this job. In particular, even if you have everything in order with the equipment here, your colleagues or other people who may have to support your code will be very hard. Please note that the debugging process is also very complicated in VB6-frequent saving and using many breakpoints! Expect a lot of crashes if your code has errors.
In addition, PostMessage should not be used with this technique. This will require much more cleaning.
Decision:
I attached a sample that can pass a structure containing only the type string and integer. To do this job, many moving parts are required. We will take a closer look at the transition from C # to VB, as it is more complicated. The converse is not as difficult as you know how to do it.
First, on the C # side, you must declare your structure. The packaging structure is actually pretty good in C #. Here is a C # sample class, which is COM visible, that demonstrates how to wrap a structure. The key is to use Marshal.StructureToPtr and Marshal.DestroyStructure on opposite sides of your call. Depending on the types used, you may not even need to write code for map types. Use the MarshalAs attribute to mark the correct mappings for VB6. Most of the elements in the enumeration used in MarshalAs correspond to the various types of variables used in VARIANT and COM automation.
The buffer used here is maintained by HGlobal, which must be freed after the call is completed. You can also use GCHandle here, which also requires similar cleaning.
MarshalAsAttribute Class @MSDN
Marshal.StructureToPtr @MSDN Method
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); } }
On the VB6 side, you need to configure a mechanism to intercept messages. Since the details are described in your other question and elsewhere, I will skip the topic of the subclass.
To expand the structure on the VB6 side, you will need to do this for each field, since there is no mechanism easily accessible to dereference the pointer value and translate it into the structure. Fortunately, you can expect field members to be aligned at 4-byte boundaries in VB6 unless you specify otherwise in C #. This allows us to work field by field, display elements from one view to another.
First, some module code to do all the support work. Here are the features and notes.
The TestInfo type is a mirror definition of the structure used on both sides.
CopyMemory is a win32 function that can be used to copy bytes.
ZeroMemory is a win32 function that resets memory to zero bytes.
In addition to these elements, we use the undocumented VarPtr () function in VB6 to get the address of the elements. We can use this for indexing in the structure on the VB6 side. For more information about this feature, see the following link.
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
Please note that this solution requires the cooperation of the sender and the recipient. Since we do not want to free the row field twice, we free the copy made on the VB6 side before returning control. This is undefined, which will happen here if you try to assign a new value to the field members, so avoid editing the fields in the structure.
The display fields of UnmanagedType.BStr in C # are directly similar to the line in VB6.
UnmanagedType.I4 maps Integer and Long to VB6. Other fields that you specified in UDT also have equivalents, although I'm not sure about DateTime in VB6.
The rest of the VB6 application (form source code) is simple.
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
Now, sending the structure from VB6 to C #, you need to do the opposite. For some simple structures, you can even send only the address of the structure itself. If you need control in order, you can get a suitable buffer memory using GlobalAlloc, and then release it using GlobalFree. Permanent copies can be performed in the same way that parameters were deployed with C # for each field. However, after the call, the cleaning procedure becomes simpler. If you used a buffer, you only need to reset the memory in the buffer before passing it to GlobalFree.
GlobalAlloc Function (Windows) @MSDN
GlobalFree Function (Windows) @MSDN
When the message arrives at the C # side, use Marshal.PtrToStructure () to map IntPtr to the .NET structure.
Marshal.PtrToStructure Method @MSDN