2009-12-19 18 views
6

Ứng dụng .NET gọi Cll. Mã C cấp phát bộ nhớ cho mảng char và trả về mảng này dưới dạng kết quả. Các ứng dụng .NET nhận kết quả này dưới dạng một chuỗi.Phân bổ bộ nhớ không được quản lý miễn phí từ mã được quản lý

Mã C:

extern "C" __declspec(dllexport) char* __cdecl Run() 
{ 
    char* result = (char*)malloc(100 * sizeof(char)); 
    // fill the array with data 
    return result; 
} 

C# mã:

[DllImport("Unmanaged.dll")] 
private static extern string Run(); 

... 
string result = Run(); 
// do something useful with the result and than leave it out of scope 

Một số xét nghiệm của nó cho thấy rằng các bộ thu rác không giải phóng bộ nhớ được phân bổ bởi mã C.

Mọi trợ giúp sẽ được đánh giá cao. :)

Trả lời

6

Managed là không giống như char *. Điều gì xảy ra bí mật là mã marshaling trong lớp interop tạo một bản sao của chuỗi không được quản lý để chuyển đổi nó thành một chuỗi được quản lý, nhưng nó không thể giải phóng bộ nhớ đó vì nó không biết nó được cấp phát như thế nào.

Tuy nhiên, bạn có thể thử phân bổ và trả lại BSTR thay vì char *. Lớp interop giao dịch tốt hơn với các kiểu dữ liệu tự động hóa hơn các kiểu dữ liệu không được quản lý cổ điển.

Lý do tại sao vấn đề là cách char * và BSTR được cấp phát trong bộ nhớ.

Bộ đệm char * được cấp phát trên bộ đệm thời gian chạy C++ sử dụng các quy trình phân bổ/phân bổ riêng tư mà CLR không biết gì, vì vậy không có cách nào để xóa bộ nhớ đó. Và để làm cho mọi thứ trở nên tồi tệ hơn, bộ đệm mà các điểm char * có thể được cấp phát bởi việc triển khai heap nội bộ của mã dll, hoặc thậm chí nó có thể trỏ đến biến thành viên trong một lớp riêng.Mặt khác, BSTRs được phân bổ bằng cách sử dụng WIndows API SysAllocString và được giải phóng bởi SyFreeStirng và vì lớp tương tác CLR biết về các API Windows này, nó biết cách giải phóng BSTR mà nó nhận được từ mã không được quản lý.

+0

Cảm ơn bạn. Hoạt động tốt. – Alex

6

Bạn không thể giải phóng bộ nhớ không được quản lý khỏi mã được quản lý. Bạn cần phải viết một thường trình trong C gọi free trên con trỏ được trả về bởi hàm Run và P/Gọi nó từ .NET.

Một lựa chọn khác là để cấp phát bộ nhớ không được quản lý trong .NET, vượt qua con trỏ đến chức năng C mà sẽ lấp đầy nó với dữ liệu và cuối cùng là tự do con trỏ này:

IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char)); 
SomeUnmanagedFunc(ptr); 
Marshal.FreeHGlobal(ptr); 
+0

Ông có thể cung cấp một ví dụ đơn giản, xin vui lòng. – Alex

+1

Việc cấp phát bộ nhớ trong ứng dụng .NET giả định rằng chúng ta biết trước kích thước của mảng. Trong giải pháp thực sự (vấn đề một) nó là tổng bí ẩn :). – Alex

+0

@Darin - Bạn nghĩ phương pháp đầu tiên sẽ hoạt động như thế nào? Có bất kỳ thủ thuật hay điều gì đó phải được thực hiện. Tôi có nên quấn cuộc gọi đến bộ nhớ không được quản lý hoạt động miễn phí trong phương pháp IDisposable cho lớp của tôi xử lý bộ nhớ không được quản lý? –

-1

NET bộ nhớ PHẢI được phân bổ trong CLR được xóa bởi GC. Bạn cần thêm một hàm để giải phóng khối trong DLL C.

Hãy nhớ giải phóng bộ nhớ trong cùng một phiên bản của C DLL đã tạo bộ nhớ. Bạn không thể trộn và kết hợp.

+1

Điều đó không đúng. CLR biết cách giải phóng một số loại khối bộ nhớ được cấp phát từ mã không được quản lý, ví dụ bộ nhớ được cấp phát bằng SysAllocString hoặc CoTaskMemAlloc. –

3

Một cách khác để thực hiện điều đó là chuyển một chuỗi được quản lý (một cá thể StringBuilder) thông qua P/Invoke (như một tham số cho hàm Run) của bạn.

