2012-09-05 17 views
10

Pytanie w skrócie to: Jak zwolnić pamięć zwróconą przez Native DLL jako ItrPtr w zarządzanym kodzie?Zwolnij pamięć niezarządzaną z zarządzanego C# za pomocą wskaźnika

Szczegóły: Szczegóły: Założenie, że mamy funkcję prostą, przyjmuje dwa parametry jako WYJŚCIE, Pierwszym z nich jest wskaźnik odniesienia do tablicy bajtów, a drugim jest Reference Int. Funkcja przydzieli ilość bajtów na podstawie niektórych reguł i zwróci wskaźnik pamięci oraz rozmiar bajtów i wartość zwracaną (1 dla sukcesu i 0 dla błędu).

Poniższy kod działa poprawnie i mogę dostać tablicę bajtów prawidłowo i liczbę bajtów i wartości zwracanej, ale gdy próbuję uwolnić pamięć za pomocą wskaźnika (IntPtr) otrzymuję wyjątek:

System Windows uruchomił punkt przerwania w pliku TestCppDllCall.exe.

Może to być spowodowane uszkodzeniem sterty, co oznacza błąd w pliku TestCppDllCall.exe lub dowolnej z bibliotek DLL, które zostały załadowane.

Przyczyną może być również naciśnięcie klawisza F12 przez naciśnięcie przycisku TestCppDllCall.exe.

Okno wyjściowe może zawierać więcej informacji diagnostycznych.

Żeby było jasne:

  1. Następny C# kod działa poprawnie z innej funkcji DLL mają tę samą sygnaturę i uwalniając pamięć działa bez problemu.

  2. Każda zmiana w kodzie (C) jest akceptowana, jeśli zachodzi potrzeba zmiany metody pamięci alokacji lub dodania dowolnego innego kodu.

  3. Wszystkie funkcje potrzebne jest rodzimy funkcji DLL przyjąć dwa parametr odniesienia (tablicę bajtów oraz int w C# [IntPtr z tablicy bajtów i int]) wypełnić je z niektórych wartości opartych na regułach i zwrócić wynik funkcji (Sukces lub porażka).


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#:

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(); 
     } 
    } 
} 

Kiedy debugowania kod ustawić przerwać punkt na linii (powrót 1) i wartość buforu:

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

I otrzymałem tę samą wartość w kodzie C#, gdy zwrot wezwania do funkcji i wartość była:

52121536 

Wynik Mam poprawny wskaźnik pamięci i jestem w stanie uzyskać wartość tablicy bajtów, jak zwolnić te bloki pamięci za pomocą tego wskaźnika w C#?

Proszę dać mi znać, jeśli coś nie jest jasne lub jeśli jest literówka, nie jestem native speakerem języka angielskiego.

Odpowiedz

13

Krótka odpowiedź: w bibliotece DLL należy dodać osobną metodę, która zwalnia pamięć.

Długa odpowiedź: istnieją różne sposoby przydzielania pamięci wewnątrz implementacji DLL. Sposób, w jaki zwolnisz pamięć, musi pasować do sposobu przydzielenia pamięci. Na przykład pamięć przydzielona z new[] (z nawiasami kwadratowymi) musi zostać zwolniona przy pomocy delete[] (w przeciwieństwie do delete lub free). C# nie zapewnia mechanizmu, aby to zrobić; musisz wysłać wskaźnik z powrotem do C++.

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

Dziękuję za szybką odpowiedź. Mam nadzieję, że istnieje sposób na uwolnienie niezarządzanej pamięci w C# bezpośrednio. – khaled

+0

Zgadzam się z dasblinkenlight: * najczystszym * sposobem jest prawdopodobnie dodanie "darmowej" metody do .dll. Ale Peter Ritchie też ma rację: jest oczywiście możliwe, że C# wywoła (poprzez interop) odpowiedni "wolny" (np. 'FreeCoTaskMem()') do jednak przydzielonego .dll (np. 'CoTaskMemAlloc()'). Jedną z rzeczy, których nie możesz * zrobić z C# jest "delete", jeśli .dll robi C++ "nowy". "malloc/free": OK. CoTaskMemAlloc/FreeCoTaskMem: również OK. "Nowy/bezpłatny": zdecydowanie * NIE *. – paulsm4

+0

@ paulsm4 Proszę wyjaśnić, jak wywołać w C# malloc/free? – awattar

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

Nie, to nie może pracować. Uchwyt sterty jest nieprawidłowy, CRT tworzy własną stertę za pomocą HeapCreate(). Jest pochowany w danych CRT, nie możesz się do niego dostać. Technicznie można znaleźć uchwyt od GetProcessHeaps(), ale nie wiesz, który to jest.

Wskaźnik może być również błędny, CRT może dodać dodatkowe informacje z wskaźnika zwróconego przez HeapAlloc(), aby zapisać dane debugowania.

Musisz wyeksportować funkcję, która wywołuje delete [], aby zwolnić bufor. Lub napisz opakowanie C++/CLI, aby można było użyć polecenia delete [] w opakowaniu. Z dodatkowym wymogiem, że kod C++ i owijka używają dokładną taką samą wersję CRT DLL (/ MD wymagany). To prawie zawsze wymaga rekompilacji kodu C++.

7

Jeśli przydzielasz własną pamięć w natywnym kodzie, użyj CoTaskMemAlloc i możesz zwolnić wskaźnik w zarządzanym kodzie za pomocą Marshal.FreeCoTaskMem. CoTaskMemAlloc jest opisany jako „jedyny sposób dzielenia pamięci w aplikacji COM oparte” (patrz http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx)

jeśli chcesz korzystać z pamięci przydzielonej z CoTaskMemAlloc z natywnym C++ obiektu, można użyć placement new do zainicjuj pamięć tak, jakby użyto operatora new. Na przykład:

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

Ta pamięć nie przeznaczyć z new po prostu wywołuje konstruktora na wstępnie przyznane w pamięci.

Wywołanie Marshal.FreeCoTaskMem nie wywołuje destruktora typu (co nie jest potrzebne, jeśli potrzebujesz tylko zwolnić pamięć); jeśli chcesz zrobić więcej niż zwolnić pamięć wywołując destruktor, musisz podać natywną metodę, która to zrobi i wywołaj P/Invoke. Przekazywanie instancji klas natywnych do kodu zarządzanego nie jest tak czy inaczej obsługiwane.

Jeśli musisz przydzielić pamięć innym API, musisz pokazać go w zarządzanym kodzie za pomocą P/Invoke, aby zwolnić pamięć w kodzie zarządzanym.

+0

+1 - CoTaskMemAlloc to oficjalny sposób na dopasowanie zarządzanych/niezarządzanych (lub innych języków) alokacji w systemie Windows. –