2011-05-02 16 views
8

Estoy usando una DLL propietaria de un tercero para la cual el código fuente no está disponible para mí. Sin embargo, el código Wrapper que parece haber sido generado automáticamente usando SWIG 1.3.39 está disponible para mí. El código del contenedor consiste en un archivo C++ que compila (utilizando algunos encabezados que describen el archivo DLL) en un archivo DLL y un proyecto C# que hace que PInvoke llame a la DLL contenedora de C++.¿Cómo evito AccessViolationException al devolver una cadena de C++ a C# en Windows de 64 bits?

Según mi interpretación de la documentación del proveedor, he compilado todo en la solución como x86 o x64, dependiendo de la plataforma de destino. El proveedor proporciona versiones de 32 bits y de 64 bits de la DLL propietaria y me he asegurado de usar la correcta para la compilación determinada. Mi máquina es de 32 bits. Probar la versión x86 de mi aplicación en mi máquina, ya sea en versiones de depuración o de liberación, parece funcionar bien. Sin embargo, en 64 bits, la aplicación funciona en modo Debug pero falla con System.AccessViolationException en modo Release.

He leído this nice blog entry que parece describir bien el problema de depuración frente a versión, así como this question and answer que dieron lugar a la publicación del blog. Sin embargo, no estoy seguro de cómo solucionar el problema en este caso.

Parece que AccessViolationException se produce la primera vez que se devuelve (o se intenta devolver) una cadena de cualquier longitud real del contenedor de C++. Aquí está el infractor del código de C:

// In one file of the C# wrapper: 
public string GetKey() 
{ 
    // swigCPtr is a HandleRef to an object already created 
    string ret = csWrapperPINVOKE.mdMUHybrid_GetKey(swigCPtr); 
    return ret; 
} 

// In the csWrapperPINVOKE class in another file in the C# wrapper: 
[DllImport("csWrapper.dll", EntryPoint="CSharp_mdMUHybrid_GetKey")] 
public static extern StringBuilder mdMUHybrid_GetKey(HandleRef jarg1); 

Y la problemática código C++ de la envoltura C++:

SWIGEXPORT char * SWIGSTDCALL CSharp_mdMUHybrid_GetKey(void * jarg1) { 
    char * jresult ; 
    mdMUHybrid *arg1 = (mdMUHybrid *) 0 ; 
    char *result = 0 ; 

    arg1 = (mdMUHybrid *)jarg1; 
    result = (char *)(arg1)->GetKey(); 
    jresult = SWIG_csharp_string_callback((const char *)result); 
    return jresult; 
} 

SWIGEXPORT ya se había definido como __declspec(dllexport). En la depuración, descubrí que SWIG_csharp_string_callback, definido como:

/* Callback for returning strings to C# without leaking memory */ 
typedef char * (SWIGSTDCALL* SWIG_CSharpStringHelperCallback)(const char *); 
static SWIG_CSharpStringHelperCallback SWIG_csharp_string_callback = NULL; 

se estaba estableciendo al delegado (en el C# envoltorio):

static string CreateString(string cString) { 
    return cString; 
} 

He tratado de jugar con este código para utilizar constructos tales como Marshal.PtrToStringAut inútilmente. ¿Cómo soluciono y/o soluciono este problema?

+0

Me encontré con exactamente el mismo problema y esta publicación realmente lo solucionó. Sin embargo, quiero saber cómo encontraste el código ofensivo. Porque cuando ejecutaba mi proyecto en el modo de lanzamiento no recibía ninguna excepción. ¡La "Infracción de acceso" solo aparece en la imagen cuando navego para liberar la carpeta y ejecutar el exe directamente desde allí! – Simsons

+0

@Subhen Ha pasado un tiempo, y desde entonces he cambiado de empleador, por lo que ya no tengo acceso a este código. Si recuerdo, estaba ejecutando esto en una instancia de Windows Server, y pude usar el depurador remoto de alguna manera. Podría estar equivocado con eso, pero sé que me tomó uno o dos días encontrar dónde se estaba rompiendo. El registro también puede ayudar, por supuesto, si puede registrar dónde se encuentra exactamente en el código cuando obtiene la excepción. – Andrew

Respuesta

3

El adecuado forma de hacer esto es hacer que su código administrado asigne el búfer al que el código no administrado escribirá (los datos de cadena). Suponiendo que no sea práctico por alguna razón, lo que debe hacer es asignar los datos de la cadena de una manera que pueda ser desasignada del código administrado.

El enfoque habitual es asignar la memoria con LocalAlloc, que luego puede desasignarse del código administrado utilizando Marshal.FreeHGlobal. De esta manera ya no necesita el (kludgy y obviamente no funcional) SWIG_csharp_string_callback y CreateString. código

C++:

SWIGEXPORT HLOCAL SWIGSTDCALL CSharp_mdMUHybrid_GetKey(mdMUHybrid* jarg1) 
{ 
    char const* const str = jarg1->GetKey(); 
    std::size_t const len = std::strlen(str); 
    HLOCAL const result = ::LocalAlloc(LPTR, len + 1u); 
    if (result) 
     std::strncpy(static_cast<char*>(result), str, len); 
    return result; 
} 

código C#:

// In one file of the C# wrapper: 
public string GetKey() 
{ 
    return csWrapperPINVOKE.mdMUHybrid_GetKey(swigCPtr); 
} 

// ... 

public static class csWrapperPINVOKE 
{ 
    // ... 

    [DllImport("csWrapper.dll")] 
    private static extern IntPtr CSharp_mdMUHybrid_GetKey(HandleRef jarg1); 

    public static string mdMUHybrid_GetKey(HandleRef jarg1) 
    { 
     var ptr = CSharp_mdMUHybrid_GetKey(jarg1); 
     try 
     { 
      return Marshal.PtrToStringAnsi(ptr); 
     } 
     finally 
     { 
      Marshal.FreeHGlobal(ptr); 
     } 
    } 
} 

Como acotación al margen, ese pequeño fragmento de código C++ que mostró es un horrible reliquia C-con-clases; si eso es representativo del resto de la base de código, entonces solo, wow ...: -/

+0

Bueno, para ser justos, el código _desea_ parece ser autogenerado (y suministrado por el proveedor, no interno, por lo que podemos estar agradecidos). Gracias por tu respuesta; Lo intentaré mañana si puedo. – Andrew

+0

@ildjarm Esto funcionó muy bien. ¡Gracias! Para informar al público de Google, hice un par de ajustes (usando 'strncpy_s' solo para deshacerme de la advertencia, por ejemplo) y puse la mayor parte de esto en una función separada para poder usarlo con todas las funciones que (usadas para) regresar un 'char *'. En el lado C#, hice el mismo tipo de cosas. En mi tiempo libre inexistente, debería considerar contribuir a swig, ya que veo desde otro proyecto que al menos la versión 2.0.1 produce un código similar. ¡Gracias de nuevo! – Andrew

+0

La función CSharp_mdMUHybrid_GetKey no es realmente eficiente, esta capa adicional debe evitarse si es posible, de lo contrario, la interoperabilidad con C++ es demasiado cara. No tengo experiencia con SWIG, pero ¿puedes llamar a jarg1-> GetKey() desde C# usando SWIG a través de P/Invoke? – xInterop

Cuestiones relacionadas