resueltos - Libere la memoria no administrada del C#administrado con su puntero
punteros y matrices en c (3)
La pregunta en palabras cortas es: ¿Cómo liberar la memoria devuelta desde Native DLL como ItrPtr en el código administrado?
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 Int de referencia. La función asignará la cantidad de bytes en función de 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 siguiente código funciona bien y puedo obtener la matriz de bytes correctamente y el recuento de bytes y el valor de retorno, pero cuando intento liberar la memoria con el puntero (IntPtr) obtengo una 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 aclarar las cosas:
El siguiente código C # funciona correctamente con otra función DLL tiene la misma firma y la liberación de la memoria funciona sin ningún problema.
Cualquier modificación en el código (C) se acepta si necesita cambiar el método de memoria de asignación o agregar cualquier otro código.
Toda la funcionalidad que necesito es Native DLL function accept Two Parameter por referencia (Byte array e int, In c # [IntPtr of byte array and int]) llenarlos con algunos valores basados en algunas reglas y devolver el resultado de la función (Success or Fail) .
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 # code:
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 depuro este código, establezco el punto de ruptura en la línea (retorno 1) y el valor del buffer 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 Conseguí el puntero de memoria correcto y puedo obtener el valor del arreglo de bytes, ¿cómo liberar estos bloques de memoria con este puntero en C #?
Por favor, avíseme si algo no está claro o si hay algún error tipográfico, no soy hablante nativo de inglés.
Respuesta corta: debe agregar un método separado en la DLL que libera la memoria por 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[]
(en lugar 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;
}
HeapFree(GetProcessHeap(), 0, byteArrayPointer);
No, eso no puede funcionar. 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 equivocado, el CRT puede haber agregado alguna información adicional del puntero devuelto por HeapAlloc () para almacenar datos de depuración.
Necesitará exportar una función que llame a eliminar [] 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 versión de la DLL CRT (se requiere MD). Esto casi siempre requiere que puedas recompilar el código C ++.
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 forma de compartir 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 utilizar la memoria asignada con CoTaskMemAlloc
con un objeto C ++ nativo, puede usar la colocación nueva para inicializar la memoria como si se hubiera utilizado el new
operador. Por ejemplo:
void * p = CoTaskMemAlloc(sizeof(MyType));
MyType * pMyType = new (p) MyType;
Esto no asigna memoria con new
just llamadas al constructor en la memoria preasignada.
Llamando 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 haga eso 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.