Seperate COM implementation from a non-COM interface
-
Thursday, April 26, 2012 3:33 PM
None of the profilers I have tried to date have been able to successfully find memory leaks across COM boundries. I have tried bunches of them commericial and non commercial. So, we came up with this idea to seperate the implementation of the COM class into a regular C++ class that can be profiled and tested, and then just foward calls from the COM class to the Impl class. That way we use the Impl class in testing and the COM interface in production.
It sounded like a good idea, but there are classes in process that make calls to it. In this experiment those classes took a boost::shared_ptr<Impl> in their constructor. Well, in production I don't have access to that, the COM class owns it as a member.
I can't implement a method GetImpl and pass a boost::shared_ptr, because COM makes me use primitive types. It probably doesn't really make sense to pass an Impl anyway. Seems awefully hacky.
How can I make those classes that call the COM class able to use an instance of the COM class OR an isntance of its Impl?
In regular C++ I'd make an abstract interface and define those dependant classes to use that. I don't think there is a way for me to do that with COM is there? Can a COM class inherit from an abstract interface? And then, how do I get that interface from a _com_ptr?
Here is some code that is a quick mockup of the problem:
class IMyClass
{
public:
virtual HRESULT Foo() = 0;
};class MyClassImpl : public IMyClass
{
public:
HRESULT Foo() { std::cout << "I am the non-COM implementation"; return S_OK; }};
class MyCOMClass : public IMyClass
{
HRESULT Foo { return m_impl->Foo(); }private:
MyClassImpl * m_impl;
};
void MyDependentCaller(IMyClass * myClass)
{
myClass->Foo();
}int main()
{
IMyComClassPtr comClass.CreateInstance(....);// Need to get IMyClass * somehow??
MyDependentCaller->(thePtr);
return 0;
}- Edited by Christopher Pisz Thursday, April 26, 2012 6:06 PM
All Replies
-
Friday, April 27, 2012 9:09 AM
what is the error?
these codes can not be built successfully?
Please mark this reply as answer if it helps you! Thanks for your cooperation! Good Luck to you.
-
Friday, April 27, 2012 3:48 PM
what is the error?
these codes can not be built successfully?
Please mark this reply as answer if it helps you! Thanks for your cooperation! Good Luck to you.
You're kidding right?
Of course the code doesn't compile. This isn't a "I have an error" question. It is a "How do I do this" question.
-
Friday, April 27, 2012 4:47 PM
On 4/26/2012 11:33 AM, Christopher Pisz wrote:
None of the profilers I have tried to date have been able to successfully find memory leaks across COM boundries. I have tried bunches of them commericial and non commercial. So, we came up with this idea to seperate the implementation of the COM class into a regular C++ class that can be profiled and tested, and then just foward calls from the COM class to the Impl class. That way we use the Impl class in testing and the COM interface in production.
It sounded like a good idea, but there are classes in process that make calls to it. In this experiment those classes took a boost::shared_ptr<Impl> in their constructor. Well, in production I don't have access to that, the COM class owns it as a member.Can't the COM wrapper hold the implementation by shared_ptr, too?
I can't implement a method GetImpl and pass a boost::shared_ptr, because COM makes me use primitive types.
COM wrapper is itself a C++ class. It can have methods not exposed via COM, and not limited by COM rules. Nothing says you must communicate with your own classes exclusively via COM interface pointers.
In regular C++ I'd make an abstract interface and define those dependant classes to use that. I don't think there is a way for me to do that with COM is there? Can a COM class inherit from an abstract interface?
It does all the time - IUnknown is an abstract interface.
And then, how do I get that interface from a _com_ptr?
Why does it have to be _com_ptr?
Igor Tandetnik
-
Friday, April 27, 2012 5:00 PM
On 4/26/2012 11:33 AM, Christopher Pisz wrote:
None of the profilers I have tried to date have been able to successfully find memory leaks across COM boundries. I have tried bunches of them commericial and non commercial. So, we came up with this idea to seperate the implementation of the COM class into a regular C++ class that can be profiled and tested, and then just foward calls from the COM class to the Impl class. That way we use the Impl class in testing and the COM interface in production.
It sounded like a good idea, but there are classes in process that make calls to it. In this experiment those classes took a boost::shared_ptr<Impl> in their constructor. Well, in production I don't have access to that, the COM class owns it as a member.Can't the COM wrapper hold the implementation by shared_ptr, too?
I can't implement a method GetImpl and pass a boost::shared_ptr, because COM makes me use primitive types.
COM wrapper is itself a C++ class. It can have methods not exposed via COM, and not limited by COM rules. Nothing says you must communicate with your own classes exclusively via COM interface pointers.
In regular C++ I'd make an abstract interface and define those dependant classes to use that. I don't think there is a way for me to do that with COM is there? Can a COM class inherit from an abstract interface?
It does all the time - IUnknown is an abstract interface.
And then, how do I get that interface from a _com_ptr?
Why does it have to be _com_ptr?
Igor Tandetnik
I am not sure how to break up quotes on this forum...
"Can't the COM wrapper hold the implementation by shared_ptr, too?"
The COM class does hold the implementation by shared_ptr. It owns it, it creates it during construction.
"COM wrapper is itself a C++ class. It can have methods not exposed via COM, and not limited by COM rules. Nothing says you must communicate with your own classes exclusively via COM interface pointers."
I tried this. I created a method to the COM class: MyClassImpl::SharedPtr GetImpl(); and the compiler complains that I cannot call it until I add it to the IDL. I suppose this is because it is not part of the generated interface, which leads to your next comment.
"Why does it have to be _com_ptr?"
Maybe it doesn't. The COM class is only a COM class in order to provide a connection point to talk to another component. I imagine I have to instantiate it as a COM interface for it to do that. My only options are to instantiate it as IMyClass *, which is a pointer to the COM interface or IMyClassPtr, which is a COM smart pointer to the interface, as far as I know. Either way the COM interface is generated by the MIDL compiler and only contains methods that have been added to the IDL. IDL has no idea what a boost::shared_ptr is and thus that method cannot be added.
In the end, I don't know how to, or assumed it was impossinble to, go about implementing or calling methods in a COM class that are not part of the IDL.
How does one do that?
-
Friday, April 27, 2012 5:18 PM
On 4/27/2012 1:00 PM, Christopher Pisz wrote:
"Can't the COM wrapper hold the implementation by shared_ptr, too?"
The COM class does hold the implementation by shared_ptr. It owns it, it creates it during construction.
So it can have a member shared_ptr<Impl> m_pImpl, and during construction do
CMyComWrapper() : m_pImpl(new Impl) {}
Otherwise, I don't see how you expect this shared_ptr business to work. Are you thinking of something like this:
class CMyComWrapper { Impl m_impl; public: shared_ptr<Impl> GetImpl() { return shared_ptr<Impl>(&m_impl); } };This won't work - when that shared_ptr goes out of scope, it will attempt to delete the instance, but it wasn't allocated with new (and in any case, the wrapper still needs it alive).
"COM wrapper is itself a C++ class. It can have methods not exposed via COM, and not limited by COM rules. Nothing says you must communicate with your own classes exclusively via COM interface pointers."
I tried this. I created a method to the COM class: MyClassImpl::SharedPtr GetImpl(); and the compiler complains that I cannot call it until I add it to the IDL.
You attempt to call it through a COM interface pointer. Don't. Instead, store a direct pointer to the actual implementation class.
You have something like this:
class CMyComWrapper : public IMyInterface {...};
Somewhere in your code, you create an instance of CMyComWrapper, then keep a reference to it in the form of IMyInterface* pointer (possibly wrapped in _com_ptr_t or whatever). Instead, or in addition, store CMyComWrapper* pointer directly. You can call any kind of methods through that raw pointer.
Igor Tandetnik
-
Friday, April 27, 2012 8:01 PM
Her I did my best to copy paste code and change the names so work won't get angry:
#ifndef MYPROJECT_MYCLASSCOM_H
#define MYPROJECT_MYCLASSCOM_H
///////////////////////////////////////////////////////////////////////////////
// \file $File$
// \authors $Author$
// \date $Date$
// \version $Revision$
//-----------------------------------------------------------------------------// Project Includes
#include "resource.h" // main symbols
#include "MyService_h.h"// Core Includes
#include "MyClassImpl.h"// Standard Includes
#include <map>// Windows Includes
#include <atlbase.h>
#include <atlcom.h>//------------------------------------------------------------------------------
// Import the other side's COM Interface and its types
#import "TheOtherSide.tlb" named_guids
//-----------------------------------------------------------------------------
class ATL_NO_VTABLE MyClassCOM : public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<MyClassCOM, &CLSID_MYCLASS>,
public ISupportErrorInfo,
public IMyClass, // Generated from IDL
public IDispEventImpl<10, MyClassCOM, &ATMS::DIID__IOtherSideInterfaceEvents , &LIBID_SOMELIB, 1, 0>
{
public:DECLARE_REGISTRY_RESOURCEID(IDR_MYCLASS)
DECLARE_NOT_AGGREGATABLE(MyClassCOM)DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(MyClassCOM)
COM_INTERFACE_ENTRY(IMYCLASS)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()BEGIN_SINK_MAP(MyClassCOM)
SINK_ENTRY_EX(10, ATMS::DIID__IOtherSideEvents, 1, OnUpdate)
END_SINK_MAP()
/// Constructor
MyClassCOM()
{
// Create the implementation of this COM interface
myClassImpl_.reset(new MyClassImpl(fromGlobals3_,
fromGlobals2_,
fromGlobals1_,
boost::bind(&MyClassCOM::ComAdviseCallback, this, _1, _2));}
/// Deconstructor
~MyClassCOM();
/// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);/// Initialize the connection to the other side
STDMETHOD(Connect) ();/// Close the connection to the other side
STDMETHOD(Disconnect) ();
/// Get the interface to the other side and use it directly
/// TODO - This is bad practice, but so much existing code uses the other side's interface directly
/// that it would require too much code change right now. We should have methods in this
/// interface that call the other side in turn.
STDMETHOD(GetOtherSide) (IDispatch ** otherside);// SNIP bunch of methods to call from this process that were also in the IDL
// Need to be able to use this
boost::shared_ptr<MyClassImpl> GetImpl() { return myClassImpl_; }private:
boost::shared_ptr<MyClassImpl> myClassImpl_; // Implementation of this COM class
/// Call back from the other side for status updates
HRESULT __stdcall OnUpdate(const BSTR update);/// Callback from the implementation of this COM class when an advise or unadvise is needed
void ComAdviseCallback(ITheOtherSidePtr theOtherSide, const bool advise);
};#endif
So, as you say, I could just instantiate this thing as a MyClassCOM * and make calls in process such as GetImpl() without needing the interface defined in the IDL, but then will this thing still work for the connection point it is setting up and get the updates from the other COM object it talks to?
If so, problem solved.
-
Friday, April 27, 2012 8:16 PM
On 4/27/2012 4:01 PM, Christopher Pisz wrote:
So, as you say, I could just instantiate this thing as a MyClassCOM * and make calls in process such as GetImpl() without needing the interface defined in the IDL, but then will this thing still work for the connection point it is setting up and get the updates from the other COM object it talks to?
What thing? What connection point? Who's setting up what, and how? What is this other COM object? What kind of updates does "it" expect to "get" from that object?
You've never mentioned a second COM object nor connection points before. How does all that fit into the picture?
If you hope to be able to call MyClassImpl methods across process boundary, then no, that won't work (if it did, you wouldn't have needed a COM wrapper in the first place).
Igor Tandetnik
-
Friday, April 27, 2012 8:26 PM
The thing...connection points..well, I honestly don't know much about how it works. It is some COM "magic" that previously existed. It talks to some COM object that is in another dll, but ends up running on the same machine. I assume those COM inheritances and SINK macros take care of it. I do not intend to pass MyClassImpl across COM, but only calling GetImpl() in the owner process where the instance is running.On 4/27/2012 4:01 PM, Christopher Pisz wrote:
So, as you say, I could just instantiate this thing as a MyClassCOM * and make calls in process such as GetImpl() without needing the interface defined in the IDL, but then will this thing still work for the connection point it is setting up and get the updates from the other COM object it talks to?
What thing? What connection point? Who's setting up what, and how? What is this other COM object? What kind of updates does "it" expect to "get" from that object?
You've never mentioned a second COM object nor connection points before. How does all that fit into the picture?
If you hope to be able to call MyClassImpl methods across process boundary, then no, that won't work (if it did, you wouldn't have needed a COM wrapper in the first place).
Igor Tandetnik
-
Friday, April 27, 2012 8:55 PM
On 4/27/2012 4:26 PM, Christopher Pisz wrote:
The thing...connection points..well, I honestly don't know much about how it works. It is some COM "magic" that previously existed. It talks to some COM object that is in another dll, but ends up running on the same machine. I assume those COM inheritances and SINK macros take care of it. I do not intend to pass MyClassImpl across COM, but only calling GetImpl() in the owner process where the instance is running.
If you have MyClassCOM* pointer in hand, then you can call GetImpl on it. It doesn't matter who else holds what other references to the same object, be it an event source from another DLL holding IOtherSideEvents* pointer, or otherwise.
Igor Tandetnik
- Marked As Answer by Christopher Pisz Tuesday, May 01, 2012 2:40 AM
-
Tuesday, May 01, 2012 2:39 AM
On 4/27/2012 4:26 PM, Christopher Pisz wrote:
The thing...connection points..well, I honestly don't know much about how it works. It is some COM "magic" that previously existed. It talks to some COM object that is in another dll, but ends up running on the same machine. I assume those COM inheritances and SINK macros take care of it. I do not intend to pass MyClassImpl across COM, but only calling GetImpl() in the owner process where the instance is running.
If you have MyClassCOM* pointer in hand, then you can call GetImpl on it. It doesn't matter who else holds what other references to the same object, be it an event source from another DLL holding IOtherSideEvents* pointer, or otherwise.
Igor Tandetnik
Yes, but that is the whole problem. I'm sorry this is hard to explain. This is a bad design and legacy, done well before I ever got my hands on it.
The process that needs it to be COM to establish a connection point with another component of the system is the same process that needs to make non-COM calls to it. The process can only have one instance of it, because another instance would create and start up the system component on the other side again. So, I need access to the COM part and the non-COM part of it from the same instance.
It's a mess. I've pretty much given up on it and am calling it horrible design that would require too much work to refactor. I've convinced my peers to make plans to throw it away. We can close his issue unless you want to persue it just for fun.
-
Tuesday, May 01, 2012 3:38 AM
Christopher Pisz wrote:
The process that needs it to be COM to establish a connection point with another component of the system is the same process that
needs to make non-COM calls to it. The process can only have one instance of it, because another instance would create and start
up the system component on the other side again. So, I need access to the COM part and the non-COM part of it from the same
instance.Why is that a problem? If nothing better, have MyClassImpl constructor save its own 'this' pointer to a global variable, then access the instance through that variable.
Igor Tandetnik
-
Tuesday, May 01, 2012 6:50 PM

