[C#] 모든 CPU를 대상으로하는 C # 프로젝트에서이 코드가 System.AccessViolationException을 던지는 이유는 무엇입니까?


Answers

아마도 x64 및 x86 버전에 대해 동일한 유형 라이브러리 (.TLB) 또는 .TLB가 포함 된 DLL을 (암시 적으로) 사용하고 있습니다.

문제는 TLB가 관리되지 않는 구조를 정의하고이 구조 레이아웃이 32 비트 및 64 비트 모드 (크기, 오프셋, ...)가 다를 수 있다는 것입니다.

.NET에서 서로 다른 프로세서 아키텍처로 컴파일 할 때 동일한 Interop 어셈블리 (암시 적 tlbimp COM 참조로 TLB에서 생성 된)를 참조하지 않도록합니다.

표준 Visual Studio 도구를 사용하여 올바르게 처리하는 것은 까다로운 작업 일 수 있습니다.

여전히 자동화 환경에서 다소 이상한이 구조체를 사용하려는 경우 tlbimp.exe를 사용하여 Interop 어셈블리를 직접 작성하고 일반 Interop 어셈블리와 마찬가지로 Interop 어셈블리를 참조하는 것이 좋지만 (비트 수에 따라 COM 참조를 직접 사용하는 대신 .csproj의 'Condition'속성을 사용하여 조정할 수 있습니다.

Question

내 IDL 프로젝트에이 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 을 throw한다는 것 입니다. 왜 그런가요? 나는 그것이 System.AccessViolationException 을 throw 할 때 -1 을 콘솔 창 에 반환하도록 Going을 인쇄하는지 확인할 수 있습니다.

편집 : 나는 복제 단계를 좁혔다. 죄송합니다.이 코드를 추가하는 것을 잊어 버렸습니다. 이것은 x64 빌드 (x64를 대상으로하는 ATL 프로젝트와 모든 CPU를 대상으로하는 C # 테스트 프로젝트)를 사용하여 내 코드를 컴파일 한 다음 모든 CPU 빌드 (ATL 프로젝트 모든 CPU를 대상으로하는 Win32 및 C # 테스트 프로젝트를 대상으로합니다.

편집 : 나는 조금 더 아래로 오류를 좁혔습니다. 무엇보다도, 모든 CPU C # 프로젝트가 항상 내 COM 개체의 x64 버전을 사용하는 것처럼 보이므로 x64 시스템의 모든 CPU가 실제로 갈 예정이므로 코드를 전파하려면 먼저 내 ATL 프로젝트의 버전을 컴파일해야합니다. x64 컨텍스트에서 실행됩니다. 또한, 그것은 라인처럼 보입니다, crapStructure->ErrorMessage = errorMessage; System.AccessViolationException 이 발생합니다. 그것은 라인을 넘어 COM 세계에서 코드를 실행하지만 C # 세계로 돌아와 예외를 던집니다.

편집 : C ++에서 printf("%d\n", sizeof(CrapStructure)); 결과는 16 입니다. C #에서 Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure))); 또한 결과는 16 입니다. 하지만 아아, 그것을 좀 더 읽고,이 한스 '대답에 의해 언급 된 쓸모없는 검사가 있습니다 : 구조체에 의해 소비 된 바이트 수를 어떻게 확인합니까?

편집 : 나는 tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll 하고 tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll 을 사용하여 모든 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 생성 한 스텁에서 x86 COM 개체를 대상으로 삼는 유일한 방법은 내 C # 프로젝트의 빌드 대상을 x86으로 수정 한 다음이 코드가 내 x86 COM 개체를 테스트하기 위해 작동하므로이 종류의 것 같습니다. 모든 CPU가 정확한 x86 구조의 COM 객체 또는 올바른 구조체 크기를 가진 x64 COM 객체를 대상으로 삼고 싶기 때문에 저의 논점과 같습니다. COM은 C #의 재앙입니다.