2011-03-14 7 views
5

Tengo problemas de pérdida de memoria con un C++ dll de terceros. Para ciertas llamadas, el dll asigna memoria para la cadena, la pasa como un char * y luego espera recibir ese puntero para que pueda desasignar la memoria.Recibir un char * de C++ en C#, y pasarlo de nuevo

Aquí hay algunos comentarios del archivo de encabezado, un par de ejemplos de dónde se devuelve el carácter * y la firma del método "Versión".

(El dll se llama SW_API, es de una cámara de comercio - si alguien ya ha envuelto esto, ¡me encantaría hablar con ellos!).

/* Strings returned by the API are similarly normal nul-terminated C strings. 
    * The user should not attempt to change any of the bytes or read past the 
    * terminating nul of any returned string. All returned strings must be 
    * released using SW_ReleaseString() once the user is finished with the 
    * result. Failure to do this will result in memory leaks. 
    */ 

    /** 
    * @typedef const char* SW_XML 
    * @brief A string containing an XML documents text. 
    * @note As with all output strings, returned XML must be freed 
    * by the user. See @ref resource. 
    * @sa ErrorCodes 
    */ 
    typedef const char* SW_XML; 

    const char* STDAPICALLTYPE SW_GetLastErrorSpecifics(); 

    SW_ErrCode STDAPICALLTYPE SW_DealGetSWML(SW_LoginID   lh, 
            const char*   swmlVersion, 
            SW_DealVersionHandle dealVersionHandle, 
            SW_XML*    resultXML_out); 


    void STDAPICALLTYPE SW_ReleaseString(const char* buffer); 

Intentar leer a partir de diversas fuentes, que han intentado el siguiente:

// Extern declarations 
    [DllImport(sw_api_dll, EntryPoint = "[email protected]", CharSet = CharSet.Ansi)] 
    public static extern IntPtr SW_GetLastErrorSpecifics(); 

    [DllImport(sw_api_dll, EntryPoint = "[email protected]", CharSet = CharSet.Ansi)] 
    public static extern int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, [Out] out IntPtr outputSWML); 

    [DllImport(sw_api_dll, EntryPoint = "[email protected]", CharSet=CharSet.Ansi)] 
    public static extern void SW_ReleaseString(IntPtr buffer); 


    // Using the externs. 
    private static string GetIntPtrStringAndRelease(IntPtr ptr) 
    { 
     string result = Marshal.PtrToStringAnsi(ptr); 
     API.SW_ReleaseString(ptr); 
     return result; 
    } 

    public static int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, ref string outputSWML) 
    { 
     IntPtr outputSWML_out = new IntPtr(); 
     int result = API.SW_DealGetSWML(lh, swmlVersion, dealVersionHandle, out outputSWML_out); 

     outputSWML = GetIntPtrStringAndRelease(outputSWML_out); 

     return result; 
    } 

    public static string SW_GetLastErrorSpecifics() 
    { 
     IntPtr ptr = API.SW_GetLastErrorSpecifics(); 
     return GetIntPtrStringAndRelease(ptr); 
    } 

Parece que no puedo conseguir la API para liberar las cuerdas.

Ahora, es posible que esto sea solo un error en la API, pero lo dudo. Es más probable que esté haciendo algo fundamentalmente erróneo.

Todo lo que sé es que mi conjunto de trabajo sigue creciendo.

La empresa en cuestión proporciona un contenedor Java pero no se extiende a un contenedor .Net.

Cualquier ayuda recibida con más gratitud.

Brett.

+0

Esto parece un error en la API para mí. Si los datos que está obteniendo (es decir, el valor devuelto por 'GetIntPtrStringAndRelease' es bueno), entonces esto debería funcionar. –

+0

'GetIntPtrStringAndRelease' se ve bien para mí. –

+0

El valor que obtengo es bueno. ¡Casi me gustaría que no fuera así! – Brett

Respuesta

2

Mi mejor estimación es que el IntPtr no es equivalente al carácter * de su cadena. Entonces, cuando llamas a SW_ReleaseString, no estás proporcionando el mismo puntero.

Lo que puede hacer es juntar un pequeño intermediario C++ CLI. En CLI de C++, y tendrá acceso directamente al char*, además de poder utilizar Marshal :: PtrToString y punteros de cadena administrados, con String^.

Aquí es lo que creo que se vería así:

C++/CLI:

String^ GetStringAndRelease(char* ptr) 
{ 
    string result = Marshal::PtrToStringAnsi(ptr); 
    SW_ReleaseString(ptr); 
    return result; 
} 

int SW_DealGetSWML(int lh, const char* swmlVersion, const char* dealVersionHandle, String% outputSWML) 
{ 
    char* outputSWML_out; 
    int result = SW_DealGetSWML(lh, swmlVersion, dealVersionHandle, outputSWML_out); 

    outputSWML = GetStringAndRelease(outputSWML_out); 

    return result; 
} 

String^ SW_GetLastErrorSpecifics() 
{ 
    char* ptr = SW_GetLastErrorSpecifics(); 
    return GetStringAndRelease(ptr); 
} 

y luego en C#:

[DllImport(your_wrapper_dll, EntryPoint = "[email protected]", CharSet = CharSet.Ansi)] 
public static extern int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, [Out] out string outputSWML); 


[DllImport(your_wrapper_dll, EntryPoint = "[email protected]", CharSet = CharSet.Ansi)] 
public static extern string SW_GetLastErrorSpecifics(); 
+2

Si está creando una DLL C++/CLI, no necesita DllImport. Es un ensamblado de .NET completo, usted agregaría una referencia a él de la misma manera que agregaría una referencia a un ensamble C# externo, no es necesario DllImports. (C# 'ref string' se convertiría en C++/CLI' String ^% ', BTW.) –

+0

Impresionante, gracias @David!Mi única exposición a C++/CLI es un puente entre C# y .NET, y eso es solo una exposición reciente. – Tim

+0

Cuando hice algo similar (hace mucho tiempo, alrededor de .Net 1.1) tuve que escribir un código que iba en contra de una DLL estándar C. Me pareció más fácil escribir un componente C++/COM + para actuar como un contenedor en lugar de intentar utilizar 'DllImport' en C#. Un dll C++/CLI es probablemente el camino a seguir. –

0

Soy un tipo C#, no es un C++ chico, pero en las DLL de C++ no administradas con las que trabajo que usan parámetros char *, las identifico como StringBuilders. Leí en alguna parte que para const char * la mejor opción es System.String pero StringBuilder para char *. Sin embargo, si necesita mantener el puntero para poder enviarlo de vuelta para liberar la memoria, ¿podría ser que StringBuilder funcione mejor ya que System.String es inmutable?

+0

Gracias por la respuesta, @Joel. Intenté esto (muy brevemente) y el proceso explotó. Sin embargo, podría haber habido un número de cosas mal con la forma en que lo implementé. Cuando tenga un poco de espacio para respirar, tendré otra oportunidad. – Brett