2012-09-05 39 views
10

Câu hỏi trong các từ ngắn gọn là: Làm thế nào để giải phóng bộ nhớ được trả về từ Native DLL như ItrPtr trong mã được quản lý?Phát hành bộ nhớ không được quản lý từ quản lý C# bằng con trỏ của nó

Chi tiết: Giả sử chúng ta có hàm đơn giản nhận hai tham số là OUTPUT, Giá trị đầu tiên là Pointer tham chiếu đến mảng byte và tham số thứ hai là tham chiếu Int. Hàm sẽ phân bổ số byte dựa trên một số quy tắc và trả về con trỏ của bộ nhớ và kích thước của byte và giá trị trả về (1 cho thành công và 0 cho thất bại).

Đoạn code dưới đây hoạt động tốt và tôi có thể nhận được các mảng byte một cách chính xác và đếm byte và giá trị trả về, nhưng khi tôi cố gắng để giải phóng bộ nhớ bằng cách sử dụng con trỏ (IntPtr) tôi nhận được ngoại lệ:

Windows đã kích hoạt điểm ngắt trong TestCppDllCall.exe.

Điều này có thể do hỏng heap, cho biết lỗi trong tệp TestCppDllCall.exe hoặc bất kỳ tệp DLL nào mà nó đã tải.

Điều này cũng có thể do người dùng nhấn F12 trong khi TestCppDllCall.exe đã tập trung.

Cửa sổ đầu ra có thể có thêm thông tin chẩn đoán.

Để làm cho mọi việc rõ ràng:

  1. Tiếp theo C# code làm việc một cách chính xác với chức năng DLL khác có chữ ký giống nhau và giải phóng bộ nhớ hoạt động mà không có vấn đề gì.

  2. Bất kỳ sửa đổi nào trong mã (C) được chấp nhận nếu bạn cần thay đổi phương thức bộ nhớ phân bổ hoặc thêm bất kỳ mã nào khác.

  3. Tất cả chức năng tôi cần là hàm DLL gốc chấp nhận hai tham số bằng tham chiếu (mảng byte và int, trong C# [IntPtr của mảng byte và int]) điền chúng với một số giá trị dựa trên một số quy tắc và trả về kết quả hàm (Thành công hay Thất bại).


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

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

Khi tôi gỡ lỗi mã này tôi đặt chia điểm trong dòng (return 1) và giá trị của bộ đệm là:

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

Và tôi có cùng giá trị trong mã C# khi hàm trả về cuộc gọi hàm và giá trị là:

52121536 

Kết quả Tôi có con trỏ Bộ nhớ chính xác và tôi có thể nhận giá trị mảng byte, cách giải phóng các khối bộ nhớ này bằng con trỏ này trong C#?

Vui lòng cho tôi biết nếu có bất kỳ điều gì không rõ ràng hoặc nếu có lỗi đánh máy, tôi không phải là người nói tiếng Anh bản ngữ.

Trả lời

13

Câu trả lời ngắn: bạn nên thêm một phương thức riêng biệt trong DLL giải phóng bộ nhớ cho bạn.

Câu trả lời dài: có nhiều cách khác nhau trong đó bộ nhớ có thể được cấp phát bên trong việc triển khai DLL của bạn. Cách bạn giải phóng bộ nhớ phải khớp với cách bạn đã cấp phát bộ nhớ. Ví dụ: bộ nhớ được phân bổ với new[] (có dấu ngoặc vuông) cần được giải phóng với delete[] (ngược với delete hoặc free). C# không cung cấp một cơ chế để bạn làm điều đó; bạn cần gửi con trỏ trở lại C++.

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

Cảm ơn bạn đã phản hồi nhanh. Tôi hy vọng rằng có một cách để giải phóng bộ nhớ không được quản lý trong C# trực tiếp. – khaled

+0

Tôi đồng ý với dasblinkenlight: cách * cleanest * được cho là thêm phương thức "miễn phí" vào .dll của bạn. Nhưng Peter Ritchie cũng đúng: chắc chắn C# có thể gọi (qua interop) một "miễn phí" tương ứng (ví dụ: 'FreeCoTaskMem()') tuy nhiên phân bổ .dll (ví dụ: CoTaskMemAlloc() '). Một điều bạn * không thể * làm từ C# là "xóa", nếu dll thực hiện một C++ "mới". "malloc/free": OK. CoTaskMemAlloc/FreeCoTaskMem: cũng OK. "Mới/miễn phí": chắc chắn * KHÔNG *. – paulsm4

+0

@ paulsm4 Vui lòng giải thích cách gọi trong C# malloc/miễn phí? – awattar

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

Không, đó không thể làm việc. Trình xử lý heap là sai, CRT tạo ra heap của riêng nó với HeapCreate(). Nó được chôn trong dữ liệu CRT, bạn không thể đến được nó. Bạn về mặt kỹ thuật có thể tìm thấy các xử lý trở lại từ GetProcessHeaps(), ngoại trừ bạn không biết đó là một trong những.

Con trỏ có thể sai, CRT có thể đã thêm một số thông tin bổ sung từ con trỏ được trả về bởi HeapAlloc() để lưu trữ dữ liệu gỡ lỗi.

Bạn cần xuất một hàm gọi xóa [] để giải phóng bộ đệm. Hoặc viết trình bao bọc C++/CLI để bạn có thể sử dụng delete [] trong trình bao bọc. Với yêu cầu thêm rằng mã C++ và trình bao bọc sử dụng chính xác cùng một phiên bản của CRT DLL (/ MD bắt buộc). Điều này hầu như luôn luôn yêu cầu bạn có thể biên dịch lại mã C++.

7

Nếu bạn đang cấp phát bộ nhớ của riêng mình bằng mã gốc, hãy sử dụng CoTaskMemAlloc và bạn có thể giải phóng con trỏ trong mã được quản lý với Marshal.FreeCoTaskMem. CoTaskMemAlloc được mô tả là "cách duy nhất để chia sẻ bộ nhớ trong một ứng dụng COM-based" (xem http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx)

nếu bạn cần phải sử dụng bộ nhớ được phân bổ với CoTaskMemAlloc với một bản ngữ C++ đối tượng, bạn có thể sử dụng vị trí mới để khởi tạo bộ nhớ như thể toán tử new đã được sử dụng. Ví dụ:

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

Điều này không cấp phát bộ nhớ với new chỉ gọi hàm tạo trên bộ nhớ được phân bổ trước.

Gọi Marshal.FreeCoTaskMem không gọi hàm hủy của loại (điều này không cần thiết nếu bạn chỉ cần giải phóng bộ nhớ); nếu bạn cần phải làm nhiều hơn bộ nhớ miễn phí bằng cách gọi hàm hủy, bạn sẽ phải cung cấp một phương thức gốc để thực hiện điều đó và P/Gọi nó. Tuy nhiên, việc chuyển các phiên bản lớp gốc sang mã được quản lý không được hỗ trợ.

Nếu bạn cần cấp phát bộ nhớ với một số API khác, bạn cần phải hiển thị nó trong mã được quản lý thông qua P/Invoke để giải phóng bộ nhớ đó trong mã được quản lý.

+0

+1 - CoTaskMemAlloc là cách chính thức để phù hợp với phân bổ quản lý/không được quản lý (hoặc bất kỳ ngôn ngữ khác) trong Windows. –

Các vấn đề liên quan