How to Implement and Register to a COM Callback

2019-08-07 00:59发布

问题:

I'm trying to create a COM interface that allows for Callback from the COM to the calling code. I can't seem to find any proper explanation for this in C#.

I got a normal COM interface working, but I don't know how to properly register and set up the callback part.

I can't find any information on how to do this properly in C#, as most google search terms I can come up with that include "com" only lead to .com sites, and don't include actual com interfaces when talking about callbacks and events, they only include the default non-com ones. Any help would be appreciated, even if it's just pointing out to a source where I can find better, more helpful information.

My Callback Interface looks like this:

using System;
using System.Runtime.InteropServices;

namespace ComAdapter
{
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("7f176b75-7b66-463a-94ab-493943792f88")]
    public interface INet40Callback
    {
        [PreserveSig]
        void Callback(string data);
    }
}

The Callback class I tried to look like this:

using net40lib;
using System;
using System.Runtime.InteropServices;

namespace ComAdapter
{
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(INet40Callback))]
    [Guid("023e4ad9-77d1-461e-af89-4cfc2b53959c")]
    public class Net40Callback : INet40CallbackRegistration
    {
        [ComVisible(false)]
        public delegate void MyCallbackDelegate(string data);

        public event MyCallbackDelegate Callback;

        public INet40Callback callbackObject;

        public Net40Callback()
        {
            Net40Class.Instance.VersionSent += Instance_VersionSent;
        }

        void Instance_VersionSent(object sender, EventArgs e)
        {
            if (callbackObject != null)
            {
                callbackObject.Callback("blah");
            }

            if (Callback != null)
            {
                Console.WriteLine("Instance_VersionSent Sending Callback");
                Callback("blah");
            }
        }

        public void RegisterCallback(INet40Callback reg)
        {
            if (reg is INet40Callback)
            {
                callbackObject = reg as INet40Callback;
            }
        }
    }
}

The calling application so far looks like this:

using ComAdapter;
using System;
using System.Runtime.InteropServices;

namespace net20lib
{
    class Net40Callback : INet40Callback
    {
        [PreserveSig] 
        public void Callback(string data)
        {
            Console.WriteLine("Callback Called with data {0}", data);
        }
    }
}

Registration:

using System;
using System.Runtime.InteropServices;

namespace ComAdapter
{
    [ComVisible(true)]
    [Guid("3b2ab267-9e9a-47be-ab0a-f55db10a971e")]
    public interface INet40CallbackRegistration
    {
        void RegisterCallback(INet40Callback callback);
    }
}

The Call from the Test Class: The implementation of the interface:

internal class Net40Callback : INet40Callback
{
  [PreserveSig]
  public void Callback(string data)
  {
      Console.WriteLine("Callback Called with data {0}", data);
  }
}

The Code for registration:

INet40Callback callback = new Net40Callback();

Type myCallbackType = Type.GetTypeFromProgID("ComAdapter.Net40Callback");
object myCallbackInstance = Activator.CreateInstance(myCallbackType);
INet40CallbackRegistration registration = (INet40CallbackRegistration)myCallbackInstance;
registration.RegisterCallback(callback);

回答1:

You really shouldn't be testing C# COM server from C# COM client, this is not how it works when it's called from unmanaged code. Then you wouldn't need to keep a reference to INet40Callback, the event declarations should be enough for a COM client to hook up events via IConnectionPointContainer (using the type library generated by RegAsm). Unless you really want to support both options.

Anyway, to make your current code work, try adding [ComVisible(true)] to Net40Callback and [ComImport] to INet40Callback:

[ComImport]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("7f176b75-7b66-463a-94ab-493943792f88")]
public interface INet40Callback
{
    // ..
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]]
[ComDefaultInterface(typeof(INet40Callback))]
internal class Net40Callback : INet40Callback
{
    // ...
}

Updated, you don't need to change much on the C# side, besides removing the public INet40Callback callbackObject reference. However, you need an unmanaged COM client to test the events. It can be a plain C++ COM app, an ATL/MFC C++ app, a VB6 app, or even an HTML App (.HTA file with an <object> tag inside). Here's an example of the latter (untested). Copy it to test.hta run this file from Windows Explorer:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "">
<html>
<head>
    <title>Managed COM Object Test</title>

    <object id="managedObject"
        classid="clsid:023e4ad9-77d1-461e-af89-4cfc2b53959c"
        codebase="#version=0,0,0,0">
    </object>

    <script for="managedObject" event="Callback(data)" type="text/javascript">
        output.innerText = "Callback(" + data + ") fired";
    </script>
</head>

<body>
    <div id="output">&nbsp;</div>
</body>
</html>


标签: c# com callback