2013-08-22 44 views
5

Tôi đã được học về marshaling nhập DLL không quản lý vào C# ... Và tôi đã đi qua một cái gì đó tôi không hoàn toàn hiểu.Chuỗi Delphi DLL trả về từ C# ... .NET 4.5 Heap Corruption nhưng .NET 4.0 hoạt động? Vui lòng giải thích?

Trong Delphi, có một chức năng mà đang trở lại Result := NewStr(PChar(somestring)) từ một Procedure SomeFunc() : PChar; Stdcall;

Từ hiểu biết của tôi, newstr chỉ phân bổ một bộ đệm trên heap địa phương ... và SomeFunc đang trở lại một con trỏ đến nó.

Trong .NET 4.0 (Client Profile), qua C# Tôi có thể sử dụng:

[DllImport("SomeDelphi.dll", EntryPoint = "SomeFunc", CallingConvention = CallingConvention.StdCall)] 
public static extern String SomeFunc(uint ObjID); 

này hoạt động (hoặc như David nói, "xuất hiện để làm việc") tốt trong Windows 7 .NET 4.0 Client Profile. Trong Windows 8, nó có hành vi không thể đoán trước, điều đó đã khiến tôi rơi xuống con đường này.

Vì vậy, tôi đã quyết định thử cùng một mã trong .NET 4.5 và có lỗi tham nhũng Heap. Được rồi, vì vậy bây giờ tôi biết đây không phải là cách chính xác để làm việc. Vì vậy, tôi đào thêm:

Vẫn trong .NET 4,5

[DllImport("SomeDelphi.dll", EntryPoint = "SomeFunc", CallingConvention = CallingConvention.StdCall)] 
public static extern IntPtr _SomeFunc(); 
public static String SomeFunc() 
{ 
    IntPtr pstr = _SomeFunc(); 
    return Marshal.PtrToStringAnsi(pstr); 
} 

này hoạt động mà không có một xô. Mối quan tâm của tôi (người mới) là NewStr() đã phân bổ bộ nhớ này và nó chỉ ngồi đó mãi mãi. Mối quan tâm của tôi không hợp lệ?

Trong .NET 4.0, tôi thậm chí có thể làm điều này và nó không bao giờ ném một ngoại lệ:

[DllImport("SomeDelphi.dll", EntryPoint = "SomeFunc", CallingConvention = CallingConvention.StdCall)] 
public static extern IntPtr _SomeFunc(); 
public static String SomeFunc() 
{ 
    String str; 
    IntPtr pstr = _SomeFunc(); 
    str = Marshal.PtrToStringAnsi(pstr); 
    Marshal.FreeCoTaskMem(pstr); 
    return str; 
} 

Mã này ném ngoại lệ đống cùng trong 4.5, tuy nhiên. Điều này khiến tôi tin rằng vấn đề nằm trong thực tế là trong .Net 4.5, marshaler đang cố gắng để FreeCoTaskMem() và đó là những gì đang ném các ngoại lệ.

Vì vậy, câu hỏi:

  1. Tại sao công việc này trong Net 4.0 và không 4.5?

  2. Tôi có nên lo lắng về việc phân bổ NewStr() trong DLL gốc không?

  3. Nếu trả lời "Không" cho # 2, thì ví dụ mã thứ hai là hợp lệ?

Trả lời

11

Thông tin quan trọng, rất khó tìm trong tài liệu, liên quan đến những gì trình marshaller thực hiện với hàm p/invoke với giá trị trả về của chuỗi loại. Giá trị trả lại được ánh xạ tới một mảng ký tự kết thúc bằng null, tức là LPCTSTR trong các thuật ngữ Win32. Càng xa càng tốt.

Nhưng marshaller cũng biết rằng chuỗi phải được phân bổ trên một đống ở đâu đó. Và nó không thể mong đợi mã nguồn gốc để deallocate nó kể từ khi chức năng bản địa đã kết thúc. Vì vậy, marshaller deallocates nó. Và nó cũng giả định rằng đống chia sẻ đã được sử dụng là đống COM. Vì vậy, marshaller gọi CoTaskMemFree trên con trỏ được trả về bởi mã gốc. Và đó là điều dẫn đến lỗi của bạn.

