Search code examples
c#c++visual-studio-2012com

Using CSExeCOMServer how do I receive the event in VS C++


Starting with Microsoft's C# .NET CSExeCOMserver (out of proc EXE) example - I have the following:

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


namespace CSExeCOMServer
{
#region Interfaces

[Guid(CSSimpleObject.InterfaceId), ComVisible(true)]
public interface ICSSimpleObject
{
    #region Properties

    float FloatProperty { get; set; }

    #endregion

    #region Methods

    string HelloWorld();

    void GetProcessThreadID(out uint processId, out uint threadId);

    #endregion
}

[Guid(CSSimpleObject.EventsId), ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICSSimpleObjectEvents
{
    #region Events

    [DispId(1)]
    void FloatPropertyChanging(float NewValue, ref bool Cancel);

    #endregion
}

#endregion

[ComVisible(true)]
[Guid(CSSimpleObject.ClassId)]
[ClassInterface(ClassInterfaceType.None)]           // No ClassInterface
[ComSourceInterfaces(typeof(ICSSimpleObjectEvents))]
public class CSSimpleObject : ReferenceCountedObject, ICSSimpleObject
{
    #region COM Component Registration

    internal const string ClassId =
        "DB9935C1-19C5-4ed2-ADD2-9A57E19F53A3";
    internal const string InterfaceId =
        "941D219B-7601-4375-B68A-61E23A4C8425";
    internal const string EventsId =
        "014C067E-660D-4d20-9952-CD973CE50436";

    // These routines perform the additional COM registration needed by 
    // the service.

    [EditorBrowsable(EditorBrowsableState.Never)]
    [ComRegisterFunction()]
    public static void Register(Type t)
    {
        try
        {
            COMHelper.RegasmRegisterLocalServer(t);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message); // Log the error
            throw ex; // Re-throw the exception
        }
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    [ComUnregisterFunction()]
    public static void Unregister(Type t)
    {
        try
        {
            COMHelper.RegasmUnregisterLocalServer(t);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message); // Log the error
            throw ex; // Re-throw the exception
        }
    }

    #endregion

    #region Properties

    private float fField = 0;

    public float FloatProperty
    {
        get { return this.fField; }
        set
        {
            bool cancel = false;
            // Raise the event FloatPropertyChanging
            if (null != FloatPropertyChanging)
                FloatPropertyChanging(value, ref cancel);
            if (!cancel)
                this.fField = value;
        }
    }

    #endregion

    #region Methods

    public string HelloWorld()
    {
        return "HelloWorld";
    }

    public void GetProcessThreadID(out uint processId, out uint threadId)
    {
        processId = NativeMethod.GetCurrentProcessId();
        threadId = NativeMethod.GetCurrentThreadId();
    }

    #endregion

    #region Events

    [ComVisible(false)]
    public delegate void FloatPropertyChangingEventHandler(float NewValue, ref bool Cancel);
    public event FloatPropertyChangingEventHandler FloatPropertyChanging;

    #endregion
}

/// <summary>
/// Class factory for the class CSSimpleObject.
/// </summary>
internal class CSSimpleObjectClassFactory : IClassFactory
{
    public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, 
        out IntPtr ppvObject)
    {
        ppvObject = IntPtr.Zero;

        if (pUnkOuter != IntPtr.Zero)
        {
            // The pUnkOuter parameter was non-NULL and the object does 
            // not support aggregation.
            Marshal.ThrowExceptionForHR(COMNative.CLASS_E_NOAGGREGATION);
        }

        if (riid == new Guid(CSSimpleObject.ClassId) ||
            riid == new Guid(COMNative.IID_IDispatch) ||
            riid == new Guid(COMNative.IID_IUnknown))
        {
            // Create the instance of the .NET object
            ppvObject = Marshal.GetComInterfaceForObject(
                new CSSimpleObject(), typeof(ICSSimpleObject));
        }
        else
        {
            // The object that ppvObject points to does not support the 
            // interface identified by riid.
            Marshal.ThrowExceptionForHR(COMNative.E_NOINTERFACE);
        }

        return 0;   // S_OK
    }

    public int LockServer(bool fLock)
    {
        return 0;   // S_OK
    }
}

/// <summary>
/// Reference counted object base.
/// </summary>
[ComVisible(false)]
public class ReferenceCountedObject
{
    public ReferenceCountedObject()
    {
        // Increment the lock count of objects in the COM server.
        ExeCOMServer.Instance.Lock();
    }

    ~ReferenceCountedObject()
    {
        // Decrement the lock count of objects in the COM server.
        ExeCOMServer.Instance.Unlock();
    }
}
}

