2013-08-22 5 views
5

मैं सी # में अप्रबंधित डीएलएल आयातों के मार्शलिंग के बारे में सीख रहा हूं ... और मैं कुछ ऐसी चीज में आया हूं जो मुझे समझ में नहीं आता है।सी # से डेल्फी डीएलएल रिटर्न स्ट्रिंग ... .NET 4.5 हीप भ्रष्टाचार लेकिन .NET 4.0 काम करता है? कृपया समझाईए?

डेल्फी में, वहाँ है कि एक Procedure SomeFunc() : PChar; Stdcall;

मेरी समझ से से Result := NewStr(PChar(somestring)) लौटा रहा है एक समारोह है, NewStr सिर्फ स्थानीय ढेर पर एक बफर आवंटित ... और SomeFunc इसे करने के लिए एक सूचक लौटा रहा है।

.NET 4.0 (ग्राहकों का प्रोफाइल) में, सी # के माध्यम से मैं उपयोग कर सकते हैं:

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

यह काम करता है (या के रूप में डेविड कहते हैं, "काम करने के लिए प्रकट होता है") विंडोज 7 .NET 4.0 ग्राहकों का प्रोफाइल में ठीक। विंडोज 8 में, यह अप्रत्याशित व्यवहार है, जिसने मुझे इस रास्ते से नीचे लाया।

तो मैंने .NET 4.5 में एक ही कोड को आजमाने का फैसला किया और ढेर भ्रष्टाचार त्रुटियों को प्राप्त किया। ठीक है, तो अब मुझे पता है कि यह चीजों को करने का सही तरीका नहीं है। तो मैं आगे खुदाई:

फिर भी .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); 
} 

यह बिना किसी बाधा के काम करता है। मेरी (नौसिखिया) चिंता यह है कि न्यूएसआरटी() ने इस स्मृति को आवंटित किया है और यह हमेशा के लिए बस बैठा है। क्या मेरी चिंता वैध नहीं है? तथापि

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

इस कोड को 4.5 में एक ही ढेर अपवाद फेंकता है,:

.NET 4.0 में, मैं भी ऐसा कर सकते हैं और यह एक अपवाद कभी नहीं फेंकता है। इससे मुझे विश्वास होता है कि समस्या इस तथ्य में निहित है कि .NET 4.5 में, मार्शलर FreeCoTaskMem() की कोशिश कर रहा है और यही अपवाद फेंक रहा है।

तो सवाल:

  1. क्यों नेट 4.0 में यह काम करते हैं और नहीं 4.5 करता है?

  2. क्या मुझे मूल डीएलएल में न्यूएसआरटी() के आवंटन के बारे में चिंतित होना चाहिए?

  3. यदि उत्तर "नहीं" # 2 का उत्तर है, तो दूसरा कोड उदाहरण मान्य है?

उत्तर

11

मुख्य जानकारी, जो दस्तावेज़ों में खोजने में बहुत मुश्किल है, चिंतित है कि मार्शलर पी/इनवॉक फ़ंक्शन के साथ टाइप स्ट्रिंग के रिटर्न वैल्यू के साथ क्या करता है। वापसी मान को शून्य-टर्मिनेटेड वर्ण सरणी में मैप किया जाता है, यानी Win32 शर्तों में LPCTSTR। अब तक सब ठीक है।

लेकिन मार्शलर यह भी जानता है कि स्ट्रिंग कहीं कहीं ढेर पर आवंटित की जानी चाहिए। और देशी फ़ंक्शन समाप्त होने के बाद से देशी कोड को इसे रद्द करने की उम्मीद नहीं कर सकती है। तो मार्शलर इसे हटा देता है। और यह भी मानता है कि साझा ढेर का उपयोग किया गया था COM ढेर था। तो मार्शलर मूल कोड द्वारा लौटाए गए सूचक पर CoTaskMemFree को कॉल करता है। और यही कारण है कि आपकी त्रुटि का कारण बनता है।

