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

时间:2017-07-20 12:56:29

标签: 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,它仍会打印将返回-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#世界时,它会抛出异常。

修改:在C ++中,printf("%d\n", sizeof(CrapStructure));会产生 16 。从C#开始,Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure)));也会导致 16 。但是,唉,再多读一遍,这是一个无用的检查,正如Hans&#39;在这里回答:How do I check the number of bytes consumed by a structure?

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

1 个答案:

答案 0 :(得分:1)

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

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

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

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

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