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

标签 c# c++ .net com pinvoke

我的 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... 到控制台窗口。

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

编辑:我进一步缩小了错误范围。首先,看起来 Any CPU C# 项目总是使用我的 COM 对象的 x64 版本,因此我的 ATL 项目的版本需要先编译才能传播任何代码,因为 x64 系统上的 Any CPU 确实会在 x64 上下文中运行。此外,看起来 crapStructure->ErrorMessage = errorMessage; 这一行导致了 System.AccessViolationException。它将在 COM 世界中执行该行之后的代码,但在返回到 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 并使用 CrappyCOMx86Managed.dll 来自 Any以 CPU 为目标的 C# 项目,但它再次抛出 System.AccessViolationException。我认为这是因为它再次转到在系统上注册的 x64 COM 对象,所以我使用 regsvr32 注销 x64 COM 对象。再次运行代码并注册 x86 COM 对象后,它会提示为具有 CLSID {F7375DA4-2C1E-400D-88F3-FF816BB21177} 的组件检索 COM 类工厂失败,原因是以下错误:80040154 类未注册 (来自 HRESULT 的异常:0x80040154 (REGDB_E_CLASSNOTREG))。 我发现使它从我的 tlbimp 生成的 stub 中以 x86 COM 对象为目标的唯一方法是修改我的 C# 项目的构建目标成为 x86,然后代码用于测试我的 x86 COM 对象,所以这对我来说似乎是一个有争议的问题,因为我想让任何 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 中的“条件”属性对其进行调整)而不是直接使用 COM 引用。

关于c# - 为什么从针对任何 CPU 的 C# 项目调用时,此代码会抛出 System.AccessViolationException?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45215419/

相关文章:

c++ - 什么::(成员方法名称)在C++中做什么

.net - 如何避免 NuGet 包重复

c# - 添加(或删除)空事件监听器是空操作吗?

C++:std::atomic<bool> 和 volatile bool

c# - 从另一个文档创建 xmlDocument

c++ - 通过特定定制环绕世界边缘

c# - 在 C# 中是 bool 读/写原子

.net - vb.net:可以将一个字符串分割成一个字符串吗

c# - "where T : class, new()"是什么意思?

c# - CsvHelper 不将数据写入内存流