将 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_UNKNOWN
或VT_DISPATCH
,VariantClear()
确实会调用Release()
,但如果VARIANT
被正确管理,那么它不应该在其中包含空接口指针。一个空的 VARIANT 应该有一个 vt
值 VT_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
派生自CComObjectRoot
或CComObjectRootEx
,即)。像您建议的那样使用CComPtr
,虽然对于客户端代码是正确的,但对于EmptyColl()
的服务器端代码是不正确的。
@remy-lebeau 看看上面标有“frmMain.frm (no error)”的代码,在这个库的任何对象被创建(并且可选地被销毁)之后,都没有发生错误。因此,要使用 CComObject,您需要一些已经从客户端对象创建的,可能的应用程序 - 创建子对象和相关对象。
不,您不需要活动对象已经存在才能使用CComObject
创建新对象。我已经编写了很多 COM 服务器,它们在调用时会返回新对象,而 CComObject
在正确使用时可以正常工作。问题不在显示的代码中,正如“此 dll 中的其他对象发生相同错误”注释所示。 DLL 一定是做错了什么,但这些细节还没有公布。
重现问题不再需要代码。在新项目中测试。以上是关于将 COM 对象传递给 VB 6 并打开监视窗口时出现 VariantClear 异常的主要内容,如果未能解决你的问题,请参考以下文章