Bằng cách đó, không có phân bổ nào được thực hiện ở phía không được quản lý.

Nói cách khác, bạn sẽ có cái gì như:

extern "C" __declspec(dllexport) void __cdecl Run(char* data) 
{ 
    // fill the array with data 
    // no return value (void) 
} 

và gọi nó là như thế này:

chuỗi
[DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)] 
static extern void Run(StringBuilder result); 

StringBuilder result = new StringBuilder(100); 
Run(result); 
+0

Khi câu trả lời của Darin này sẽ làm việc nếu kích thước của bộ nhớ cần thiết được biết đến. –

7

P/Invoke marshaller sẽ giả định rằng bộ nhớ cho kiểu trả về đã được cấp phát với CoTaskMemAlloc() và sẽ gọi CoTaskMemFree() để giải phóng nó. Nếu điều này đã không được thực hiện, chương trình sẽ thất bại với một ngoại lệ trên Vista và Win7 nhưng âm thầm rò rỉ bộ nhớ trên XP. Sử dụng SysAllocString() có thể được thực hiện để làm việc nhưng bạn phải chú thích kiểu trả về trong thuộc tính [DllImport]. Không làm như vậy sẽ vẫn gây ra rò rỉ, không có chẩn đoán trên Win7. BSTR là không phải là một con trỏ tới khối bộ nhớ được CoTaskMemAlloc phân bổ, có 4 byte ở phía trước địa chỉ được trỏ tới để lưu trữ kích thước chuỗi.

Một trong các kết hợp sau đây sẽ làm việc đúng cách:

extern "C" __declspec(dllexport) 
BSTR __stdcall ReturnsAString() { 
    return SysAllocString(L"Hello world"); 
} 

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll")] 
[return: MarshalAs(UnmanagedType.BStr)] // NOTE: required! 
private static extern string ReturnsAString(); 

Hoặc:

extern "C" __declspec(dllexport) 
const wchar_t* __stdcall ReturnsAString() { 
    const wchar_t* str = L"Hello world"; 
    wchar_t* retval = (wchar_t*)CoTaskMemAlloc((wcslen(str)+1) * sizeof(wchar_t)); 
    wcscpy(retval, str); 
    return retval; 
} 

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)] 
private static extern string ReturnsAString(); 

Bạn nên xem xét cho phép các mã khách hàng để vượt qua một bộ đệm vì vậy không có vấn đề quản lý bộ nhớ. Điều đó phải giống như thế này:

extern "C" __declspec(dllexport) 
void __stdcall ReturnsAString(wchar_t* buffer, size_t buflen) { 
    wcscpy_s(buffer, buflen, L"Hello world"); 
} 

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)] 
private static extern void ReturnsAString(StringBuilder buffer, int buflen); 
... 
    StringBuilder sb = new StringBuilder(256); 
    ReturnsAString(sb, sb.Capacity); 
    string s = sb.ToString(); 
2

Tôi đã đọc một số câu hỏi về PInvoke và tôi dừng lại ở đây. Tôi không biết nếu vấn đề vẫn còn liên quan đến bạn nhưng tôi quyết định đăng câu trả lời của tôi cho độc giả trong tương lai.

Đó là câu trả lời cuối cùng của bạn cho câu trả lời của Darin Dimitrov. Khi kích thước của bộ nhớ được cấp phát không được biết, giải pháp tipical là gọi hàm không được quản lý bằng một con trỏ null và nhận kích thước trong một tham số ngoài. Sau đó, chúng tôi phân bổ không gian cần thiết và gọi lại chức năng không được quản lý.

dụ dưới đây:

//MANAGED SIDE 
IntPtr ptr = IntPtr.Zero; 
int size = 0; 
myFunc(ptr, out size); 
ptr = Marshal.AllocHGlobal(size); 
myFunc(ptr, out size); 
//Do your work.. 
Marshal.FreeHGlobal(ptr); 



//UNMANEGED SIDE 
int myFunc(void* dest, size_t& size){ 
    if(dest==NULL) 
     //calculate de size.. 
     size = 'resul' 
     return 0; 
    } 
    // create the array and copy all elements 
    memcopy(dest, ... , size); 
    //free all allocated space in unmanaged and return success 
    return 0; 
} 
+0

Cách tiếp cận khá thú vị nhưng nó giả định rằng thời gian thực thi của mã được quản lý rất nhanh nên chúng ta có thể gọi nó hai lần. Trong thực tế mã được quản lý có thể a) thực thi chậm b) trả về các tập kết quả hơi khác nhau để bộ nhớ được cấp phát sẽ không đủ –