当从针对任何CPU的C#项目调用时,为什么此代码会抛出System.AccessViolationException?

我的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 ErrorCode = errorCode; crapStructure->ErrorMessage = errorMessage; CComPtr 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 。 这是为什么? 我可以validation当它抛出System.AccessViolationException ,它仍然打印Going返回-1 …到控制台窗口。

编辑 :我缩小了复制步骤。 抱歉,但是我忘了添加它。这似乎发生在使用x64版本编译我的代码时(针对x64的ATL项目和针对任何CPU的C#测试项目),然后转换回任何CPU版本(ATL项目)定位Win32和针对任何CPU的C#测试项目。

编辑 :我已经将错误缩小了一点。 首先,看起来Any CPU C#项目总是使用我的COM对象的x64版本,因此我的ATL项目的版本需要首先编译以便传播任何代码,因为x64系统上的任何CPU都是真的要在x64上下文中运行。 此外,它看起来像行, crapStructure->ErrorMessage = errorMessage; 导致System.AccessViolationException 。 它将在COM世界中执行代码,但是在返回到C#世界时,它会抛出exception。

编辑 :在C ++中, printf("%d\n", sizeof(CrapStructure)); 结果16 。 来自C#, Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure))); 也导致了16 。 但是,唉,再多读一遍,这是一个无用的检查,正如Hans在这里回答的那样: 我如何检查结构消耗的字节数?

编辑 :我尝试使用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未注册类(HRESULTexception) :0x80040154(REGDB_E_CLASSNOTREG))。 我发现从我的tlbimp生成存根中定位x86 COM对象的唯一方法是将我的C#项目的构建目标修改为x86,然后代码用于测试我的x86 COM对象,所以这种似乎对我来说是一个没有实际意义的点,因为我想让任何CPU专门针对我的x86 COM对象或具有正确结构大小的x64 COM对象。 COM是C#中的灾难。

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

问题是你的TLB定义了一个非托管结构,这个结构布局在32位和64位模式(大小,偏移,……)中会有所不同。

从.NET开始,您需要确保在使用不同的处理器体系结构进行编译时,不会引用相同的Interop程序集(通过隐式tlbimp COM引用从TLB生成)。

使用标准的Visual Studio工具来完成它可能很棘手。

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