处理接口指针的方法的正确实现

发布于 2024-12-08 04:09:21 字数 3244 浏览 0 评论 0原文

我有一个用 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 对象查询 IPersistStreamInitIPersistPropertyBag 接口。为什么?另外我不明白为什么在查询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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

逆光飞翔i 2024-12-15 04:09:21

当然,QueryInterface() 使其成为一个干净的单行代码,它利用任何内置 QI 的错误处理并处理必须添加的引用计数:

STDMETHODIMP COMCvDC::GetPen(ICOMCvPen** ppPen)
{
    if (m_pen) return m_pen->QueryInterface(__uuidof(ICOMCvPen), (void**)ppPen);
    else {
        *ppPen = 0;
        return E_FAIL;
    }
}

STDMETHODIMP COMCvDC::SetPen(ICOMCvPen* pPen)
{
    if (m_pen) m_pen->Release();
    m_pen = pPen;
    return S_OK;
}

不要忘记在构造函数中将 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:

STDMETHODIMP COMCvDC::GetPen(ICOMCvPen** ppPen)
{
    if (m_pen) return m_pen->QueryInterface(__uuidof(ICOMCvPen), (void**)ppPen);
    else {
        *ppPen = 0;
        return E_FAIL;
    }
}

STDMETHODIMP COMCvDC::SetPen(ICOMCvPen* pPen)
{
    if (m_pen) m_pen->Release();
    m_pen = pPen;
    return S_OK;
}

Don't forget to initialize m_pen to NULL in the constructor and release it in the destructor. Or use a smart pointer.

森林散布 2024-12-15 04:09:21

接口指针管理没有什么特别之处,除了一件事:您必须处理 AddRef/Release 调用。这就是为什么强烈建议使用智能指针包装器来进行自动管理(C++ 伪代码):

CComPtr<ICOMCvPen> m_pen;

HRESULT Get(IPen** ppPen) { ... *ppPen = CComPtr<ICOMCvPen>(m_pen).Detach();  ... }
HRESULT Set(IPen* pPen) { ... m_pen = pPen;  ... }

就是这样!如果你不使用 CComPtr,你就必须一直执行“if(!x) x->Release()”这样的事情,而且很容易出错。

回复:更新 1

  • 如果 IID_IUnknown 是 VB6 的 CoCreateInstance 中的参数,则可能会首先查询 IUnknown;然后它QueryInterface获取感兴趣的接口。
  • IPersistStreamInitIPersistPropertyBag 的查询可能是 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):

CComPtr<ICOMCvPen> m_pen;

HRESULT Get(IPen** ppPen) { ... *ppPen = CComPtr<ICOMCvPen>(m_pen).Detach();  ... }
HRESULT Set(IPen* pPen) { ... m_pen = pPen;  ... }

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 if IID_IUnknown was the argument in VB6's CoCreateInstance; then it QueryInterface'd the interface of interest.
  • a query for IPersistStreamInit and IPersistPropertyBag 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文