如何在基于 ATL 的服务器中正确转换和使用本机 COM 类型?

Posted

技术标签:

【中文标题】如何在基于 ATL 的服务器中正确转换和使用本机 COM 类型?【英文标题】:How to properly convert and use native COM types in a ATL-based server? 【发布时间】:2013-06-10 21:39:22 【问题描述】:

我必须编写一个由插件接口调用的 COM 服务器 DLL(使用 ATL) 旧的(闭源)VB6 应用程序,并希望避免 可能的泄漏(当然)!接口描述按原样给出,不能 不幸地改变了:

将被调用的类方法在VB端声明如下:

Public Function Process(data As Object,
                        Params As Variant,
                        Results() As Single) As Integer

IDL 中的接口声明如下:

[id(1)] HRESULT Process([in,out] IDispatch**       pDataIDisp,
                        [in,out] VARIANT*          pParamsVar,
                        [in,out] SAFEARRAY(FLOAT)* pResSA,
                        [out,retval] SHORT*        pRetVal);

最后被调用的代码如下所示:

STDMETHODIMP Analyzer::Process(IDispatch** pDataIDisp,
                               VARIANT*    pParamsVar,
                               SAFEARRAY** pResSA,
                               SHORT*      pRetVal)

    try
    
        // Prepare for access
        CComPtr<IDispatch> dataComPtr = *pDataIDisp;

        // VARTYPE from caller is VT_VARIANT | VT_ARRAY | VT_BYREF;
        CComVariant prms = *pParamsVar;               // OR use .Attach ?
        CComSafeArray<VARIANT> prmsArr = prms.parray; // OR use prms.Attach ?

        // SafeArray is FADF_HAVEVARTYPE
        CComSafeArray<FLOAT> res = *pResSA; // OR use res.Attach(pResSA*) ?

        
            // Use ATL types wrapped from above
        

        // Cleanup ????
        .
        .
        .
    
    catch (...) 
    return S_OK;

我想知道的是:

    是我接受(和转换)参数到 ATL 的方法吗 输入正确的用法还是有其他(更好的?)方法?

    我是否必须自己调用 IDispatch* 上的 AddRef() 和/或 Release() 或 分配给 CComPtr 是否足以完成这一切?

    作为 VT_BYREF 给出的第二个参数的含义是什么?

    头文件说:

    *  VT_VARIANT          [V][T][P][S]  VARIANT *
    *  VT_ARRAY            [V]           SAFEARRAY*
    *  VT_BYREF            [V]           void* for local use
    

    这对我来说不是很清楚.... #-o

    在存储在 SAFEARRAY 上的 SafeArray(Un)AccessData 是否足够 VARIANT(第二个参数)?

    要让这个东西正常工作(健壮),还需要考虑什么其他事情吗?

感谢您抽出宝贵时间帮助我!

ps:我已经得到了这个工作(或多或少)我只想避免问题(泄漏!) 我不能 调试,因为调用应用程序是封闭源代码,不受我控制...

【问题讨论】:

【参考方案1】:

您正在使用安全数组和输入/输出参数使您的喜好变得更加复杂。尤其是与您将要与之交互的古老 VB6 结合使用。

有一些简单的规则可以使其清晰、简单和可靠:

[in][out][out, retval]参数;在您的代码 sn-p 中,您使用了 [in, out] 并没有显示任何实际更改值以利用 out 说明符的意图:当您没有特殊原因时,它只会使 C++ 端变得复杂,并且额外的引用级别增加了错误 in 参数不需要附加到 C++ 类和释放 当您从 C++ 方法返回并为 out 参数留下一个值时,您通常会从持有者 C++ 类中 Detach() 接口指针、字符串、变体和安全数组 - 将所有权从内部类转移到调用者释放资源的义务 与其直接在 IDL 上使用安全数组并尝试在 VB 端匹配信号,我建议使用变体:您始终可以将数组放入变体中,包括变体数组,VB6 将能够回滚 out 和 not retval 参数在 VB6 端将是 ByRef,in 参数将是 ByVal

你会得到这样的:

[id(1)] HRESULT Process([in] IDispatch* pDataIDisp,
                        [in] VARIANT pParamsVar,
                        [out, retval] VARIANT* pvResult);

在 C++ 服务器端,您只能从参数中读取,不需要释放。您将通过使用 ATL CComVariant 和朋友完全在 C++ 中构建输出变量来初始化输出变量,然后在处理的最后阶段将其分离。

Public Function Process(data As Object,
                        Params As Variant) As Object
' ...
Dim Data, Params As Object
' ...
Dim Result As Object
Result = Server.Process(Data, Params)
' NOTE: Result is OK to be an array

【讨论】:

嗨罗曼!感谢您的回答。给出了 IDL 中的接口合同,我不确定是否足以更改它,因为无法更改 VB6 客户端应用程序(封闭源代码)!接口描述用于该应用程序要使用的插件。所以问题显然可以归结为:如何正确处理 [in,out] 以及 IDispatch* 是否已经 AddRef'fed? 好的,在 [in, out] 你会收到一个正确初始化的值。如果你不改变它,你不需要 AddRef/Release 它。分配给 ATL 类会自动进行额外的引用管理。只有 Attach/Detach 会更改引用计数,因此您根本不需要 Attach,而 Detach 用于将值释放到输出参数的地方。

以上是关于如何在基于 ATL 的服务器中正确转换和使用本机 COM 类型?的主要内容,如果未能解决你的问题,请参考以下文章

在构建导出包含ATL :: CString成员的类的DLL时发出警告C4251

如何将 JavaScript array() 转换为 ATL/COM 数组?

如何将 ATL/MFC CString 转换为 QString?

向 VBScript (ATL) 公开 COM 事件

正确关闭使用 ATL 在单独线程上创建的窗口

生成导出包含 ATL::CString 成员的类的 DLL 时出现警告 C4251