How do I send/receive windows messages between VB6 and c#?

13,770

Solution 1

Before I start, I'd like to say that I concur with MarkJ. COM Interop will make your life much easier and will not require you to do as much work.

SendMessage is the preferred way to call one side or the other via Windows Message handlers. PostMessage is tough to use with complex types, as the lifetime of data associated with a windows message in both .NET and VB6 is difficult to manage while the message is queued, and completion of the message is unknown unless you implement some form of callback mechanism.

Anyhow, sending windows messages from anywhere to a C# window merely requires that you know the HWND of the C# window that is to receive the message. Your snippet looks to be correct as a handler, except that the switch statement should check against the Msg parameter first.

protected override void WndProc(ref Message m)
{

    int _iWParam = (int)m.WParam;
    int _iLParam = (int)m.LParam;
    switch ((ECGCardioCard.APIMessage)m.Msg)
    {
            // handling code goes here
    }
    base.WndProc(ref m);
}

Retrieving a window handle from a C# Form, Window, or Control can be done via the .Handle property.

Control.Handle Property @ MSDN

We'll assume here that you have some way of transferring the window handle from C# to VB6.

From VB6, the signature of the SendMessage window is the following:

Private Declare Function SendMessage Lib "USER32.DLL" _
    (ByVal hWnd As Long, ByVal uMsg As Long, _
    ByVal wParam As Long, ByVal lParam As Long) As Long

To call it, you would do something like the following. For brevity, uMsg is WM_APP (32768), wParam/lParam are 0:

Dim retval As Long
retval = SendMessage(hWnd, 32768, 0, 0)

Likewise, sending a message from C# is similar. To get the HWND of a window in VB6, use the .hWnd property of the window in VB6 that should receive the message.

As it appears that you are using your own set of message identifiers, there are extra steps to handle custom message identifiers in VB6. Most people handle this by subclassing a form window, and using the subclass procedure to filter those message. I've included sample code to demonstrate C# to VB6 since handling custom messages is trickier in VB6.

Here is the source code for the pair of test programs, a C# library and a VB6 Forms Project. The C# library should be configured with 'Register for COM Interop' and 'Make Assembly COM-Visible' in the Project settings.

First, the C# library. This library contains a single COM component that will be visible to VB6 as the type 'CSMessageLibrary.TestSenderSimple'. Note that you need to include a P/Invoke signature (like the VB6 method) for SendMessage.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace CSMessageLibrary
{
    [ComVisible(true)]
    public interface ITestSenderSimple
    {
        // NOTE: Can't use IntPtr because it isn't VB6-compatible
        int hostwindow { get; set;}
        void DoTest(int number);
    }

    [ComVisible(true)]
    public class TestSenderSimple : ITestSenderSimple
    {
        public TestSenderSimple()
        {
            m_HostWindow = IntPtr.Zero;
            m_count = 0;
        }

        IntPtr m_HostWindow;
        int m_count;

        #region ITestSenderSimple Members
        public int hostwindow 
        {
            get { return (int)m_HostWindow; } 
            set { m_HostWindow = (IntPtr)value; } 
        }

        public void DoTest(int number)
        {
            m_count++;

            // WM_APP is 0x8000 (32768 decimal)
            IntPtr retval = SendMessage(
                m_HostWindow, 0x8000, (IntPtr)m_count, (IntPtr)number);
        }
        #endregion

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        extern public static IntPtr SendMessage(
          IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);
    }
}

Now, on the VB6 side, you will need to add support to subclass a window. Aside from better solutions that can be applied per-window, we'll just go with something that shows how to setup a single window.

First, to run this sample, make sure you have built the C# application and properly registered it with COM. Then add a reference from VB6 to the .tlb file that is alongside the C# output. You'll find this in the bin/Debug or bin/Release directory under the C# project.

The following code should be placed into a Module. In my test project I used a module called 'Module1'. The following definitions should be noted in this Module.

