C# Language
इंटरोऑपरेबिलिटी
खोज…
टिप्पणियों
Win32 API के साथ C # का उपयोग करके काम करना
विंडोज Win32 एपीआई के रूप में बहुत सारी कार्यक्षमता को उजागर करता है। इन API का उपयोग करके आप विंडोज़ में प्रत्यक्ष ऑपरेशन कर सकते हैं, जिससे आपके एप्लिकेशन का प्रदर्शन बढ़ जाता है। स्रोत पर क्लिक करें
विंडोज एपीआई की एक विस्तृत श्रृंखला को उजागर करता है। विभिन्न एपीआई के बारे में जानकारी प्राप्त करने के लिए आप पिनवोक जैसी साइटों की जांच कर सकते हैं।
मानव रहित C ++ DLL से आयात समारोह
यहां एक फ़ंक्शन को आयात करने का एक उदाहरण दिया गया है जो एक अप्रबंधित C ++ DLL में परिभाषित किया गया है। "MyDLL.dll" के लिए C ++ स्रोत कोड में, फ़ंक्शन add
परिभाषित किया गया है:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
return a + b;
}
फिर इसे निम्नानुसार C # प्रोग्राम में शामिल किया जा सकता है:
class Program
{
// This line will import the C++ method.
// The name specified in the DllImport attribute must be the DLL name.
// The names of parameters are unimportant, but the types must be correct.
[DllImport("myDLL.dll")]
private static extern int add(int left, int right);
static void Main(string[] args)
{
//The extern method can be called just as any other C# method.
Console.WriteLine(add(1, 2));
}
}
extern "C"
और __stdcall
क्यों जरूरी हैं, इस बारे में स्पष्टीकरण के लिए कॉलिंग कन्वेंशन और सी ++ नाम का प्रबंधन देखें।
गतिशील पुस्तकालय ढूँढना
जब एक्सटर्नल मेथड पहली बार लागू किया जाता है तो C # प्रोग्राम उपयुक्त DLL को खोजेगा और लोड करेगा। डीएलएल को खोजने के लिए कहां खोज की गई है, और आप खोज स्थानों को कैसे प्रभावित कर सकते हैं, इस बारे में अधिक जानकारी के लिए यह स्टैकओवरफ्लो प्रश्न देखें ।
कॉम के लिए कक्षा को उजागर करने का सरल कोड
using System;
using System.Runtime.InteropServices;
namespace ComLibrary
{
[ComVisible(true)]
public interface IMainType
{
int GetInt();
void StartTime();
int StopTime();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MainType : IMainType
{
private Stopwatch stopWatch;
public int GetInt()
{
return 0;
}
public void StartTime()
{
stopWatch= new Stopwatch();
stopWatch.Start();
}
public int StopTime()
{
return (int)stopWatch.ElapsedMilliseconds;
}
}
}
C ++ नाम मैनलिंग
C ++ कंपाइलर निर्यात कार्यों के नामों में अतिरिक्त जानकारी को कूटबद्ध करता है, जैसे कि तर्क प्रकार, विभिन्न तर्कों के साथ ओवरलोड को संभव बनाने के लिए। इस प्रक्रिया को नाम मैनलिंग कहा जाता है। , सी # (और सामान्य रूप में अन्य भाषाओं के साथ इंटरॉप) में कार्यों का आयात करने के नाम के रूप में के साथ इस का कारण बनता है समस्याओं int add(int a, int b)
समारोह नहीं रह गया है add
लिए, यह हो सकता है ?add@@YAHHH@Z
, _add@8
कंपाइलर और कॉलिंग कन्वेंशन के आधार पर _add@8
या कुछ और।
नाम की समस्या को सुलझाने के कई तरीके हैं:
extern "C"
का उपयोग करके सी एक्सटर्नल लिंकेज पर स्विच करने वाले फ़ंक्शंस का निर्यात करना, जिसमें सी नेम मेनिंगिंग का उपयोग किया जाता है:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
फंक्शन का नाम अभी भी
_add@8
(_add@8
) होगा, लेकिनStdCall
+extern "C"
नाम कीStdCall
को C # कंपाइलर द्वारा मान्यता प्राप्त है।myDLL.def
मॉड्यूल परिभाषा फ़ाइल में निर्यात किए गए फ़ंक्शन नाम निर्दिष्ट करना:EXPORTS add
int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
इस मामले में फ़ंक्शन का नाम शुद्ध
add
जाएगा।आयातित आम का नाम। आपको मांगलिक नाम देखने के लिए कुछ DLL दर्शक की आवश्यकता होगी, फिर आप इसे स्पष्ट रूप से निर्दिष्ट कर सकते हैं:
__declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
अधिवेशन बुला रहे हैं
कॉलिंग फ़ंक्शंस के कई कन्वेंशन हैं, जो निर्दिष्ट करते हैं कि (कॉलर या कैली) स्टैक से किस तरह से बहस करते हैं, कैसे तर्क पारित किए जाते हैं और किस क्रम में। C ++ डिफ़ॉल्ट रूप से Cdecl
कॉलिंग कन्वेंशन का उपयोग करता है, लेकिन C # StdCall
अपेक्षा StdCall
, जो आमतौर पर Windows API द्वारा उपयोग किया जाता है। आपको एक या दूसरे को बदलने की जरूरत है:
C ++ में
StdCall
लिए कॉलिंग कन्वेंशन बदलें:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
या,
Cdecl
को C # में कन्वेंशन कॉलिंग बदलें:extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
यदि आप Cdecl
कॉलिंग कन्वेंशन और एक Cdecl
नाम के साथ एक फ़ंक्शन का उपयोग करना चाहते हैं, तो आपका कोड इस तरह दिखाई देगा:
__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?add@@YAHHH@Z")]
thiscall ( __thiscall ) मुख्य रूप से उन कार्यों में उपयोग किया जाता है जो एक वर्ग के सदस्य हैं।
जब कोई फ़ंक्शन इसकॉल ( __thiscall ) का उपयोग करता है, तो कक्षा के लिए एक सूचक को पहले पैरामीटर के रूप में नीचे पारित किया जाता है।
मानव रहित DLL का गतिशील लोडिंग और अनलोडिंग
DllImport
विशेषता का उपयोग करते समय आपको संकलन समय पर सही dll और विधि का नाम जानना होगा। यदि आप अधिक लचीले होना चाहते हैं और रनटाइम पर निर्णय लेते हैं कि कौन सी dll और लोड करने की विधियाँ हैं, तो आप Windows API विधियों LoadLibrary()
, GetProcAddress()
और FreeLibrary()
उपयोग कर सकते हैं। यह मददगार हो सकता है अगर पुस्तकालय का उपयोग रनटाइम स्थितियों पर निर्भर करता है।
सूचक द्वारा दिया GetProcAddress()
का उपयोग कर एक प्रतिनिधि में casted किया जा सकता है Marshal.GetDelegateForFunctionPointer()
।
निम्न कोड नमूना पिछले उदाहरणों से myDLL.dll
साथ इसे प्रदर्शित करता है:
class Program
{
// import necessary API as shown in other examples
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void FreeLibrary(IntPtr module);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr module, string proc);
// declare a delegate with the required signature
private delegate int AddDelegate(int a, int b);
private static void Main()
{
// load the dll
IntPtr module = LoadLibrary("myDLL.dll");
if (module == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}");
return;
}
// get a "pointer" to the method
IntPtr method = GetProcAddress(module, "add");
if (method == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}");
FreeLibrary(module); // unload library
return;
}
// convert "pointer" to delegate
AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));
// use function
int result = add(750, 300);
// unload library
FreeLibrary(module);
}
}
Win32 त्रुटियों से निपटना
इंटरोप विधियों का उपयोग करते समय, आप एपीआई कॉल पर अतिरिक्त जानकारी प्राप्त करने के लिए GetLastError API का उपयोग कर सकते हैं।
DllImport अटैचमेंट SetLastError विशेषता
SetLastError = true
इंगित करता है कि कैली सेटेलस्टॉर (Win32 एपीआई फ़ंक्शन) को कॉल करेगा।
SetLastError = false
इंगित करता है कि कैली सेटेलस्टॉर (Win32 एपीआई फ़ंक्शन) को कॉल नहीं करेगा , इसलिए आपको एक त्रुटि जानकारी नहीं मिलेगी।
जब SetLastError सेट नहीं होता है, तो यह गलत (डिफ़ॉल्ट मान) पर सेट होता है।
आप मार्शल का उपयोग कर त्रुटि कोड प्राप्त कर सकते हैं। GetLastWin32Error विधि:
उदाहरण:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);
यदि आप म्यूटेक्स खोलने की कोशिश कर रहे हैं जो मौजूद नहीं है, तो GetLastError ERROR_FILE_NOT_FOUND को वापस कर देगा ।
var lastErrorCode = Marshal.GetLastWin32Error();
if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
//Deal with error
}
सिस्टम त्रुटि कोड यहां देखे जा सकते हैं:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
GetLastError API
एक देशी GetLastError API है जिसका आप उपयोग कर सकते हैं:
[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
- Win32 API को प्रबंधित कोड से कॉल करते समय, आपको हमेशा Mars.GetLastWin32Error का उपयोग करना चाहिए।
यहाँ पर क्यों:
आपके Win32 कॉल के बीच जो त्रुटि सेट करता है (SetLastError को कॉल करता है), CLR अन्य Win32 कॉल को कॉल कर सकता है जो SetLastError को भी कॉल कर सकता है, यह व्यवहार आपके त्रुटि मान को ओवरराइड कर सकता है। इस परिदृश्य में, यदि आप GetLastError कहते हैं, तो आप एक अमान्य त्रुटि प्राप्त कर सकते हैं।
SetLastError = true सेट करना, यह सुनिश्चित करता है कि CLR त्रुटि कोड को पुनः प्राप्त करता है , इससे पहले कि यह अन्य Win32 कॉलों को निष्पादित करता है।
पिन की हुई वस्तु
हमारे कूड़े की सफाई के लिए जीसी (गारबेज कलेक्टर) जिम्मेदार है।
जबकि जीसी हमारे कचरे को साफ करता है, वह अप्रयुक्त वस्तुओं को प्रबंधित ढेर से निकालता है जो ढेर के विखंडन का कारण बनता है। जब जीसी को हटाने के साथ किया जाता है, तो यह ढेर संपीड़न (डीफ़्रैग्मेंटेशन) करता है जिसमें ढेर पर चलती वस्तुओं को शामिल करना होता है।
चूंकि जीसी निर्धारक नहीं है, जब मूल कोड के लिए प्रबंधित ऑब्जेक्ट संदर्भ / पॉइंटर को पास करते हैं, तो जीसी किसी भी समय किक कर सकता है, अगर यह इनरोप कॉल के ठीक बाद होता है, तो एक बहुत अच्छी संभावना है कि ऑब्जेक्ट (जो संदर्भ मूल में पारित हो गया) होगा प्रबंधित हीप पर ले जाया जाए - परिणामस्वरूप, हमें प्रबंधित पक्ष पर एक अमान्य संदर्भ मिलता है।
इस परिदृश्य में, आपको मूल कोड को पास करने से पहले ऑब्जेक्ट को पिन करना चाहिए।
पिन की हुई वस्तु
पिन की गई वस्तु एक ऐसी वस्तु है जिसे GC द्वारा स्थानांतरित करने की अनुमति नहीं है।
जीसी पिनडल हैंडल
आप Gc.Alloc पद्धति का उपयोग करके एक पिन ऑब्जेक्ट बना सकते हैं
GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned);
- प्रबंधित ऑब्जेक्ट के लिए पिन किए गए GCHandle को प्राप्त करना एक विशिष्ट वस्तु को चिह्नित करता है, जिसे GC द्वारा स्थानांतरित नहीं किया जा सकता है, जब तक कि हैंडल को मुक्त नहीं किया जाता है
उदाहरण:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
public void EnterCriticalSection(CRITICAL_SECTION section)
{
try
{
GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned);
EnterCriticalSection(handle.AddrOfPinnedObject());
//Do Some Critical Work
LeaveCriticalSection(handle.AddrOfPinnedObject());
}
finaly
{
handle.Free()
}
}
सावधानियां
- जब पिनिंग (विशेष रूप से बड़े वाले) ऑब्जेक्ट पिन किए गए GcHandle को जितनी जल्दी हो सके मुक्त करने की कोशिश करते हैं, क्योंकि यह ढेर विक्षेपण को बाधित करता है।
- यदि आप GcHandle को मुक्त करना भूल जाते हैं तो कुछ नहीं होगा। इसे एक सुरक्षित कोड सेक्शन में करें (जैसे कि अंतिम)
मार्शल के साथ पढ़ना संरचनाएं
मार्शल क्लास में PtrToStructure नाम का एक फ़ंक्शन होता है, यह फ़ंक्शन हमें एक अनवांटेड पॉइंटर द्वारा रीडिंग स्ट्रक्चर्स की क्षमता प्रदान करता है।
PtrToStructure फ़ंक्शन को कई अधिभार मिले, लेकिन वे सभी एक ही इरादा रखते हैं।
सामान्य PtrToStructure :
public static T PtrToStructure<T>(IntPtr ptr);
टी - संरचना प्रकार।
पीटीआर - मेमोरी के एक अप्रबंधित ब्लॉक के लिए एक संकेतक।
उदाहरण:
NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);
- यदि आप देशी संरचनाओं को पढ़ते समय प्रबंधित वस्तुओं से निपटते हैं, तो अपनी वस्तु को पिन करना न भूलें :)
T Read<T>(byte[] buffer)
{
T result = default(T);
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());
}
finally
{
gch.Free();
}
return result;
}