为什么从针对 Any CPU 的 C# 项目调用此代码时会抛出 System.AccessViolationException?
Why does this code throw System.AccessViolationException when called from a C# project targeting Any CPU?
我的 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
时,它仍然打印 Going to return -1... 到控制台 window.
编辑: 我缩小了复制步骤。抱歉,但我忘了添加这个。这似乎发生在使用 x64 构建编译我的代码时(针对 x64 的 ATL 项目和针对 Any CPU 的 C# 测试项目),然后转换回 Any CPU 构建(针对 Win32 的 ATL 项目和针对 Any CPU 的 C# 测试项目)。
编辑:我将错误范围缩小了一点。首先,看起来 Any CPU C# 项目总是使用我的 COM 对象的 x64 版本,所以我的 ATL 项目的版本需要先编译才能传播任何代码,因为 Any CPU在 x64 系统上实际上是在 x64 上下文中 运行。此外,看起来 crapStructure->ErrorMessage = errorMessage;
行导致了 System.AccessViolationException
。它将在 COM 世界中执行代码,但在 return 到 C# 世界时它会抛出异常。
编辑:在 C++ 中,printf("%d\n", sizeof(CrapStructure));
导致 16。在 C# 中,Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure)));
也会导致 16。但是,唉,再读一遍,这是一个无用的支票,正如汉斯在这里的回答所提到的:How do I check the number of bytes consumed by a structure?
编辑:我尝试做 tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll
并使用 Any CPU 中的 CrappyCOMx86Managed.dll
-针对 C# 项目,但它再次抛出 System.AccessViolationException
。我认为这是因为它又 转到系统上注册的 x64 COM 对象,所以我使用 regsvr32
注销 x64 COM 对象。 运行 代码再次注册了 x86 COM 对象,它抱怨 Retrieving the COM class factory for component with CLSID {F7375DA4-2C1E-400D-88F3-FF816BB21177} failed due出现以下错误:80040154 Class 未注册(HRESULT 异常:0x80040154 (REGDB_E_CLASSNOTREG))。 我发现使它以我的 [= 中的 x86 COM 对象为目标的唯一方法26=]-生成的存根是通过将我的 C# 项目的构建目标修改为 x86,然后代码用于测试我的 x86 COM 对象,所以这种对我来说似乎是一个有实际意义的点,因为我想要 Any CPU 以正确的结构大小专门针对我的 x86 COM 对象或我的 x64 COM 对象。 COM 在 C# 中是一场灾难。
对于 x64 和 x86 版本,您可能(隐含地)使用相同的类型库 (.TLB) 或包含 .TLB 的 DLL。
问题是您的 TLB 定义了一个非托管结构,该结构布局在 32 位和 64 位模式下会有所不同(大小、偏移量...)。
在 .NET 中,您要确保在使用不同的处理器架构进行编译时,您没有引用完全相同的互操作程序集(通过隐式 tlbimp COM 引用从 TLB 生成)。
使用标准 Visual Studio 工具可能会很棘手。
如果您仍想使用这样的结构(这在自动化世界中有点奇怪),我建议您自己使用 tlbimp.exe 构建互操作程序集,并像普通 .NET 一样简单地引用这些互操作程序集程序集,但取决于位数(您可以使用 .csproj 中的 'Condition' 属性对其进行调整),而不是直接使用 COM 引用。
我的 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
时,它仍然打印 Going to return -1... 到控制台 window.
编辑: 我缩小了复制步骤。抱歉,但我忘了添加这个。这似乎发生在使用 x64 构建编译我的代码时(针对 x64 的 ATL 项目和针对 Any CPU 的 C# 测试项目),然后转换回 Any CPU 构建(针对 Win32 的 ATL 项目和针对 Any CPU 的 C# 测试项目)。
编辑:我将错误范围缩小了一点。首先,看起来 Any CPU C# 项目总是使用我的 COM 对象的 x64 版本,所以我的 ATL 项目的版本需要先编译才能传播任何代码,因为 Any CPU在 x64 系统上实际上是在 x64 上下文中 运行。此外,看起来 crapStructure->ErrorMessage = errorMessage;
行导致了 System.AccessViolationException
。它将在 COM 世界中执行代码,但在 return 到 C# 世界时它会抛出异常。
编辑:在 C++ 中,printf("%d\n", sizeof(CrapStructure));
导致 16。在 C# 中,Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure)));
也会导致 16。但是,唉,再读一遍,这是一个无用的支票,正如汉斯在这里的回答所提到的:How do I check the number of bytes consumed by a structure?
编辑:我尝试做 tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll
并使用 Any CPU 中的 CrappyCOMx86Managed.dll
-针对 C# 项目,但它再次抛出 System.AccessViolationException
。我认为这是因为它又 转到系统上注册的 x64 COM 对象,所以我使用 regsvr32
注销 x64 COM 对象。 运行 代码再次注册了 x86 COM 对象,它抱怨 Retrieving the COM class factory for component with CLSID {F7375DA4-2C1E-400D-88F3-FF816BB21177} failed due出现以下错误:80040154 Class 未注册(HRESULT 异常:0x80040154 (REGDB_E_CLASSNOTREG))。 我发现使它以我的 [= 中的 x86 COM 对象为目标的唯一方法26=]-生成的存根是通过将我的 C# 项目的构建目标修改为 x86,然后代码用于测试我的 x86 COM 对象,所以这种对我来说似乎是一个有实际意义的点,因为我想要 Any CPU 以正确的结构大小专门针对我的 x86 COM 对象或我的 x64 COM 对象。 COM 在 C# 中是一场灾难。
对于 x64 和 x86 版本,您可能(隐含地)使用相同的类型库 (.TLB) 或包含 .TLB 的 DLL。
问题是您的 TLB 定义了一个非托管结构,该结构布局在 32 位和 64 位模式下会有所不同(大小、偏移量...)。
在 .NET 中,您要确保在使用不同的处理器架构进行编译时,您没有引用完全相同的互操作程序集(通过隐式 tlbimp COM 引用从 TLB 生成)。
使用标准 Visual Studio 工具可能会很棘手。
如果您仍想使用这样的结构(这在自动化世界中有点奇怪),我建议您自己使用 tlbimp.exe 构建互操作程序集,并像普通 .NET 一样简单地引用这些互操作程序集程序集,但取决于位数(您可以使用 .csproj 中的 'Condition' 属性对其进行调整),而不是直接使用 COM 引用。