WM_APP - Used as a custom message identifier that will be interference-free.
GWL_WNDPROC - Constant that is used for SetWindowLong to request modification to the window handler.
SetWindowLong - Win32 function that can modify special attributes on windows.
CallWindowProc - Win32 function that can relay windows messages to a designated window handler (function).
SubclassWindow - Module function to setup subclassing for a designated window.
UnsubclassWindow - Module function to tear down subclassing for a designated window.
SubWndProc - Module function that will be inserted via subclassing, to allow us to intercept custom windows messages.

Public Const WM_APP As Long = 32768
Private Const GWL_WNDPROC = (-4)
Private procOld As Long

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

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 strInfo As String
            strInfo = "wParam: " & CStr(wParam) & vbCrLf & "lParam: " & CStr(lParam)

            Call MsgBox(strInfo, vbOKOnly, "WM_APP Received!")

            SubWndProc = True
            Exit Function
        End If
    End If

    SubWndProc = CallWindowProc(procOld, hWnd, iMsg, wParam, lParam)
End Function

In the test form, I've wired up an instance of the test C# object as a member of the form. The form includes a button whose id is 'Command1'. The subclass is setup when the form loads, and then removed when the form is closed.

Dim CSharpClient As New CSMessageLibrary.TestSenderSimple

Private Sub Command1_Click()
    CSharpClient.DoTest (42)
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

Sending numeric arguments that fit in 4 bytes is trivial, either as the wParam or lParam. However, sending complex types and strings is much tougher. I see that you've created a separate question for that, so I will provide answers to that over there.

REF: How do I send a struct from C# to VB6, and from VB6 to C#?

Solution 2

To send in VB6 you need to use an API call (SendMessage or PostMessage). To receive in VB6 you need to use subclassing (complicated - here's the best way I know).

Have you considered using COM Interop instead? It's a much easier way to communicate between VB6 and C# than windows messages.

Solution 3

Use the PostMessage Windows API function.

At the beginning of your class:

[DllImport("User32.dll", EntryPoint="PostMessage")]
private static extern int PostMessage(int hWnd, int Msg, int wParam, int lParam);

const int WM_USER = 0x0400;
const int CM_MARK = WM_USER + 1;

Then handle the message by overriding the class's WndProc.

protected override void WndProc(ref Message m)
{
  if (m.Msg == CM_MARK) {
    if (this.ActiveControl is TextBox) {
      ((TextBox)this.ActiveControl).SelectAll();
    }
  }
  base.WndProc(ref m);
} // end sub.

Then in your Enter event:

private void txtMedia_Enter(object sender, EventArgs e)
{
  PostMessage(Handle.ToInt32(), CM_MARK, 0, 0);
} // end sub.

This works because you force your custom processing to occur after Windows does its default processing of the Enter event and it's associated mouse-handling. You put your request on the message queue and it is handled in turn, in the WndProc event. When your event is called, you make sure the current window is a text box and select it if it is.

Share:
13,770
cabgef
Author by

cabgef

Updated on June 15, 2022

Comments

  • cabgef
    cabgef almost 2 years

    I know I can receive messages with the code below in c#, how do I send to vb6, and receive in vb6, and send from vb6?

        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
        protected override void WndProc(ref Message m)
        {
    
            int _iWParam = (int)m.WParam;
            int _iLParam = (int)m.LParam;
            switch ((ECGCardioCard.APIMessage)m.WParam)
            {
                // handling code goes here
            }
            base.WndProc(ref m);
        }
    
  • MarkJ
    MarkJ over 14 years
    +1. There's a more "objecty" way to do the VB6 subclassing though visualstudiomagazine.com/articles/2009/07/16/…
  • meklarian
    meklarian over 14 years
    I went with this method just to keep the sample shorter. The technique at that link is definitely superior, though.
  • ufo
    ufo almost 10 years
    Why I receive the compile error "Invalid use of AddressOf operator"? I don't use a module, but the Form1 directly. Is this allowed?
  • meklarian
    meklarian almost 10 years
    @ufo It has been a while since I've worked with VB, but I believe you can't avoid the use of Module because a member function on a form won't have the proper function signature. There will be an implicit extra argument in such a case (just like how non-static class member functions have this implicitly declared in C++).