|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Obtaining a "CoClass" from interface in .NET for the purposes of sinking events.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. <shehr***@gmail.com> wrote in message Show quote news:1142582610.366767.225220@e56g2000cwe.googlegroups.com... >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 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, Unlike in .NET, COM events are not a part of a CoClass' interface. There is > 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. 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. <shehr***@gmail.com> wrote in message Show quote news:1142611413.446602.113730@j33g2000cwa.googlegroups.com... > 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 again,
I've just come across this KB article that can also shed some light on the COM events magic: http://support.microsoft.com/kb/810228/en-us <shehr***@gmail.com> wrote in message Show quote news:1142611413.446602.113730@j33g2000cwa.googlegroups.com... > 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 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/microsoft.public.dotnet.framework.interop/browse_thread/thread/33cd07e07019da6c/dc0e776fd2c72bc0?lnk=st&q=unmanaged+COM+interface+pointer&rnum=2&hl=en#dc0e776fd2c72bc0 http://groups.google.com/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/c0b66009ebfc5fd2/cef56996ce6137f5?lnk=st&q=GetTypedObjectForIUnknown&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 ... |
|||||||||||||||||||||||