处理接口指针的方法的正确实现
我有一个用 C++ 实现的 COM 对象。我正在 VB6 应用程序中使用该对象。
问题是如何实现获取并返回接口指针的方法。 这是 IDL 的示例:
[...]
interface ICOMCvDC : IUnknown
{
HRESULT GetPen([retval][out] ICOMCvPen** ppPen);
HRESULT SetPen([in] ICOMCvPen* pPen);
};
下面是组件类对象的骨架:
class COMCvDC : public ICOMCvDC
{
public:
...
STDMETHODDECL GetPen(
/* [retval][out] */ ICOMCvPen** ppPen);
STDMETHODDECL SetPen(
/* [in] */ ICOMCvPen* pPen);
...
protected:
ICOMCvPen* m_pen;
};
...
STDMETHODIMP COMCvDC::GetPen(
/* [retval][out] */ ICOMCvPen** ppPen)
{
*ppPen = m_pen;
return S_OK;
}
STDMETHODIMP COMCvDC::SetPen(
/* [in] */ ICOMCvPen* pPen)
{
m_pen = pPen;
return S_OK;
}
我是 COM 的初学者,所以我不确定我是否以正确的方式这样做。我觉得我需要在某些接口指针上使用 QueryInterface 方法。另外,了解 VB6 在解释以下代码时正在做什么也很有趣:
Dim pen1 As ICOMCvPen
Set pen1 = dc1.GetPen()
它是否在 GetPen
方法返回的接口指针上调用 AddRef
方法?
更新 1
我已经实现了两个测试对象(COMCvTest 和 COMCvTestFactory),它们只记录所有被调用的方法。然后我执行了以下 VB6 代码:
Dim test1 As ICOMCvTest
Set test1 = New COMCvTest
Debug.Print "Ref: " & test1.GetReferenceCounter
Set test1 = Nothing
下面是这些对象的生命周期日志:
COMCvTestFactory::COMCvTestFactory(); m_cRef = 1
COMCvTestFactory::QueryInterface() --- begin ---
IID is {00000001-0000-0000-C000-000000000046}
IID is IID_IClassFactory
COMCvTestFactory::AddRef(); m_cRef = 2 (was 1)
COMCvTestFactory::QueryInterface() ---- end ----
COMCvTestFactory::Release(); m_cRef = 1 (was 2)
COMCvTestFactory::CreateInstance() --- begin ---
COMCvTest::COMCvTest(); m_cRef = 1
COMCvTest::QueryInterface() --- begin ---
IID is {00000000-0000-0000-C000-000000000046}
IID is IID_IUnknown
COMCvTest::AddRef(); m_cRef = 2 (was 1)
COMCvTest::QueryInterface() ---- end ----
COMCvTest::Release(); m_cRef = 1 (was 2)
COMCvTestFactory::CreateInstance() ---- end ----
COMCvTest::AddRef(); m_cRef = 2 (was 1)
COMCvTest::Release(); m_cRef = 1 (was 2)
COMCvTestFactory::Release(); m_cRef = 0 (was 1); deleting object
COMCvTestFactory::~COMCvTestFactory()
COMCvTest::QueryInterface() --- begin ---
IID is {00000000-0000-0000-C000-000000000046}
IID is IID_IUnknown
COMCvTest::AddRef(); m_cRef = 2 (was 1)
COMCvTest::QueryInterface() ---- end ----
COMCvTest::QueryInterface() --- begin ---
IID is {9F660698-1950-4DE8-BB5F-C8D2D61F7367}
IID is IID_ICOMCvTest
COMCvTest::AddRef(); m_cRef = 3 (was 2)
COMCvTest::QueryInterface() ---- end ----
COMCvTest::QueryInterface() --- begin ---
IID is {7FD52380-4E07-101B-AE2D-08002B2EC713}
IID is IID_IPersistStreamInit
COMCvTest::QueryInterface() --- begin ---
IID is {37D84F60-42CB-11CE-8135-00AA004BB851}
IID is IID_IPersistPropertyBag
COMCvTest::Release(); m_cRef = 2 (was 3)
COMCvTest::Release(); m_cRef = 1 (was 2)
COMCvTest::GetReferenceCounter; m_cRef = 1
COMCvTest::Release(); m_cRef = 0 (was 1); deleting object
COMCvTest::~COMCvTest()
看起来 VB6 正在尝试从 COM 对象查询 IPersistStreamInit
和 IPersistPropertyBag
接口。为什么?另外我不明白为什么在查询ICOMCvTest
接口指针之前先查询IUnknown
接口?
I have a COM object that is implemented in C++. I am using this object from a VB6 application.
The question is how to implement a methods that get and return a pointer to an interface.
Here is the sample of an IDL:
[...]
interface ICOMCvDC : IUnknown
{
HRESULT GetPen([retval][out] ICOMCvPen** ppPen);
HRESULT SetPen([in] ICOMCvPen* pPen);
};
And below is the skeleton of the component class object:
class COMCvDC : public ICOMCvDC
{
public:
...
STDMETHODDECL GetPen(
/* [retval][out] */ ICOMCvPen** ppPen);
STDMETHODDECL SetPen(
/* [in] */ ICOMCvPen* pPen);
...
protected:
ICOMCvPen* m_pen;
};
...
STDMETHODIMP COMCvDC::GetPen(
/* [retval][out] */ ICOMCvPen** ppPen)
{
*ppPen = m_pen;
return S_OK;
}
STDMETHODIMP COMCvDC::SetPen(
/* [in] */ ICOMCvPen* pPen)
{
m_pen = pPen;
return S_OK;
}
I am a beginner in COM, so I am unsure if I do it the right way. I feel like I need to use QueryInterface
method on some of the interface pointers. Also it is interesting to know what VB6 is doing when it interprets the code like:
Dim pen1 As ICOMCvPen
Set pen1 = dc1.GetPen()
Does it call the AddRef
method on the interface pointer returned by GetPen
method?
Update 1
I have implemented two test objects (COMCvTest and COMCvTestFactory) that just log all the methods being called. Then I executed the following VB6 code:
Dim test1 As ICOMCvTest
Set test1 = New COMCvTest
Debug.Print "Ref: " & test1.GetReferenceCounter
Set test1 = Nothing
Below is the log of the life cycle of these objects:
COMCvTestFactory::COMCvTestFactory(); m_cRef = 1
COMCvTestFactory::QueryInterface() --- begin ---
IID is {00000001-0000-0000-C000-000000000046}
IID is IID_IClassFactory
COMCvTestFactory::AddRef(); m_cRef = 2 (was 1)
COMCvTestFactory::QueryInterface() ---- end ----
COMCvTestFactory::Release(); m_cRef = 1 (was 2)
COMCvTestFactory::CreateInstance() --- begin ---
COMCvTest::COMCvTest(); m_cRef = 1
COMCvTest::QueryInterface() --- begin ---
IID is {00000000-0000-0000-C000-000000000046}
IID is IID_IUnknown
COMCvTest::AddRef(); m_cRef = 2 (was 1)
COMCvTest::QueryInterface() ---- end ----
COMCvTest::Release(); m_cRef = 1 (was 2)
COMCvTestFactory::CreateInstance() ---- end ----
COMCvTest::AddRef(); m_cRef = 2 (was 1)
COMCvTest::Release(); m_cRef = 1 (was 2)
COMCvTestFactory::Release(); m_cRef = 0 (was 1); deleting object
COMCvTestFactory::~COMCvTestFactory()
COMCvTest::QueryInterface() --- begin ---
IID is {00000000-0000-0000-C000-000000000046}
IID is IID_IUnknown
COMCvTest::AddRef(); m_cRef = 2 (was 1)
COMCvTest::QueryInterface() ---- end ----
COMCvTest::QueryInterface() --- begin ---
IID is {9F660698-1950-4DE8-BB5F-C8D2D61F7367}
IID is IID_ICOMCvTest
COMCvTest::AddRef(); m_cRef = 3 (was 2)
COMCvTest::QueryInterface() ---- end ----
COMCvTest::QueryInterface() --- begin ---
IID is {7FD52380-4E07-101B-AE2D-08002B2EC713}
IID is IID_IPersistStreamInit
COMCvTest::QueryInterface() --- begin ---
IID is {37D84F60-42CB-11CE-8135-00AA004BB851}
IID is IID_IPersistPropertyBag
COMCvTest::Release(); m_cRef = 2 (was 3)
COMCvTest::Release(); m_cRef = 1 (was 2)
COMCvTest::GetReferenceCounter; m_cRef = 1
COMCvTest::Release(); m_cRef = 0 (was 1); deleting object
COMCvTest::~COMCvTest()
It seems like VB6 is trying to query IPersistStreamInit
and IPersistPropertyBag
interfaces from the COM object. Why? Also I don't understand why there is a query of IUnknown
interface before query of ICOMCvTest
interface pointer?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当然,QueryInterface() 使其成为一个干净的单行代码,它利用任何内置 QI 的错误处理并处理必须添加的引用计数:
不要忘记在构造函数中将 m_pen 初始化为 NULL 并释放它在析构函数中。或者使用智能指针。
Sure, QueryInterface() makes it a clean one-liner that takes advantage of any error handling built-in QI and takes care of the reference count you have to add:
Don't forget to initialize m_pen to NULL in the constructor and release it in the destructor. Or use a smart pointer.
接口指针管理没有什么特别之处,除了一件事:您必须处理 AddRef/Release 调用。这就是为什么强烈建议使用智能指针包装器来进行自动管理(C++ 伪代码):
就是这样!如果你不使用 CComPtr,你就必须一直执行“if(!x) x->Release()”这样的事情,而且很容易出错。
回复:更新 1
IID_IUnknown
是 VB6 的 CoCreateInstance 中的参数,则可能会首先查询IUnknown
;然后它QueryInterface
获取感兴趣的接口。IPersistStreamInit
和IPersistPropertyBag
的查询可能是 VB6 运行时标准对象初始化的一部分;如果该对象与表单一起加载,则可以从持久数据对其进行初始化。另请注意,通常会在初始化期间预先查询对象的某些接口,以便它们在稍后需要时准备就绪,尤其是在初始化过程中。如果要重复使用它们。
There is nothing special with interface pointer management, except one thing: you have to take care of AddRef/Release calls. This is why it is strongly recommended to use smart pointer wrapper that do auto-management (C++ pseudo-code):
And that's it! If you don't use CComPtr, you have to do "if(!x) x->Release()" stuff all the way and it's so easy to make a mistake.
Re: Update 1
IUnknown
might be queried first ifIID_IUnknown
was the argument in VB6's CoCreateInstance; then itQueryInterface
'd the interface of interest.IPersistStreamInit
andIPersistPropertyBag
is perhaps a part of standard object initialization with VB6 runtime; should this object be loaded with the form, it could be initialized from persistent data.Also note that it's typical to pre-query certain interfaces off the object right during initialization so that they are ready when they needed later, esp. if they are to be reused.