Obtaining a "CoClass" from interface in .NET for the purposes of sinking events.

  • Thread starter Thread starter shehrzad
  • Start date Start date
S

shehrzad

I am trying to implement an COM object (in ATL), that can be used in a
variety of contexts (Win32 console apps, MFC dialog apps, WinForms C#
or MC++ apps). This ActiveX control is a logger component writes a
message to a log file, calls OutputDebugString, and then fires an
event. The way I have my code structured is that I have an unmanaged
static library that wraps this COM object around a singleton:

class LoggerSingleton { // lots of implementation details omitted
public:
LoggerSingleton *getInstance();
ILogger *getActiveX() { return m_pInterface; }
void printf(char *fmt, ...); // calls m_pInterface->writeMsg(...)
private:
static LoggerSingleton *m_pInstance;
interface ILogger *m_pInterface; // from CoCreateInstance(...)
};

My idea was that in my Managed C++ WinForms app, I'll be able to log
messages by simply calling LoggerSingleton::getInstance()->printf(),
and likewise all of my unmanaged threads that are implented in static
libraries would do the same. Moreover, my thought was to use COM
interop to sink the events fired by the underlying control, so for
example whenever I sink a "new log message" event I can display it in a
Rich Text Edit box. The key thing here is I want to use interop
facilities so that it is .NET-esque, namely I want to use delegates to
set up my event sinks, as opposed to dropping down and using connection
points and such.

My problem is that from the .NET world I am having trouble creating a
CoClass object (defined in the Interop assembly) from an interface
pointer. The COM object has already been created by a thread in
unmanaged code by the time my WinForms app is ready to += its delegate.
It's relatively straightforward for me to get a managed reference to
an interface from an existing COM interface:

using System::Runtime::InteropServices;
Object ^iuobj =
Marshal::GetObjectForIUnknown(
(IntPtr)LoggerSingleton::getInstance()->getActiveX() );
// LoggerActiveXLib is generated for me when I
// "add reference" to my ActiveX object
// and the type library is imported into the project
LoggerActiveXLib::ILogger ^pLogger =
Marshal::CreateWrapperOfType(iuobj,
Type::GetTpyeFromCLSID(Guid(...)));

So this is all fine & dandy, my Managed C++ can invoke
pLogger->writeMsg(...) while my native threads are invoking the same
method (through that singleton). My problem is how do I sink the event
that the COM object exposes (through its CoClass) if I just have a
managed ref to a COM interface?

I can instantiate the "interop CoClass" very easily by doing something
like:

LoggerActiveXLib::LoggerClass ^pLoggerCoClass = gcnew
LoggerActiveXLib::LoggerClass();

again of course where LoggerClass is generated for me. I can then sink
events via

pLoggerCoClass->someEvent += gcnew
LoggerActiveXLib::_ILoggerEvents_EventHandler(...);

But then this is a whole new instantation of my ActiveX logger, and
hence when my unmanaged code writes log messages I won't get
notification of them. Is there some way for me to get a managed
reference to a CoClass Interop object from a managed reference to it's
COM interface Interop object? I attempted a bunch of things within the
Marshal class, to no avail, for example:

#using LoggerActiveXLib;

LoggerClass ^pLoggerCoClass = (LoggerClass ^)
Marshal::CreateWrapperOfType(iuobj,
Type::GetTpyeFromCLSID(Guid(...)));

and

LoggerClass ^pLoggerCoClass = (LoggerClass ^pLoggerCoClass)

Marshal::GetTypedObjectForIUnknown((IntPtr)LoggerSingleton::getInstance()->getActiveX()

Type::GetTpyeFromCLSID(Guid(...)));

In each of these cases, I got an exception with something to the effect
that "Unable to case COM object of type 'System.__ComObject' to class
type ..."

Failing miserably, I then figured that ok, since I can't seem to get
the CoClass object (which I just need so I can set up my event sinks,
implemented cleanly using .NET delegates) I'll just instantiate a
CoClass object and pass it down to this unmanaged code, and then the
unmanaged code will be none the wiser and I can get my delegates in the
managed code. I added a "setter" in my singleton, so from .NET land I
could do something like the following:

// instantiate CoClass
// add delegates to sink ActiveX events

IntPtr pInterface = Marshal::GetIunknownForObject(pLoggerCoClass );
LoggerSingleton::getInstance()->setActiveX( pInterface );

Surprisingly enough, this almost worked, except for one problem.
Now in the ATL connection point method, where the event is actually
fired, ->Invoke is coming back with an HRESULT of "Exception Occurred",
whenever my unmanaged threads write a log message. The actual writing
of a log message, i.e. invocation of the method through the COM
interface passed down from the Managed C++ app, works, but the event
throwing doesn't. Everything works if the Windows Form Managed C++
code writes a message.

This made me think it was some threading apartment thing, since it
works from one context but not the other. I did make sure I was
calling CoInitializeEx(NULL, COINIT,_MULTITHREADED) in all of my
threads, yet I still remain stymied.

In conclusion, if I could somehow set up ActiveX event sinks using
delegates given just an interface managed reference I'd be set (I don't
think this possible).

Barring that, how can I get a managed ref to the CoClass, because that
would also work out for me.