The above registers and I output a test.tlb file just fine - I have a COM client that uses the above COM Server in standard visual C++ as follows:

#include "stdafx.h"
#include <objbase.h>
#include <comutil.h>

#import <C:\Users\ndavis\Documents\Visual Studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\test.tlb> no_namespace named_guids

int _tmain(int argc, _TCHAR* argv[])
{

ULONG procID;
ULONG threadID;

//initialize COM for this thread
//HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
HRESULT hr = CoInitialize(NULL);

ICSSimpleObjectPtr pSimple_1;
hr = pSimple_1.CreateInstance(CLSID_CSSimpleObject, NULL, CLSCTX_LOCAL_SERVER);
_bstr_t hw_response = pSimple_1->HelloWorld();
_bstr_t testID = pSimple_1->GetProcessThreadID(&procID, &threadID);

pSimple_1->FloatProperty = 1.7f;

CoUninitialize();

    return 0;

}

All the above works: pSimple_1->HelloWorld(); returns correct response, pSimple_1->GetProcessThreadID(...) returns correct response; and pSimple_1->FloatProperty = 1.7f sets the FloatProperty correctly.

QUESTION: How do I receive the FloatPropertyChanging(float value, bool* cancel) event in my visual C++ client code above? (please no ATL).

Thanks

Additional information that has gotten me a little farther - I have added the following to my code:

Adding the following class to my client:

class EventHandler : public ICSSimpleObjectEvents
{

public:
EventHandler(void) { }
~EventHandler(void) { }

HRESULT __stdcall QueryInterface(const IID &, void **);
ULONG __stdcall AddRef(void) { return 1; }
ULONG __stdcall Release(void) { return 1; }

virtual HRESULT __stdcall GetTypeInfoCount(UINT * pTypeInfoCount) { return -1; }
virtual HRESULT __stdcall GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) { return -1; }
virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) { return -1; }
virtual HRESULT __stdcall Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { return -1; }

virtual HRESULT __stdcall raw_FloatPropertyChanging(float x, VARIANT_BOOL * cancel) { return -1; }

HRESULT __stdcall FloatPropertyChanging(float NewValue, bool *Cancel);
};

HRESULT __stdcall EventHandler::FloatPropertyChanging(float NewValue, bool *Cancel) {
printf("float value changing: NewValue = %f", NewValue);
return S_OK;
}

HRESULT __stdcall EventHandler::QueryInterface(const IID &iid, void **pp) {

if(iid == __uuidof(ICSSimpleObjectEvents) || iid == __uuidof(IUnknown) || iid == __uuidof(IDispatch))
{
    *pp = this;
    AddRef();
    return S_OK;
}
return E_NOINTERFACE;
}

And adding the following to get IConnectionPoint:

IUnknown *pUnk = NULL;

hr = CoCreateInstance(CLSID_CSSimpleObject, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **)&pUnk);

IConnectionPointContainerPtr pContainer;
IConnectionPointPtr pConnection;
hr = pUnk->QueryInterface(__uuidof(IConnectionPointContainer), (void**) &pContainer);
hr = pContainer->FindConnectionPoint(__uuidof(ICSSimpleObjectEvents), (IConnectionPoint**) &pConnection);


EventHandler* pSink = new EventHandler;

DWORD dwAdviseCookie;

hr = pConnection->Advise((IUnknown*)pSink, &dwAdviseCookie);

pSimple_1->FloatProperty = 1.7f;

Setting a break point in the C# .NET COM Server at:

    public float FloatProperty
    {
        get { return this.fField; }
        set
        {
            bool cancel = false;   <--- I set break point here
            // Raise the event FloatPropertyChanging
            if(FloatPropertyChanging != null)  <--- FloatPropertyChanging is null here?
                FloatPropertyChanging(value, ref cancel);
            if (!cancel)
                this.fField = value;
        }
    }

I see that FloatPropertyChanging is null so therefore FloatPropertyChanging(...) is never called? Anybody see what I am doing wrong?


Solution

  • The sample documentation specifically calls for the event interface to be dispatch-only:

    enter image description here

    But in your code you have made it a dual interface.

    [Guid(CSSimpleObject.EventsId), ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface ICSSimpleObjectEvents
    

    I also don't see an implementation of Invoke in your C++ client code. Well, I do. It's a badly broken one.

    I strongly suggest you try implementing the events through IDispatch because that's how the sample instructs to do it. You can probably keep the dual interface and use DispInvoke to automatically implement IDispatch::Invoke in terms of the v-table.

    At an absolute minimum, put a breakpoint inside your empty implementation of Invoke and see if that gets hit when the event is fired.

    Also, for example this sample (not .NET) also instructs that IDispatch should be used for the event source/sink.