निष्कर्ष यह है कि यदि आप C# p/invoke end पर स्ट्रिंग रिटर्न मान का उपयोग करना चाहते हैं, तो आपको मूल अंत में मिलान करना होगा। ऐसा करने के लिए PAnsiChar या PWideChar वापस करें और CoTaskMemAlloc को कॉल के साथ वर्ण सरणी आवंटित करें।

आप बिल्कुल न्यूएसआरटी का उपयोग नहीं कर सकते हैं। वास्तव में आपको उस समारोह को कभी भी कॉल नहीं करना चाहिए। आपका मौजूदा कोड व्यापक रूप से टूटा हुआ है और आप जो भी कॉल न्यूएसआरटी में करते हैं, वह स्मृति रिसाव की ओर जाता है।

कुछ सरल उदाहरण कोड है कि काम करेंगे:

डेल्फी

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; 

सी #

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

आप शायद के लिए एक सहायक में मूल कोड लपेट के लिए चाहते हो जाएगा सुविधा।

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; 

फिर भी एक और विकल्प के लिए एक BSTR लौट सकते हैं और सी # तरफ MarshalAs (UnmanagedType.BStr) का प्रयोग है। हालांकि, इससे पहले कि आप ऐसा करते हैं, इस पढ़ें: Why can a WideString not be used as a function return value for interop?


आप विभिन्न .net संस्करणों में अलग व्यवहार क्यों दिखाई देता है? निश्चित रूप से कहना मुश्किल है। आपका कोड दोनों में टूटा हुआ है। शायद इस तरह की त्रुटियों का पता लगाने के लिए नए संस्करण बेहतर हैं। शायद कुछ और अंतर है। क्या आप एक ही मशीन पर एक ही ओएस पर 4.0 और 4.5 दोनों चला रहे हैं। शायद आपका 4.0 टेस्ट पुराने ओएस पर चल रहा है जो COM ढेर भ्रष्टाचार के लिए त्रुटियों को फेंक नहीं देता है।

मेरी राय यह है कि टूटी हुई कोड काम करने के लिए क्यों थोड़ा सा समझ है। कोड टूटा हुआ है। इसे ठीक करें, और आगे बढ़ें।

+0

रिकॉर्ड के लिए, यह वही मशीन, एक ही ओएस, एक ही कंपाइलर, वही सब कुछ है। बस प्रोजेक्ट पर राइट क्लिक करें -> फ्रेमवर्क को 4.5 और व्हायोला में बदलें, यह क्रैश हो जाता है। वैसे भी, मुझे खुशी है कि मैं इसे सही ढंग से समझ रहा था, और हमेशा आपकी मदद के लिए धन्यवाद। –

1

मेरे कुछ बिंदुओं:

  1. पहले, Marshal.FreeCoTaskMem कॉम आबंटित स्मृति ब्लॉकों को मुक्त कराने के लिए है! डेल्फी द्वारा आवंटित अन्य मेमोरी ब्लॉक के लिए काम करने की गारंटी नहीं है।

  2. NewStr अब मान्य नहीं है (मैं googling के बाद इस मिल):

    NewStr (स्थिरांक एस: स्ट्रिंग): PString; पदावनत;

मेरा सुझाव यह है कि आप एक DLL फ़ंक्शन भी निर्यात करते हैं जो FreeCoTaskMem का उपयोग करने के बजाय स्ट्रिंग डेलोकेशन करता है।

+0

यह हमेशा .NET 4.0 में क्यों काम करता है लेकिन 4.5 नहीं? क्या न्यूएसआरटी() किसी भी तरह से साझा ढेर पर आवंटित है, या 4.0 मार्शलर बस क्या हो रहा है से अनजान है? –

+0

यह सिर्फ "काम करने के लिए प्रतीत होता है"। भले ही यह .NET 4.0 के लिए काम करता है, इसका मतलब यह नहीं है कि यह पूरी तरह से कानूनी और सही है। FreeCoTaskMem का उपयोग कर डेल्फी आवंटित स्ट्रिंग ऑब्जेक्ट को मुक्त करना * गलत * तरीका है। – nim