为什么当从面向任何 CPU 的 C# 项目调用此代码时,此代码会引发 System.AccessViolationExc

Why does this code throw System.AccessViolationException when called from a C# project targeting Any CPU?

本文关键字:代码 AccessViolationExc System 调用 项目 任何 CPU 为什么      更新时间:2023-10-16

我的ATL项目中有这个IDL:

[
object,
uuid(61B0BFF7-E9DF-4D7E-AFE6-49CC67245257),
dual,
nonextensible,
pointer_default(unique)
]
interface ICrappyCOMService : IDispatch {
typedef
[
uuid(C65F8DE6-EDEF-479C-BD3B-17EC3F9E4A3E),
version(1.0)
]
struct CrapStructure {
INT ErrorCode;
BSTR ErrorMessage;
} CrapStructure;
[id(1)] HRESULT TestCrap([in] INT errorCode, [in] BSTR errorMessage, [in, out] CrapStructure *crapStructure);
};
[
uuid(763B8CA0-16DD-48C8-BB31-3ECD9B9DE441),
version(1.0),
]
library CrappyCOMLib
{
importlib("stdole2.tlb");
[
uuid(F7375DA4-2C1E-400D-88F3-FF816BB21177)      
]
coclass CrappyCOMService
{
[default] interface ICrappyCOMService;
};
};

这是我在C++中的实现:

STDMETHODIMP CCrappyCOMService::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* const arr[] = {
&IID_ICrappyCOMService
};
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
if (InlineIsEqualGUID(*arr[i], riid))
return S_OK;
}
return S_FALSE;
}
STDMETHODIMP CCrappyCOMService::TestCrap(INT errorCode, BSTR errorMessage, CrapStructure *crapStructure) {
memset(crapStructure, 0, sizeof(CrapStructure));
crapStructure->ErrorCode = errorCode;
crapStructure->ErrorMessage = errorMessage;
CComPtr<ICreateErrorInfo> x;
ICreateErrorInfo* pCreateErrorInfo;
CreateErrorInfo(&pCreateErrorInfo);
pCreateErrorInfo->AddRef();
pCreateErrorInfo->SetDescription(errorMessage);
pCreateErrorInfo->SetGUID(IID_ICrappyCOMService);
pCreateErrorInfo->SetSource(L"Component.TestCrap");
IErrorInfo* pErrorInfo;
pCreateErrorInfo->QueryInterface(IID_IErrorInfo, (void**)&pErrorInfo);
pErrorInfo->AddRef();
SetErrorInfo(0, pErrorInfo);
pErrorInfo->Release();
pCreateErrorInfo->Release();
printf("Going to return %d...n", errorCode);
return errorCode;
}

我在 C# 中这样称呼它:

static void Main(string[] args)
{
var service = new CrappyCOMService();
var crapStructure = new CrapStructure();
try
{
service.TestCrap(-1, "This is bananas.", ref crapStructure);
}
catch (COMException exception)
{
Console.WriteLine(exception.ErrorCode);
Console.WriteLine(exception.Message);
}
Console.WriteLine(crapStructure.ErrorCode);
Console.WriteLine(crapStructure.ErrorMessage);
}

如果我从面向 x64 的 C# 项目运行代码,那么一切正常。问题是,当我从面向 C# 中的任何 CPU 的项目调用TestCrap方法时,它会抛出System.AccessViolationException。为什么?我可以验证当它抛出System.AccessViolationException时,它仍然打印将返回 -1...到控制台窗口。

编辑:我已经缩小了复制步骤。对不起,但我忘了添加这个。当使用 x64 版本(面向 x64 的 ATL 项目和面向任何 CPU 的 C# 测试项目(编译我的代码,然后转换回任何 CPU 构建(面向 Win32 的 ATL 项目和面向任何 CPU 的 C# 测试项目(编译我的代码时,似乎会发生这种情况。

编辑:我已经进一步缩小了错误范围。首先,看起来 Any CPU C# 项目始终使用我的 COM 对象的 x64 版本,因此需要首先编译我的 ATL 项目的版本才能传播任何代码,因为 x64 系统上的任何 CPU 实际上都将在 x64 上下文中运行。此外,它看起来像线,crapStructure->ErrorMessage = errorMessage;导致System.AccessViolationException。它将在 COM 世界中执行代码,但在返回到 C# 世界中时,它会引发异常。

编辑:在C++中,printf("%dn", sizeof(CrapStructure));结果为16。从 C# 开始,Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure)));也会产生16。但是,唉,再读一遍,这是一个无用的检查,正如汉斯在这里的回答所提到的:如何检查结构消耗的字节数?

编辑:我尝试做tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll并使用我的任何面向CPU的C#项目中的CrappyCOMx86Managed.dll,但它再次抛出了System.AccessViolationException。我认为这是因为它再次转到系统上注册的x64 COM对象,因此我使用regsvr32注销x64 COM对象。再次运行代码并注册 x86 COM 对象,它抱怨检索具有 CLSID {F7375DA4-2C1E-400D-88F3-FF816BB21177} 的组件的 COM 类工厂由于以下错误而失败:80040154类未注册(HRESULT 的异常:0x80040154 (REGDB_E_CLASSNOTREG((。我发现让它从我的tlbimp生成的存根中面向 x86 COM 对象的唯一方法是将我的 C# 项目的构建目标修改为 x86,然后代码用于测试我的 x86 COM 对象,所以这对我来说似乎是一个有争议的问题,因为我希望让任何 CPU 专门针对我的 x86 COM 对象或我的 x64 COM 对象正确的结构尺寸。COM 在 C# 中是一场灾难。

可能发生的情况是(隐式地(使用相同的类型库(.TLB(,或包含 .TLB,适用于 x64 和 x86 版本。

问题是您的 TLB 定义了一个非托管结构,并且此结构布局在 32 位和 64 位模式下会有所不同(大小、偏移量等(。

在 .NET 中,您希望确保在使用不同的处理器体系结构进行编译时不会引用完全相同的互操作程序集(通过隐式 tlbimp COM 引用从 TLB 生成(。

使用标准的Visual Studio工具进行正确的操作可能很棘手。

如果您仍然想使用这样的结构(这在自动化世界中有点奇怪(,我建议您自己使用 tlbimp.exe 构建互操作程序集,并像普通 .NET 程序集一样简单地引用这些互操作程序集,但取决于位数(您将能够使用 .csproj 中的"条件"属性进行调整(,而不是直接使用 COM 引用。