2012-09-05 34 views
10

La pregunta en palabras cortas es: ¿Cómo liberar la memoria devuelta desde el DLL nativo como ItrPtr en el código administrado?Liberar la memoria no administrada del C# administrado con el puntero del mismo

Detalles: Supongamos que tenemos una función simple toma dos parámetros como SALIDA, El primero es el Puntero de referencia a la matriz de bytes y el segundo es Referencia Int. La función asignará la cantidad de bytes según algunas reglas y devolverá el puntero de la memoria y el tamaño de los bytes y el valor de retorno (1 para el éxito y 0 para el error).

El código siguiente funciona bien y puedo obtener la matriz de bytes correctamente y el recuento de bytes y el valor de retorno, pero cuando intento para liberar la memoria utilizando el puntero (IntPtr) me sale excepción:

Windows ha desencadenado un punto de interrupción en TestCppDllCall.exe.

Esto puede deberse a una corrupción del montón, que indica un error en TestCppDllCall.exe o cualquiera de las DLL que ha cargado.

Esto también puede deberse a que el usuario presiona F12 mientras TestCppDllCall.exe tiene foco.

La ventana de salida puede tener más información de diagnóstico.

Para dejar las cosas claras:

  1. El siguiente código C# funciona correctamente con otra función DLL tienen la misma firma y liberar la memoria funciona sin ningún problema.

  2. Se acepta cualquier modificación en el código (C) si necesita cambiar el método de memoria de asignación o agregar cualquier otro código.

  3. Toda la funcionalidad que necesito es la función nativa DLL aceptar dos parámetros por referencia (matriz de bytes y int, en C# [IntPtr de byte array y int]) llenarlos con algunos valores basados ​​en algunas reglas y devolver el resultado de la función (Éxito o fracaso).


CppDll.h

#ifdef CPPDLL_EXPORTS 
#define CPPDLL_API __declspec(dllexport) 
#else 
#define CPPDLL_API __declspec(dllimport) 
#endif 

extern "C" CPPDLL_API int writeToBuffer(unsigned char *&myBuffer, int& mySize); 

CppDll.CPP

#include "stdafx.h" 
#include "CppDll.h" 

extern "C" CPPDLL_API int writeToBuffer(unsigned char*& myBuffer, int& mySize) 
{ 
    mySize = 26; 

    unsigned char* pTemp = new unsigned char[26]; 
    for(int i = 0; i < 26; i++) 
    { 
     pTemp[i] = 65 + i; 
    } 
    myBuffer = pTemp; 
    return 1; 
} 

C# código:

using System; 
using System.Text; 
using System.Runtime.InteropServices; 

namespace TestCppDllCall 
{ 
    class Program 
    { 
     const string KERNEL32 = @"kernel32.dll"; 
     const string _dllLocation = @"D:\CppDll\Bin\CppDll.dll"; 
     const string funEntryPoint = @"writeToBuffer"; 

     [DllImport(KERNEL32, SetLastError = true)] 
     public static extern IntPtr GetProcessHeap(); 
     [DllImport(KERNEL32, SetLastError = true)] 
     public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem); 
     [DllImport(_dllLocation, EntryPoint = funEntryPoint, CallingConvention = CallingConvention.Cdecl)] 
     public static extern int writeToBuffer(out IntPtr myBuffer, out int mySize); 

     static void Main(string[] args) 
     { 
      IntPtr byteArrayPointer = IntPtr.Zero; 
      int arraySize; 
      try 
      { 
       int retValue = writeToBuffer(out byteArrayPointer, out arraySize); 
       if (retValue == 1 && byteArrayPointer != IntPtr.Zero) 
       { 
        byte[] byteArrayBuffer = new byte[arraySize]; 
        Marshal.Copy(byteArrayPointer, byteArrayBuffer, 0, byteArrayBuffer.Length); 
        string strMyBuffer = Encoding.Default.GetString(byteArrayBuffer); 
        Console.WriteLine("Return Value : {0}\r\nArray Size : {1}\r\nReturn String : {2}", 
         retValue, arraySize, strMyBuffer); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Error calling DLL \r\n {0}", ex.Message); 
      } 
      finally 
      { 
       if (byteArrayPointer != IntPtr.Zero) 
        HeapFree(GetProcessHeap(), 0, byteArrayPointer); 
      } 
      Console.ReadKey(); 
     } 
    } 
} 

Cuando depurar este código i conjunto de puntos de ruptura en la línea (retorno 1) y el valor del tampón fue:

myBuffer = 0x031b4fc0 "ABCDEFGHIJKLMNOPQRSTUVWXYZ‎‎‎‎««««««««î‏" 

Y obtuve el mismo valor en el código C# cuando la función devolvió la llamada y el valor fue:

52121536 

El resultado Obtuve el puntero de Memoria correcto y puedo obtener el valor del conjunto de bytes, ¿cómo liberar estos bloques de memoria con este puntero en C#?

Háganme saber si algo no está claro o si hay algún error tipográfico, no soy hablante nativo de inglés.

Respuesta

13

Respuesta corta: debe agregar un método separado en la DLL que libera la memoria para usted.

Respuesta larga: hay diferentes formas en que se puede asignar la memoria dentro de su implementación de DLL. La forma de liberar la memoria debe coincidir con la forma en que ha asignado la memoria. Por ejemplo, la memoria asignada con new[] (con corchetes) debe liberarse con delete[] (a diferencia de delete o free). C# no proporciona un mecanismo para que lo haga; necesita enviar el puntero a C++.

extern "C" CPPDLL_API void freeBuffer(unsigned char* myBuffer) { 
    delete[] myBuffer; 
} 
+0

Gracias por su rápida respuesta. Esperaba que haya una manera de liberar memoria no administrada en C# directamente. – khaled

+0

Estoy de acuerdo con dasblinkenlight: la forma * más limpia * es sin duda agregar un método "libre" a su .dll. Pero Peter Ritchie también está en lo cierto: ciertamente es posible que C# invoque (a través de la interoperabilidad) un "libre" correspondiente (por ejemplo, 'FreeCoTaskMem()') a sin embargo el .dll asignado (por ejemplo 'CoTaskMemAlloc()'). Una cosa que * no * se puede hacer desde C# es "eliminar", si el .dll hace un C++ "nuevo". "malloc/free": OK. CoTaskMemAlloc/FreeCoTaskMem: también está bien. "Nuevo/gratis": definitivamente * NO *. – paulsm4

+0

@ paulsm4 Por favor, explique cómo invocar en C# malloc/free? – awattar

3
HeapFree(GetProcessHeap(), 0, byteArrayPointer); 

No, eso no puede trabajar. El identificador del montón está mal, el CRT crea su propio montón con HeapCreate(). Está enterrado en los datos de CRT, no puedes acceder a él. Técnicamente puede encontrar el manejador de GetProcessHeaps(), excepto que no sabe cuál es.

El puntero también puede estar mal, el CRT puede haber agregado alguna información adicional del puntero devuelto por HeapAlloc() para almacenar datos de depuración.

Deberá exportar una función que invoca delete [] para liberar el búfer. O escriba un contenedor C++/CLI para que pueda usar delete [] en el contenedor. Con el requisito adicional de que el código C++ y el contenedor utilicen la misma misma versión de la DLL CRT (se requiere/MD). Esto casi siempre requiere que puedas recompilar el código C++.

7

Si está asignando su propia memoria en código nativo, use CoTaskMemAlloc y puede liberar el puntero en código administrado con Marshal.FreeCoTaskMem. CoTaskMemAlloc se describe como "la única manera de compartir la memoria en una aplicación basada en COM" (ver http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx)

si necesita usar la memoria asignada con CoTaskMemAlloc con un nativo de C++ objeto, puede utilizar colocación de nueva a Inicialice la memoria como si se hubiera usado el operador new. Por ejemplo:

void * p = CoTaskMemAlloc(sizeof(MyType)); 
MyType * pMyType = new (p) MyType; 

Esta memoria no asigna con new simplemente llama al constructor de la pre-memoria.

Llamar a Marshal.FreeCoTaskMem no llama al destructor del tipo (que no es necesario si solo necesita liberar memoria); si necesita hacer algo más que memoria libre llamando al destructor, tendrá que proporcionar un método nativo que lo haga y P/invocarlo. No se admite pasar instancias de clase nativa a código administrado de todos modos.

Si necesita asignar memoria con alguna otra API, deberá exponerla en código administrado a través de P/Invoke para liberarla en código administrado.

+0

+1 - CoTaskMemAlloc es la forma oficial de hacer coincidir las asignaciones gestionadas/no gestionadas (o cualquier otro lenguaje cruzado) en Windows. –

Cuestiones relacionadas