Kết luận là nếu bạn muốn sử dụng giá trị trả về chuỗi trên kết thúc C# p/invoke, bạn cần khớp với giá trị đó ở đầu gốc. Để làm như vậy trả về PAnsiChar hoặc PWideChar và phân bổ các mảng ký tự với một cuộc gọi đến CoTaskMemAlloc.

Bạn hoàn toàn không thể sử dụng NewStr tại đây. Trong thực tế, bạn không bao giờ nên gọi hàm đó. Mã hiện tại của bạn bị hỏng hoàn toàn và mọi cuộc gọi bạn thực hiện với NewStr đều dẫn đến rò rỉ bộ nhớ.

Một số mã ví dụ đơn giản mà sẽ làm việc:

Delphi

function SomeFunc: PAnsiChar; stdcall; 
var 
    SomeString: AnsiString; 
    ByteCount: Integer; 
begin 
    SomeString := ... 
    ByteCount := (Length(SomeString)+1)*SizeOf(SomeString[1]); 
    Result := CoTaskMemAlloc(ByteCount); 
    Move(PAnsiChar(SomeString)^, Result^, ByteCount); 
end; 

C#

[DllImport("SomeDelphi.dll")] 
public static extern string SomeFunc(); 

Bạn có lẽ sẽ muốn quấn mã gốc lên trong một helper cho tiện.

function COMHeapAllocatedString(const s: AnsiString): PAnsiChar; stdcall; 
var 
    ByteCount: Integer; 
begin 
    ByteCount := (Length(s)+1)*SizeOf(s[1]); 
    Result := CoTaskMemAlloc(ByteCount); 
    Move(PAnsiChar(s)^, Result^, ByteCount); 
end; 

Một tùy chọn khác là trả lại BSTR và sử dụng MarshalAs (UnmanagedType.BStr) ở phía C#. Tuy nhiên, trước khi bạn làm như vậy, hãy đọc điều này: Why can a WideString not be used as a function return value for interop?


Tại sao bạn thấy hành vi khác nhau trong các phiên bản .net khác nhau? Khó nói chắc chắn. Mã của bạn cũng bị hỏng cả hai. Có lẽ các phiên bản mới hơn là tốt hơn trong việc phát hiện các lỗi như vậy. Có lẽ có một số khác biệt khác. Bạn đang chạy cả 4.0 và 4.5 trên cùng một máy, cùng một hệ điều hành. Có lẽ thử nghiệm 4.0 của bạn đang chạy trên một hệ điều hành cũ hơn mà không ném lỗi cho lỗi heap COM.

Ý kiến ​​của tôi là có ít điểm hiểu tại sao mã bị hỏng có vẻ hoạt động. Mã bị hỏng. Sửa nó và tiếp tục.

+0

Đối với hồ sơ, nó là cùng một máy, cùng một hệ điều hành, cùng một trình biên dịch, cùng tất cả mọi thứ. Chỉ cần kích chuột phải vào Project -> thay đổi framework thành 4.5 và viola, nó bị treo. Dù sao, tôi vui vì tôi đã hiểu nó một cách chính xác, và cảm ơn như luôn luôn cho sự giúp đỡ của bạn. –

1

vài điểm của tôi:

  1. Thứ nhất, Marshal.FreeCoTaskMem là dành cho giải phóng COM phân bổ các khối bộ nhớ! Nó không được bảo đảm để làm việc cho các khối bộ nhớ khác được phân bổ bởi Delphi.

  2. newstr bị phản đối (tôi có được điều này sau khi googling):

    newstr (const S: string): PString; không được chấp nhận;

Đề xuất của tôi là bạn cũng xuất chức năng DLL thay thế chuỗi deallocation thay vì sử dụng FreeCoTaskMem.

+0

Tại sao nó luôn hoạt động trong .Net 4.0 nhưng không phải 4.5? Là NewStr() bằng cách nào đó phân bổ trên đống chia sẻ, hoặc là 4.0 marshaler chỉ đơn giản là không biết những gì đang xảy ra? –

+0

Nó chỉ "xuất hiện để làm việc". Ngay cả khi nó hoạt động cho .NET 4.0, nó không có nghĩa là nó hoàn toàn hợp pháp và chính xác. Giải phóng một đối tượng chuỗi được phân bổ Delphi bằng FreeCoTaskMem là cách * sai *. – nim