2013-08-22 16 views
5

Yönetilmeyen DLL içe aktarmalarını C# ye dönüştürmeyi öğrendim ... Ve tam olarak anlamadığım bir şeyle karşılaşıyorum.Delphi DLL dönüş dizesi C# ... .NET 4.5 Yığın Bozulması ancak .NET 4.0 çalışır? Açıkla lütfen?

Delphi, Benim anlayış bir Procedure SomeFunc() : PChar; Stdcall;

den Result := NewStr(PChar(somestring)) dönen bir fonksiyon vardır, newstr sadece yerel yığın bir tampon ayırır ... ve SomeFunc kendisine bir işaretçi geri dönüyor. .NET 4.0 (Müşteri Profili) olarak

, C# aracılığı ı kullanabilirsiniz:

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

Bu işleri (veya David söylediği gibi, "çalışmak için görünür") Windows 7 .NET 4.0 İstemci Profili cezası. Windows 8'de, bu yolu beni aşağıya getiren tahmin edilemez bir davranışı var.

Yani aynı kodu .NET 4.5'te denemeye karar verdim ve Yığın bozulmasıyla ilgili hatalar oluştu. Tamam, şimdi biliyorum ki bu işlerin yapılması için doğru bir yol değil. Bu yüzden daha fazla kazmak:

Hala

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

Bu sorunsuz olarak çalışmasını .NET 4.5. Benim (acemi) kaygım, NewStr() 'in bu belleği tahsis etmesi ve sonsuza kadar orada oturması. Endişem geçerli değil mi?

.NET 4.0, ben bile bunu yapabilirsiniz ve özel bir durum oluşturmaz:

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

Bu kod 4,5 aynı yığın istisna atar ancak. Bu, problemin, .Net 4.5'de, marşetin FreeCoTaskMem() için çalıştığını ve istisnaları fırlattığı gerçeğinde olduğuna inanmamı sağlıyor.

Yani sorular:

  1. Neden bu .Net 4.0 iş değil 4.5 yapar?

  2. NewStr() 'ın yerel DLL dosyasında tahsisi konusunda endişelenmeli miyim?

  3. # 2'ye "Hayır" cevabını verirseniz, ikinci kod örneği geçerlidir?

cevap

11

Dokümanlarda bulunması çok zor olan anahtar bilgiler, marşalın tip dizgisi döndürme değeriyle bir p/invoke işleviyle ne yaptığını gösterir. Dönüş değeri, null sonlandırılmış bir karakter dizisi, yani LPCTSTR Win32 terimleriyle eşlenir. Çok uzak çok iyi. Ancak marşaliner, dizenin bir yerde bir yığına dağıtılmış olması gerektiğini de bilir. Yerel işlev bittiğinden, yerel kodun kendisini ayırmasını bekleyemez. Yani mareşal, onu tahsis ediyor. Ve ayrıca, kullanılan paylaşılan yığının COM yığını olduğunu varsayar. Bu yüzden mareşal, yerel kod tarafından döndürülen işaretçiye CoTaskMemFree'yi çağırır. Ve bu sizin hatalarınıza neden olan şeydir.

Sonuç olarak, C# p/invoke ucunda dize dönüş değeri kullanmak isterseniz, bunu yerel uçta eşleştirmeniz gerekir. Bunu yapmak için PAnsiChar veya PWideChar'ı döndürün ve karakter dizilerini CoTaskMemAlloc'a bir çağrıyla ayırın.

Burada kesinlikle NewStr kullanamazsınız. Aslında asla bu işlevi çağırmamalısınız. Mevcut kodunuz kapsamlı bir şekilde kırılmış ve NewStr'e yaptığınız her çağrı bir bellek sızıntısına yol açıyor. çalışacak

Bazı basit bir örnek kod:

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

Muhtemelen için bir yardımcı yukarı yerel kod kaydırmak isteyeyim Kolaylık.

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; 

Yine başka bir seçenek BSTR dönmek ve C# tarafında MarshalAs (UnmanagedType.BStr) kullanmaktır. Why can a WideString not be used as a function return value for interop?


Neden farklı .net sürümlerinde farklı davranışlar görüyorum: bu işlemden önce Ancak, bu okuma? Tabii ki söylemek zor. Kodun her ikisinde de olduğu gibi. Belki de daha yeni versiyonlar bu tür hataları tespit etmede daha iyidir. Belki başka bir fark var. Aynı makinede, aynı işletim sisteminde hem 4.0 hem de 4.5 çalıştırıyor musunuz? Belki de 4.0 testiniz, COM yığın bozulmaları için hata atmayan eski bir işletim sisteminde çalışıyor.

Kanaatim, bozuk kodun çalışmasının neden göründüğünün anlaşılması gereken küçük bir nokta. Kod bozuk. Düzelt ve devam et.

+0

Kayıt için, aynı makine, aynı işletim sistemi, aynı derleyici, aynı şey. Sadece sağ tıklayın Proje -> çerçeve 4.5 ve viyola değiştirin, çöker. Her neyse, doğru anladığıma sevindim ve yardımın için her zaman olduğu gibi teşekkürler. –

1

Benim birkaç puan:

  1. Birincisi, Marshal.FreeCoTaskMem COM ayrılan bellek blokları boşaltmayı göre! Delphi tarafından ayrılan diğer bellek blokları için çalışması garanti edilmez.

  2. newstr kullanımdan kaldırılmıştır (I yaptığım andan bu olsun):

    newstr (const S: string): PString; kaldırıldı;

Benim önerim, FreeCoTaskMem kullanmak yerine dize ayrılmayı gerçekleştiren bir DLL işlevi de vermenizdir.

+0

Neden her zaman çalışır. Net 4.0, 4.5 değil mi? NewStr() bir şekilde paylaşılan yığına ayrılıyor mu, yoksa 4.0 marş motoru ne olup bittiğinden habersiz mi? –

+0

Sadece "işe yarıyor". .NET 4.0 için çalışıyor olsa bile, bunun tamamen yasal ve doğru olduğu anlamına gelmez. FreeCoTaskMem kullanarak bir Delphi tahsis edilmiş string nesnesini serbest bırakmak * yanlış * yoldur. – nim