将 COM 对象传递给 VB 6 并打开监视窗口时出现 VariantClear 异常

Posted

技术标签:

【中文标题】将 COM 对象传递给 VB 6 并打开监视窗口时出现 VariantClear 异常【英文标题】:VariantClear exception when passing COM object to VB 6 with opened watch window 【发布时间】:2016-04-28 04:09:07 【问题描述】:

为什么会出现这个错误?引用计数增加,线程模型是单个单元。 Coll-object 和 EmptyColl-function 都位于一个 dll 中。 ATL 工程的默认调用转换为 __stdcall。此 dll 中的其他对象也发生了同样的错误。

使用 NULL 对象清除 VARIANT 时,VariantClear 会引发异常: 在 VB6.EXE 中的 0x75C14974 (oleaut32.dll) 处引发异常:0xC0000005: 访问冲突读取位置 0x00000008。

frmMain.frm(错误,原因见下文):

Private Sub Form_Load()
  Dim c As Coll
  Set c = EmptyColl
  'error when ends here with variable "c" in the watch window.
End Sub

frmMain.frm(无错误):

Private Sub Form_Load()
  Dim c2 As Coll 'instead of Coll can be any object of same library
  Set c2 = New Coll 'creation
  Set c2 = Nothing 'destroying (optionaly)
  Dim c As Coll
  Set c = EmptyColl
  'no error
End Sub

filyus.idl:

[
  object,
  uuid(6FA7FAEB-5CE3-4A80-9288-2667EE5E7596),
  dual,
  nonextensible,
  pointer_default(unique)
]
interface IColl : IDispatch
  //some methods
;

[
  uuid(157F3D2F-A427-4D5A-B908-87868297EA43),
  version(1.0),
]
library Filyus

  importlib("stdole2.tlb");
  [
    dllname("Filyus")
  ]
  module Filyus
    [entry("EmptyColl")]
    HRESULT EmptyColl([out, retval] IColl** Coll);
  
;

filyus.def:

LIBRARY

EXPORTS
  DllCanUnloadNow       PRIVATE
  DllGetClassObject PRIVATE
  DllRegisterServer PRIVATE
  DllUnregisterServer   PRIVATE
  DllInstall        PRIVATE
  EmptyColl

ole.h:

extern HRESULT EmptyColl(IColl** Coll);

ole.cpp:

HRESULT EmptyColl(IColl** Coll) 
  HRESULT hr; CComObject<CColl>* Object;
  if (Coll != nullptr) 
    hr = CComObject<CColl>::CreateInstance(&Object);
    if (hr == S_OK) 
      Object->AddRef();
      *Coll = Object; //same error with using QueryInterface
    
  
  else hr = E_POINTER;
  return hr;

【问题讨论】:

"这个 dll 中的其他对象也发生了同样的错误" - 那么很明显你在你的 DLL 中做了一些根本性的错误。请提供Minimal, Complete, and Verifiable example,显示更多您的 DLL 代码。特别是它是如何声明和设置 CColl 类以及您遇到问题的其他类的。 【参考方案1】:

EmptyColl() 需要使用__stdcall 调用约定:

extern HRESULT __stdcall EmptyColl(IColl** Coll);

HRESULT __stdcall EmptyColl(IColl** Coll) 
    //...

或者,使用STDMETHODCALLTYPE 宏,它解析为__stdcall

extern HRESULT STDMETHODCALLTYPE EmptyColl(IColl** Coll);

HRESULT STDMETHODCALLTYPE EmptyColl(IColl** Coll) 
    //...

如果没有声明调用约定,默认情况下,C/C++ 编译器将使用__cdecl,除非配置不同。 __cdecl__stdcall 以不同的方式管理调用堆栈。如果您不使用正确的调用约定,您将破坏调用堆栈。 COM 标准需要__stdcall,这正是 VB 所期望的。

【讨论】:

我的项目默认调用转换是__stdcall。 最好明确代码中的调用约定,而不是依赖项目设置。 从 COM 接口开始的偏移量 8 是 COM 对象的 Release() 方法的 vtable 条目。地址为00000008 的AV 意味着Release() 正在一个空接口指针上被调用。如果VARIANT 的类型是VT_UNKNOWNVT_DISPATCHVariantClear() 确实会调用Release(),但如果VARIANT 被正确管理,那么它不应该在其中包含空接口指针。一个空的 VARIANT 应该有一个 vtVT_EMPTY 代替。也就是说,如果CreateInstance() 失败,我确实看到EmptyColl() 没有将Coll 指针初始化为null。 否则 *科尔 = nullptr;没有解决问题。请看上面的frmMain.frm(没有错误)代码。【参考方案2】:

由于对对象的错误访问而发生错误。 CComPtr 用于客户端,CComObject 用于服务器端(直接访问,仅当您已创建此库的任何对象时才获取它)。

正确的ole.cpp:

HRESULT EmptyColl(IColl** Coll) 
  HRESULT hr; CComPtr<IColl> Object;
  if (Coll != nullptr) 
    hr = Object.CoCreateInstance(CLSID_Coll);
    if (hr == S_OK) 
      Object.CopyTo(Coll);
    
  
  else hr = E_POINTER;
  return hr;

【讨论】:

EmptyColl() 实现是服务器端代码,而不是客户端代码。 EmptyColl() 不是从另一个库创建 COM 对象的实例,而是创建与 EmptyColl() 驻留在同一 DLL 中的 CColl 类的新实例。在这种情况下,OP 对CComObject 的使用是正确的(假设CColl 派生自CComObjectRootCComObjectRootEx,即)。像您建议的那样使用CComPtr,虽然对于客户端代码是正确的,但对于EmptyColl() 的服务器端代码是不正确的。 @remy-lebeau 看看上面标有“frmMain.frm (no error)”的代码,在这个库的任何对象被创建(并且可选地被销毁)之后,都没有发生错误。因此,要使用 CComObject,您需要一些已经从客户端对象创建的,可能的应用程序 - 创建子对象和相关对象。 不,您不需要活动对象已经存在才能使用CComObject 创建新对象。我已经编写了很多 COM 服务器,它们在调用时会返回新对象,而 CComObject 在正确使用时可以正常工作。问题不在显示的代码中,正如“此 dll 中的其他对象发生相同错误”注释所示。 DLL 一定是做错了什么,但这些细节还没有公布。 重现问题不再需要代码。在新项目中测试。

以上是关于将 COM 对象传递给 VB 6 并打开监视窗口时出现 VariantClear 异常的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin:将函数传递给类

将范围参数传递给Crystal对象

jQuery UI 问题。如何将参数传递给函数

我可以将类引用作为参数传递给 VB Net 中的函数吗?

将 VB 函数回调作为参数传递给 .NET

Vuejs 2将道具对象传递给子组件并检索