2013-08-22 15 views
5

Ho imparato a eseguire il marshalling delle importazioni di DLL non gestite in C# ... E mi sono imbattuto in qualcosa che non capisco.Delphi DLL stringa di ritorno da C# ... .NET 4.5 Heap Corruption ma .NET 4.0 funziona? Spiega per favore?

In Delphi, v'è una funzione che sta tornando da un Result := NewStr(PChar(somestring))Procedure SomeFunc() : PChar; Stdcall;

Dalla mia comprensione, newstr solo alloca un buffer sul mucchio locali ... e somefunc sta tornando un puntatore ad esso.

In .NET 4.0 (Client Profile), via C# posso usare:

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

Questo funziona (o come dice David, "sembra funzionare") bene in Windows 7 .NET 4.0 Client Profile. In Windows 8, ha un comportamento imprevedibile, che mi ha portato su questa strada.

Così ho deciso di provare lo stesso codice in .NET 4.5 e ho ottenuto errori di corruzione di heap. Ok, ora so che questo non è il modo corretto di fare le cose. Così ho scavare ulteriormente:

Sempre nel .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); 
} 

Questo funziona senza intoppi. La mia (novizia) preoccupazione è che NewStr() ha allocato questa memoria ed è rimasta lì per sempre. La mia preoccupazione non è valida?

In .NET 4.0, posso anche fare questo e non è mai genera un'eccezione:

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

Questo codice genera la stessa eccezione heap in 4.5, tuttavia. Questo mi porta a credere che il problema risieda nel fatto che in .Net 4.5, il marshaler sta provando a FreeCoTaskMem() e questo è ciò che sta generando le eccezioni.

quindi le domande:

  1. Perché fa questo lavoro in .Net 4.0 e non 4.5?

  2. Dovrei preoccuparmi dell'allocazione di NewStr() nella DLL nativa?

  3. Se la risposta è "No" a # 2, il secondo esempio di codice è valido?

risposta

11

L'informazione chiave, che è molto difficile da trovare nei documenti, riguarda ciò che il marshaller fa con una funzione p/invoke con un valore di ritorno di tipo stringa. Il valore di ritorno è mappato su un array di caratteri con terminazione null, ovvero LPCTSTR in termini Win32. Fin qui tutto bene.

Ma il marshaller sa anche che la stringa deve essere stata allocata su un heap da qualche parte. E non può aspettarsi che il codice nativo lo distolga da quando la funzione nativa è finita. Quindi il marshaller lo rilascia. Inoltre, presuppone che l'heap condiviso utilizzato sia l'heap COM. Quindi il marshaller chiama CoTaskMemFree sul puntatore restituito dal codice nativo. E questo è ciò che porta al tuo errore.

La conclusione è che se si desidera utilizzare il valore di ritorno stringa sul fine C/invoke di C#, è necessario farlo corrispondere sul lato nativo. Per fare ciò, restituire PAnsiChar o PWideChar e allocare gli array di caratteri con una chiamata a CoTaskMemAlloc.

Non è assolutamente possibile utilizzare NewStr qui. In realtà non dovresti mai chiamare quella funzione. Il codice esistente è completamente rotto e ogni chiamata effettuata a NewStr porta a una perdita di memoria.

qualche esempio di codice semplice che funziona:

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

Probabilmente si vorrebbe avvolgere il codice nativo in un aiuto per convenienza.

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; 

Un'altra opzione è quella di restituire un BSTR e utilizzare MarshalAs (UnmanagedType.BStr) sul lato C#. Tuttavia, prima di farlo, leggere questo: Why can a WideString not be used as a function return value for interop?


Perché guardi un comportamento diverso in diverse versioni NET? Difficile dirlo di sicuro. Il tuo codice è altrettanto rotto in entrambi. Forse le versioni più recenti sono migliori nel rilevare tali errori. Forse c'è un'altra differenza. Stai utilizzando sia la 4.0 sia la 4.5 sulla stessa macchina, lo stesso OS. Forse il tuo test 4.0 è in esecuzione su un vecchio sistema operativo che non genera errori per la corruzione dell'heap di COM.

La mia opinione è che non ha molto senso capire perché il codice non funzionante sembra funzionare. Il codice è rotto Risolvilo e vai avanti.

+0

Per la cronaca, è la stessa macchina, stesso sistema operativo, stesso compilatore, stesso tutto. Basta fare clic con il tasto destro del mouse su Project -> change framework su 4.5 e viola, si blocca. Ad ogni modo, sono contento di averlo capito correttamente, e grazie come sempre per il tuo aiuto. –

1

I miei punti poche:

  1. In primo luogo, è Marshal.FreeCoTaskMem per liberare i blocchi di memoria allocata COM! Non è garantito il funzionamento per altri blocchi di memoria allocati da Delphi.

  2. newstr è deprecato (ottengo questo dopo googling):

    newstr (const S: stringa): PString; deprecato;

Il mio suggerimento è che si esporta anche una funzione DLL che esegue la deallazione di stringa invece di utilizzare FreeCoTaskMem.

+0

Perché funziona sempre in .Net 4.0 ma non 4,5? NewStr() in qualche modo si sta allocando sull'heap condiviso oppure il marshaler 4.0 non si accorge di cosa sta succedendo? –

+0

Semplicemente "sembra funzionare". Anche se funziona per .NET 4.0, non significa che sia completamente legale e corretto. Liberare un oggetto stringa allocato a Delphi usando FreeCoTaskMem è il modo * errato *. – nim

Problemi correlati