Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# COM Late Binding Event

0.00/5 (No votes)
21 Apr 2010 2  
This article is a guide to building a .NET component, using it in a VB6 project at runtime using late binding, attaching its events and get a callback.

Introduction

This article is a guide to building a .NET component and using it in a VB6 project. There are many samples about this issue, so why did I write a new one? In my humble opinion, in other articles, the missing part is to attach its event at runtime. So in this article, we will build a .NET component, mark it as a COM visible component, use it at runtime in VB6 and attach to its events.

.NET - COM Interoperability

There is a great article about this issue written by KRISHNA PRASAD.N. actually, called .NET - COM Interoperability Calling .NET components from COM Client, .NET Marshalling , Interop marshaler, COM marshaler etc., issues are well explained.

You should also check C# COM if you need to remember how to create a C# COM Interop DLL step by step by RakeshGunijan.

Early vs Late Binding

Early binding and late binding have their own advantages and disadvantages. I won't dig into details. I mostly prefer late binding especially if I am using someone's API because late binding has the advantage of removing some of the version dependencies. If you have to use or distribute monthly updating API or application, believe me, late binding is a better option.

What is functionpointer

A function pointer is a convention that enables you to pass the address of a user-defined function as an argument to another function you've declared for use within your application.

What are the Options

We will build a COM interop, use it from VB6 at runtime (without adding any reference to our project) and attach to its events. Here are the options.

Option 1 - Using a Callback Function

Passing the address of our event as a function pointer from VB6 to C# and invoke it from C#. (Thanks to Misha Shneerson for solving this issue at MSDN Forums.)

public void AttachToAnEventMethod1(int address)
{
    CallBackFunction cb = (CallBackFunction)Marshal.GetDelegateForFunctionPointer
	(new IntPtr(address), typeof(CallBackFunction));
    SetCallbackMethod1(cb);
}

Option 2 - Using Reflection

Pass the consumer object and callback function's name from VB6 to C# and invoke the function from C# via reflection.

object objApp_Late = this.consumer;
Type objClassType = this.consumer.GetType();
object ret = objApp_Late.GetType().InvokeMember
	(this.callbackEventName, BindingFlags.InvokeMethod, null, objApp_Late, null);

Building C# COM

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("CSharpApi.CsApiCore")]
[ComSourceInterfaces(typeof(NotifyInterface))]
public class CsApiCore : _CsApiCore
{
    private string sessionID = "";
    public string SessionID
    {
        get { return sessionID; }
        set { sessionID = value; }
    }

    public event NotifyDelegate NotifyEvent;

    public CsApiCore()
    {
        this.SessionID = System.Guid.NewGuid().ToString().ToUpper();
    }

    public int Add(int a, int b)
    {
        return a + b;
    }

    public int MultiplyAndNotify(int a, int b)
    {
        for (int i = 0; i < 2; i++)
        {
            Thread.Sleep(500);
            notifyClients();
        }
        return a * b;
    }

    private void notifyClients()
    {
        if (NotifyEvent != null)
        {
            NotifyEvent();
        }

        if (this.consumer != null)
        {
            //method2
            object objApp_Late = this.consumer;
            Type objClassType = this.consumer.GetType();
            object ret = objApp_Late.GetType().InvokeMember(this.callbackEventName, 
		BindingFlags.InvokeMethod, null, objApp_Late, null);
            //method2
        }
        if (this.cb != null)
        {
            //method1
            cb.DynamicInvoke();
            //method1
        }
    }

    private object consumer = null;
    private string callbackEventName = "";

    public void AttachToAnEventMethod2(object consumerObject, string eventName)
    {
        this.consumer = consumerObject;
        this.callbackEventName = eventName;
    }

    public void DetachMethod2()
    {
        this.callbackEventName = "";
        this.consumer = null;
    }

    public void AttachToAnEventMethod1(int address)
    {
        CallBackFunction cb = (CallBackFunction)
	Marshal.GetDelegateForFunctionPointer(new IntPtr(address), 
		typeof(CallBackFunction));
        SetCallbackMethod1(cb);
    }
    public void DetachMethod1()
    {
        this.cb = null;
    }

    private Delegate cb = null;
    public void SetCallbackMethod1([MarshalAs(UnmanagedType.FunctionPtr)] 
	CallBackFunction callback)
    {
        this.cb = callback;
    }

We built a simple class for test purposes. Our aim is to attach to an event and when we execute MultiplyAndNotify method at runtime, get a feedback from VB6.

Using C# COM from VB6

We first need to connect to the C# COM using late binding.

Dim apiHandler  As Object
Dim obj As Object
Set obj = CreateObject("CSharpApi.CsApiCore")
If obj Is Nothing Then
    MsgBox ("Could not start Cs Api")
Else
    Set apiHandler = obj
End If

We may test if everything works fine. We'll use CallByName function. The CallByName() function is used to manipulate objects by either executing a method or setting the value of a property.

Dim sRet As String
sRet = CallByName(apiHandler, "SessionID", VbGet)
Msgbox sRet

What we need to do is to use method 1, attach to a C# event, execute a method (MultiplyAndNotify), and get a callback from VB6.

Dim functinPointerAddress As Long
functinPointerAddress = getAddressOfFunction(AddressOf Vb6NotifyEventOnModule)
    
Dim o As Object
Set o = Me
    
ret = CallByName(apiHandler, "AttachToAnEventMethod1", VbMethod, functinPointerAddress)
    
ret = CallByName(apiHandler, "MultiplyAndNotify", VbMethod, 3, 5)
    
Dim oRet
   
oRet = CallByName(apiHandler, "DetachMethod1", VbMethod)
    
MsgBox ret

Using the Code

Download the source files. You will see a CsApiTestSln.sln solution file. Open and build solution. CsApiTest is a main API project in this solution. You may use CsApiConsumer C# project to debug the main API. In CsApiTest\bin\Debug subdirectory, you'll see a VB6 project called CsApiVb6Test.vbp. This is a VB6 client made for using the main API.

Conclusion

Limitation of AddressOf function is: You can only retrieve the address of a function or sub (public or private) contained within a Standard VB Module. There's no way around this. So if you use method1, your callback function or event must be contained within a Standard VB Module (bas file).

If you decide to use method 2, you have to pass an object and an event name and invoke it from C#. In this case, you may use a class module or a form (as you can see in the VB6 sample project).

Decide on a method according to your needs.

References

History

  • 21st April, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here