Lastly, how come if I explictly instantiate a CoClass object through
..NET interop and pass it down to the unmanaged world, unmanaged code
cannot fire events while managed code can? Do I perhaps need to "pin"
the interface pointer before it crosses the Managed to Native boundary?

Any help anyone can provide would be *most* appreciated.

Shehrzad Qureshi
 
Hi,

I am really not sure why you need a CoClass instance to subscribe to events.
In the pure COM world, I recall one had to query for the
IConnectionPointContainer interface (and any existing interface pointer for
the object instance could be used to query for that interface).

Now, in the managed world, you can probably use the
UCOMIConnectionPointContainer wrapper.

I also remember COM events are somehow automatically wrapped with delegates
in interop assemblies, and I still can't figure out why you need a reference
to the CoClass itself.
 
Hi Dmytro,

Thanks for the prompt reply - I wonder if I've confused you with my
choice of terms. My interop assembly gives me a couple of types in
it's namespace, namely this ILogger and LoggerClass. I assumed that
LoggerClass is the CoClass.

Anyways, I can marshal my unmanaged interface pointer to ^ ILogger (the
managed ref to the interface), however with this object reference there
are NO events. I wasn't aware of UCOMIConnectionPointContainer, and I
will investigate that.

Now if I gcnew a LoggerClass, then indeed, I get the same properties,
methods, as ILogger, but now I also get the delegates for the ActiveX
events. So I need to somehow marshal my native interface pointer to
this object, or figure out how to get access to the delegates from
ILogger. I get an invalid cast exception if I attempt to marshal the
native pointer to a handle to LoggerClass.

I hope this explains things.

-SQ
 
Hi,
I assumed that LoggerClass is the CoClass.

Your assumption is correct.
Now if I gcnew a LoggerClass, then indeed, I get the same properties,
methods, as ILogger, but now I also get the delegates for the ActiveX
events. So I need to somehow marshal my native interface pointer to
this object, or figure out how to get access to the delegates from
ILogger. I get an invalid cast exception if I attempt to marshal the
native pointer to a handle to LoggerClass.

Unlike in .NET, COM events are not a part of a CoClass' interface. There is
a notion of so-called "source interface", but its counterpart in the managed
world is rather a delegate declaration. The bottom line is that you cannot
marshal the delegates available in the CoClass - just because there's an
invisible layer of wrappers connecting the delegates and the corresponding
connection points.

What you can do however is implementing the Singleton pattern at the class
factory level (where it actually belongs in - the CoClass itself shouldn't
govern its own creation IMO). Thus, you can return a pointer to the CoClass
itself (and not to the ILogger interface), so you won't have any hassle at
the .NET level.
 
Hi Dmytro,

Thanks again for all your help. I have solved the problem, and will
post my solution at the end of this email. But 1st, it looks like
there are other folks who have had the same problem that I did, but did
a better job of articulating the problem. A few links follow (watch
for word-wrap):

http://www.dotnet247.com/247reference/msgs/1/8726.aspx
http://www.dotnet247.com/247reference/msgs/10/50024.aspx
http://groups.google.com/group/micr...terface+pointer&rnum=2&hl=en#dc0e776fd2c72bc0
http://groups.google.com/group/micr...jectForIUnknown&rnum=1&hl=en#cef56996ce6137f5
http://www.informit.com/articles/article.asp?p=25922&seqNum=5&rl=1

Let me restate the problem. I have an pointer to an ActiveX interface
coming to me from a piece of unmanaged code. While I could very well
use connection points and what not to sink the ActiveX events, I want
to use delegates. If I add a reference to the ActiveX control in
question into my Managed C++ Windows Forms application, the interop
assembly contains the class I need. Somehow I need to marshal the
unmanaged interface pointer into the COM "class object", in order to
use delegates to sink the events. The following code accomplishes
this:

// assume pLogger comes from unmanaged-land
interface ILogger *pLogger = ... ;

using namespace System::Runtime::InteropServices;
using namespace LoggerActiveXLib;
Object ^iuobj = Marshal::GetObjectForIUnknown( (IntPtr)pLogger );
//
// LoggerActiveXLib::ILogger is automatically generated when I import
// the reference into my .NET project. It is hosted in the interop
// assembly for the ActiveX ctrl.
//
ILogger ^pILogger;
// 1st convert the IUnknown-pointer to a temporary COM-object.
System::Type ^type = Type::GetTypeFromCLSID(Guid("..."));
pILogger = (ILogger ^)Marshal::CreateWrapperOfType(iuobj, type);
//
// LoggerActiveXLib::LoggerClass is what I really need. If all I have
// is pILogger, then I can merely invoke methods and properties.
// For sinking events without using connection points and
// .Advise/.UnAdvise old-style ATL programming, I need to marshal
// pILogger into a handle to a LoggerClass (the type of which is also
// hosted in the interop assembly)
//
LoggerClass ^pTemp = gcnew LoggerClass();
type = Type::GetTypeFromHandle( Type::GetTypeHandle(pTemp) );
// now create the .NET COM wrapper
m_pLogger =
(LoggerClass ^) Marshal::CreateWrapperOfType(m_pILogger, type);
// now I can sink events "the .NET way"
m_pLogger->_ILoggerEvents_Event_LogMsg += gcnew ...
 
Back
Top