c# c++ .net com pinvoke

¿Por qué este código arroja System.AccessViolationException cuando se llama desde un proyecto C#que apunta a cualquier CPU?



c++ .net (1)

Lo que probablemente sucede es que está (implícitamente) usando la misma Biblioteca de tipos (.TLB) o DLL que contiene .TLB, para las versiones x64 y x86.

El problema es que su TLB define una estructura no administrada, y este diseño de estructura será diferente en el modo de 32 bits y 64 bits (tamaños, compensaciones, ...).

Desde .NET, debe asegurarse de no hacer referencia al mismo conjunto de interoperabilidad exacto (producido a partir de TLB mediante la referencia implícita de tlbimp COM) al compilar con diferentes arquitecturas de procesador.

Puede ser complicado hacerlo bien con las herramientas estándar de Visual Studio.

Si aún desea utilizar estructuras como esta (lo cual es un poco extraño en el mundo de la Automatización), le recomiendo que construya Conjuntos Interop usando tlbimp.exe por usted mismo, y simplemente haga referencia a estos Conjuntos Interop como ensambles .NET normales, pero dependiendo de la bitness ( podrá modificarlo usando los atributos ''Condición'' en .csproj) en lugar de usar referencias COM directamente.

Tengo este IDL en mi proyecto ATL:

[ 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; }; };

Esta es mi implementación en 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; }

Lo llamo así en 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); }

Si ejecuto el código de un proyecto C # dirigido a x64, entonces todo funciona bien. El problema es que cuando llamo al método TestCrap desde un proyecto dirigido a Cualquier CPU en C #, arroja System.AccessViolationException . ¿Porqué es eso? Puedo verificar que cuando lanza System.AccessViolationException , todavía imprime Ir a devolver -1 ... a la ventana de la consola.

Editar : he reducido los pasos de reproducción. Lo siento, pero olvidé agregar esto. Esto parece suceder cuando compilo mi código usando una compilación x64 (el proyecto ATL apuntando a x64 y el proyecto de prueba C # apuntando a cualquier CPU), luego haciendo la transición a una compilación Any CPU (el proyecto ATL dirigido a Win32 y al proyecto de prueba C # dirigido a cualquier CPU).

Editar : Reduje el error un poco más. En primer lugar, parece que el proyecto Any CPU C # siempre usa la versión x64 de mi objeto COM, por lo que la versión de mi proyecto ATL debe compilarse primero para que se propague cualquier código porque cualquier CPU en un sistema x64 realmente va a funcionar. ejecutar en el contexto x64. Además, parece que la línea, crapStructure->ErrorMessage = errorMessage; está causando System.AccessViolationException . Ejecutará el código en el mundo COM más allá de la línea, pero al regresar al mundo C # devuelve la excepción.

Editar : en C ++, printf("%d/n", sizeof(CrapStructure)); resultados en 16 . Desde C #, Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure))); también resulta en 16 . Pero, por desgracia, al leer sobre esto un poco más, se trata de un cheque inútil como lo menciona la respuesta de Hans aquí: ¿cómo puedo verificar el número de bytes consumidos por una estructura?

Editar : Intenté hacer tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll y usar CrappyCOMx86Managed.dll desde mi proyecto C # con CrappyCOMx86Managed.dll a cualquier CPU , pero arrojó System.AccessViolationException nuevamente. Pensé que esto era porque volvía al objeto COM x64 registrado en el sistema, así que utilicé regsvr32 para anular el registro del objeto COM x64. Ejecutando el código nuevamente y con el objeto COM x86 registrado, se queja de que la recuperación de la fábrica de la clase COM para el componente con CLSID {F7375DA4-2C1E-400D-88F3-FF816BB21177} falló debido al siguiente error: 80040154 Clase no registrada (Excepción de HRESULT : 0x80040154 (REGDB_E_CLASSNOTREG)). La única forma que encontré para hacer que apunte al objeto COM x86 desde mi tlbimp generado por tlbimp fue modificando el objetivo de compilación de mi proyecto C # para que sea x86, y luego el código funciona para probar mi objeto COM x86, por lo que este tipo de como un punto discutible para mí porque me gustaría tener cualquier CPU específicamente dirigida a mi objeto COM x86 o mi objeto COM x64 con el tamaño de estructura correcto. COM es un desastre en C #.