Reputation: 668
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?
Upvotes: 1
Views: 1184
Reputation: 11
you need to implement Invoke in your C++ client code, something like...
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);
virtual HRESULT __stdcall raw_FloatPropertyChanging(float x, VARIANT_BOOL * cancel) { return -1; }
HRESULT __stdcall FloatPropertyChanging(float NewValue, bool *Cancel);
};
HRESULT __stdcall EventHandler::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
// The riid parameter is always supposed to be IID_NULL
if (riid != IID_NULL)
return DISP_E_UNKNOWNINTERFACE;
if (pdispparams) //DISPID dispIdMember
{
float NewValue;
bool * pCancel;
switch (dispidMember) {
case 1:
NewValue = pdispparams->rgvarg[1].fltVal;
pCancel = (bool *)pdispparams->rgvarg[0].pboolVal;
return FloatPropertyChanging(NewValue, pCancel);
default:
return E_NOTIMPL;
}
} else return E_NOTIMPL;
}
Upvotes: 1
Reputation: 283713
The sample documentation specifically calls for the event interface to be dispatch-only:
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.
Upvotes: 1