2013-08-22 33 views
5

Dowiedziałem się o ładowaniu niezarządzanych importów DLL do C# ... I natknąłem się na coś, czego nie całkiem rozumiem.Delphi DLL zwraca ciąg z C# ... .NET 4.5 Heap Corruption ale .NET 4.0 działa? Wytłumacz, proszę?

W Delphi, jest to funkcja, która zwraca Result := NewStr(PChar(somestring)) z Procedure SomeFunc() : PChar; Stdcall;

Z mojego zrozumienia, NewStr tylko przydziela bufor na lokalnej stercie ... i SomeFunc zwraca wskaźnik do niego.

W .NET 4.0 (Profil klienta), za pośrednictwem C# można użyć:

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

To działa (lub jak mówi David "wydaje się działać") grzywny w Windows 7 .NET 4.0 Client Profile. W systemie Windows 8 ma nieprzewidywalne zachowanie, które doprowadziło mnie do tej drogi.

Postanowiłem więc wypróbować ten sam kod w .NET 4.5 i dostałem błędy korupcji sterty. Okay, więc teraz wiem, że to nie jest właściwy sposób robienia rzeczy. Więc wbijam dalej:

Jeszcze w .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); 
} 

To działa bez zarzutu. Mój (nowicjusz) problem polega na tym, że NewStr() przydzielił tę pamięć i po prostu siedzi tam na zawsze. Czy moja troska nie jest ważna?

W .NET 4.0, mogę nawet tego robić i nigdy nie zgłasza wyjątek:

[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; 
} 

Ten kod rzuca ten sam wyjątek sterty w 4,5, jednak. To prowadzi mnie do przekonania, że ​​problem polega na tym, że w .Net 4.5, Marshaler próbuje FreeCoTaskMem() i to jest to, co rzuca wyjątki.

Więc pytania:

  1. Dlaczego to działa w .NET 4.0, a nie 4.5?

  2. Czy powinienem się martwić o alokację NewStr() w macierzystej bibliotece DLL?

  3. Jeśli odpowiedź "Nie" na nr 2, to drugi przykład kodu jest ważny?

Odpowiedz

11

Kluczowe informacje, które bardzo trudno znaleźć w dokumentach, dotyczą tego, co robi koordynator Mars z funkcją p/invoke z zwracaną wartością typu string. Zwracana wartość jest odwzorowywana na zakończoną znakiem Null tablicę znaków, tj. LPCTSTR w wyrażeniach Win32. Jak na razie dobrze.

Ale koordynator wie również, że ciąg musi zostać przydzielony gdzieś na kupie. Nie można oczekiwać, że natywny kod zwolni go, ponieważ natywna funkcja została zakończona. Tak więc marshaller rozdziela to. Zakłada się również, że używana kupka współdzielona była kupą COM. Tak więc marshaller wywołuje CoTaskMemFree na wskaźniku zwróconym przez natywny kod. I to właśnie prowadzi do twojego błędu.

Wniosek jest taki, że jeśli chcesz użyć wartości zwracanej przez ciąg znaków na końcu C# p/invoke, musisz dopasować to na rodzimym końcu. Aby to zrobić, należy zwrócić PAnsiChar lub PWideChar i przydzielić tablice znaków za pomocą wywołania CoTaskMemAlloc.

Absolutnie nie możesz użyć NewStr tutaj. W rzeczywistości nigdy nie powinieneś wywoływać tej funkcji. Twój istniejący kod jest kompletnie zepsuty, a każde połączenie z NewStr prowadzi do wycieku pamięci.

Niektóre prosty przykład kodu, który będzie działać:

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

Prawdopodobnie będzie chciał zawinąć kodu macierzystego w pomocnika dla wygoda.

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; 

Jeszcze jedną opcją jest zwrócenie BSTR i użycie MarshalAs (UnmanagedType.BStr) po stronie C#. Jednak zanim to zrobisz, przeczytaj to: Why can a WideString not be used as a function return value for interop?


Dlaczego widzisz różne zachowanie w różnych wersjach .NET? Trudno powiedzieć na pewno. Twój kod jest tak samo zepsuty w obu. Być może nowsze wersje lepiej wykrywają takie błędy. Być może jest jakaś inna różnica. Czy korzystasz z wersji 4.0 i 4.5 na tym samym komputerze, w tym samym systemie operacyjnym. Być może twój test 4.0 działa na starszym systemie operacyjnym, który nie generuje błędów związanych z uszkodzeniami stosu COM.

Uważam, że nie ma sensu rozumieć, dlaczego złamany kod wydaje się działać. Kod jest uszkodzony. Napraw to i idź dalej.

+0

Dla rekordu jest to ta sama maszyna, ten sam system operacyjny, ten sam kompilator, to samo. Kliknij prawym przyciskiem myszy Projekt -> zmień frameworię na 4.5 i viola, to się zawiesi. W każdym razie cieszę się, że zrozumiałem to poprawnie i dziękuję jak zawsze za pomoc. –

1

Moi kilka punktów:

  1. Pierwsze, Marshal.FreeCoTaskMem jest uwolnienie COM przydzielony bloków pamięci! Nie ma gwarancji, że będzie działać dla innych bloków pamięci przydzielonych przez Delphi.

  2. NewStr jest przestarzała (mam to po googlowania):

    NewStr (const S: string): PString; przestarzałe;

Moja sugestia jest taka, że ​​można również wyeksportować funkcję DLL, która wykonuje zwalnianie ciągów zamiast używania FreeCoTaskMem.

+0

Dlaczego to zawsze działa w .Net 4.0, ale nie w wersji 4.5? Czy NewStr() w jakiś sposób alokuje na wspólną stertę, czy też 4.0 marshaler po prostu nie zdaje sobie sprawy z tego, co się dzieje? –

+0

Po prostu "wydaje się działać". Nawet jeśli działa dla .NET 4.0, nie oznacza to, że jest całkowicie legalny i poprawny. Zwolnienie obiektu łańcucha przydzielonego przez Delphi za pomocą FreeCoTaskMem jest * błędnym * sposobem. – nim