خدمات استدعاء المنصة
خدمات استدعاء النظام الأساسي ، والتي يشار إليها عادةً باسم P/Invoke ، هي ميزة من ميزات تطبيقات البنية التحتية للغة المشتركة ، مثل وقت تشغيل اللغة المشتركة من Microsoft ، والتي تُمكّن التعليمات البرمجية المُدارة من استدعاء التعليمات البرمجية الأصلية .
توفر لغات البرمجة المُدارة، مثل C# أو VB.NET، وصولاً أصلياً إلى الفئات والأساليب والأنواع المُعرّفة ضمن المكتبات التي تُشكّل إطار عمل .NET. ورغم أن إطار عمل .NET يُوفّر مجموعة واسعة من الوظائف، إلا أنه قد يفتقر إلى الوصول إلى العديد من مكتبات نظام التشغيل منخفضة المستوى، والتي تُكتب عادةً بلغة برمجة غير مُدارة، أو مكتبات الطرف الثالث المكتوبة أيضاً بلغة برمجة غير مُدارة. يُعدّ P/Invoke الأسلوب الذي يُمكن للمبرمج استخدامه للوصول إلى الدوال في هذه المكتبات. يتم استدعاء الدوال داخل هذه المكتبات من خلال تعريف توقيع الدالة غير المُدارة ضمن الكود المُدار، والذي يُمثّل الدالة الفعلية التي يُمكن استدعاؤها كأي أسلوب مُدار آخر. يُشير التعريف إلى مسار ملف المكتبة، ويُحدّد مُعاملات الدالة وقيمتها المُعادة في أنواع مُدارة، والتي يُرجّح أن يتم تحويلها ضمنياً من وإلى الأنواع غير المُدارة بواسطة بيئة تشغيل اللغة العامة (CLR). عندما تصبح أنواع البيانات غير المُدارة معقدة للغاية بحيث لا يمكن إجراء تحويل ضمني بسيط من وإلى الأنواع المُدارة، يسمح الإطار للمستخدم بتحديد سمات على الدالة، والقيمة المرجعة، و/أو المعلمات لتحسين كيفية ترتيب البيانات بشكل صريح حتى لا تؤدي إلى استثناءات عند محاولة القيام بذلك ضمنيًا.
تتوفر لمبرمجي البرامج المُدارة العديد من المفاهيم المجردة للبرمجة منخفضة المستوى، مقارنةً بالبرمجة بلغات غير مُدارة. ونتيجةً لذلك، سيحتاج المبرمج الذي يمتلك خبرةً في البرامج المُدارة فقط إلى مراجعة مفاهيم البرمجة الأساسية، مثل المؤشرات والهياكل والتمرير بالمرجع، للتغلب على بعض العقبات التي تواجهه عند استخدام P/Invoke.
بنيان
ملخص
يوجد نوعان من P/Invoke قيد الاستخدام حاليًا وهما:
صريح
- يتم استيراد التعليمات البرمجية الأصلية عبر مكتبات الارتباط الديناميكي (DLLs).
- تحدد البيانات الوصفية المضمنة في تجميعة المتصل كيفية استدعاء الكود الأصلي والوصول إلى البيانات ( عادة ما يتطلب ذلك محددات المصدر المنسوبة لمساعدة المترجم في إنشاء رابط التنسيق ).
- هذا التعريف هو الجزء "الصريح".
ضمني
- باستخدام لغة C++/CLI ، يمكن للتطبيق استخدام الذاكرة المُدارة (عن طريق تتبع المؤشرات) وأي منطقة ذاكرة أصلية في آنٍ واحد، دون الحاجة إلى التصريح الصريح بذلك. (ضمني)
- تتمثل الفائدة الرئيسية في هذه الحالة في أنه إذا تغيرت هياكل البيانات الأصلية الأساسية، فما دامت التسمية متوافقة، يتم تجنب حدوث تغيير جذري .
- i.e. Adding/removing/re-ordering structures in a native header will be transparently supported so long as the structure member names did not also change.
Details
When using P/Invoke, the CLR handles DLL loading and conversion of the unmanaged previous types to CTS types (also referred to as parameter marshalling).[1]To perform this, the CLR:
- Locates the DLL containing the function.
- Loads the DLL into memory.
- Locates the address of the function in memory and pushes its arguments onto the stack, marshaling data as required.
P/Invoke is useful for using standard (unmanaged) C or C++DLLs. It can be used when a programmer needs to have access to the extensive Windows API, as many functions provided by the Windows libraries lack available wrappers. When a Win32 API is not exposed by the .NET Framework the wrapper to this API must be written manually.
Pitfalls
Writing P/Invoke wrappers can be difficult and error prone. Using native DLLs means that the programmer can no longer benefit from type safety and garbage collection as is usually provided in the .NET environment. When they are used improperly this may cause problems such as segmentation faults or memory leaks. Getting the exact signatures of the legacy functions for use in the .NET environment can be hard, which can result in such problems. For this purpose tools and websites exist to obtain such signatures, helping to prevent signature problems.
Other pitfalls include:
- Incorrect data alignment of user-defined types in the managed language: there are different ways data can be aligned depending on compilers or compiler directives in C and care must be taken to explicitly tell the CLR how to align data for non-blittable types. A common example of this is when trying to define a data type in .NET to represent a union in C. Two different variables overlap in memory, and defining these two variables in a type in .NET would cause them to be in different locations in memory, so special attributes must be used to correct the issue.
- تداخل جامع البيانات المهملة في اللغة المُدارة مع موقع البيانات: إذا كان مرجعٌ ما محليًا لدالة في .NET وتم تمريره إلى دالة أصلية، فعند انتهاء الدالة المُدارة، قد يستعيد جامع البيانات المهملة هذا المرجع. لذا، يجب الحرص على تثبيت مرجع الكائن ، لمنع جمعه أو نقله بواسطة جامع البيانات المهملة، مما قد يؤدي إلى وصول غير صالح من قِبل الوحدة الأصلية.
عند استخدام C++/CLI، يكون CIL المُصدر حرًا في التفاعل مع الكائنات الموجودة في الذاكرة المُدارة، وفي الوقت نفسه مع أي موقع ذاكرة أصلي قابل للعنونة. يمكن استدعاء كائن موجود في الذاكرة المُدارة، أو تعديله، أو إنشاؤه، باستخدام صيغة "object->field;" البسيطة لتعيين القيم أو تحديد استدعاءات الدوال. ينتج عن ذلك تحسينات كبيرة في الأداء بفضل التخلص من أي تبديل سياق غير ضروري، وانخفاض متطلبات الذاكرة (مكدسات أقصر).
وهذا يأتي مصحوباً بتحديات جديدة:
تُحدد هذه المراجع حلولاً لكل مشكلة من هذه المشكلات في حال ظهورها. ومن أهم مزاياها الاستغناء عن تعريف البنية، وعدم وجود مشكلات تتعلق بترتيب تعريف الحقول والمحاذاة في سياق C++ Interop.
أمثلة
أمثلة أساسية
يوضح هذا المثال البسيط الأول كيفية الحصول على إصدار ملف DLL معين :
توقيع دالة DllGetVersion في واجهة برمجة تطبيقات ويندوز :
HRESULT DllGetVersion ( DLLVERSIONINFO * pdvi )استدعاء كود C# لاستدعاء دالة DllGetVersion :
[StructLayout(LayoutKind.Sequential)] private struct DLLVERSIONINFO { public int cbSize ; public int dwMajorVersion ; public int dwMinorVersion ; public int dwBuildNumber ; public int dwPlatformID ; }[DllImport("shell32.dll")] static extern int DllGetVersion ( ref DLLVERSIONINFO pdvi );يوضح المثال الثاني كيفية استخراج أيقونة من ملف:
توقيع دالة ExtractIcon في واجهة برمجة تطبيقات ويندوز:
HICON ExtractIcon ( HINSTANCE hInst , LPCTSTR lpszExeFileName , UINT nIconIndex );استدعاء كود C# لاستدعاء دالة ExtractIcon :
[DllImport("shell32.dll")]staticexternIntPtrExtractIcon(IntPtrhInst,[MarshalAs(UnmanagedType.LPStr)]stringlpszExeFileName,uintnIconIndex);This next complex example shows how to share an Event between two processes in the Windows platform:
CreateEvent function signature:
HANDLECreateEvent(LPSECURITY_ATTRIBUTESlpEventAttributes,BOOLbManualReset,BOOLbInitialState,LPCTSTRlpName);P/Invoke C# code to invoke the CreateEvent function:
[DllImport("kernel32.dll", SetLastError=true)]staticexternIntPtrCreateEvent(IntPtrlpEventAttributes,boolbManualReset,boolbInitialState,[MarshalAs(UnmanagedType.LPStr)]stringlpName);A more complex example
// native declarationtypedefstruct_PAIR{DWORDVal1;DWORDVal2;}PAIR,*PPAIR;// Compiled with /clr; use of #pragma managed/unmanaged can lead to double thunking;// avoid by using a stand-alone .cpp with .h includes.// This would be located in a .h file.// Note use of de/referencing. It must match your use.template<>inlineCLR_PAIR^MarshalAs<CLR_PAIR^,PAIR>(constPAIR&Src){CLR_PAIR^Dest=gcnewCLR_PAIR;Dest->Val1=Src.Val1;Dest->Val2=Src.Val2;returnDest;};// ...CLR_PAIR^mgdPair1;CLR_PAIR^mgdPair2;PAIRnative0;PAIR*native1=&native0;native0=NativeCallGetRefToMemory();// Using marshal_as. It makes sense for large or frequently used types.mgdPair1=MarshalAs<CLR_PAIR^>(*native1);// Direct field usemgdPair2->Val1=native0.Val1;mgdPair2->Val2=native0.Val2;return(mgd_pair1);// Return to C#Tools
There are a number of tools which are designed to aid in the production of P/Invoke signatures.
Writing a utility application that would import C++ header files and native DLL files and produce an interface assembly automatically turns out to be quite difficult. The main problem with producing such an importer/exporter for P/Invoke signatures is the ambiguity of some C++ function call parameter types.
Brad Abrams has this to say on the subject:[4]
The problem lies with C++ functions like the following:
__declspec(dllexport)voidMyFunction(char*params);What type should we use for the parameter params in our P/Invoke signature ? This could be either a C++ null terminated string, or could be a char array or could be an output char parameter. So should we use string, StringBuilder, char[] or ref char ?
Regardless of this issue, there are a few tools available to make the production of P/Invoke signatures simpler.
One of the tools listed below, xInterop C++ .NET Bridge has resolved this issue by implementing multiple overrides of the same C++ method in .NET world, developers can then pick the correct one to make the call.
PInvoke.net
PInvoke.net is a wiki containing P/Invoke signatures for a large number of standard Windows APIs. It is owned by Redgate Software and has around 50000 hits per month.
The signatures are manually produced by users of the wiki. They can be searched using a free addin to Microsoft Visual Studio.
PInvoker
PInvoker is an application which imports native DLLs and C++ .h files and exports fully formed and compiled P/Invoke interop DLLs. It overcomes the ambiguity problem by wrapping native pointer function parameters in PInvoker specific .NET interface classes. Instead of using standard .NET parameter types in P/Invoke method definitions (char[], string, etc.) it uses these interface classes in the P/Invoke function calls.
For instance, if we consider the above example code, PInvoker would produce a .NET P/Invoke function accepting a .NET interface class wrapping the native char*. The construction of this class could be from a string or from a char[]. The actual native memory structure for both is the same, but the respective interface class constructors for each type will populate the memory in different ways. The responsibility for deciding what .NET type needs to be passed into the function is therefore passed to the developer.
Microsoft Interop Assistant
Microsoft Interop Assistant is a free tool available with binaries and source code available for download on CodePlex. It is licensed under the Microsoft Limited Public License (Ms-LPL).
It has two parts:
- A converter which takes small sections of native C++ header file code containing struct and method definitions. It then produces C# P/Invoke code for you to copy and paste into your applications.
- A searchable database of converted Windows API constant, method and struct definitions.
Because this tool produces C# source code rather than a compiled dll the user is free to make any changes necessary to the code before use. So the ambiguity problem is solved by the application picking one particular .NET type to use in the P/Invoke method signature and if necessary the user can change this to the required type.
P/Invoke Wizard
The P/Invoke Wizard uses a similar method to the Microsoft Interop Assistant in that it accepts native C++ .h file code and produces C# (or VB.NET) code for you to paste into your .NET application code.
It also has options for which framework you wish to target: .NET Framework for the desktop or .NET Compact Framework for Windows Mobile smart devices (and Windows CE).
xInterop C++ .NET Bridge
xInterop C++ .NET Bridge is a windows application to created C# wrapper for native C++ DLLs and C++ bridge to access .NET assemblies, it comes with a C#/.NET library which wraps the standard C++ classes, such as string, iostream, etc., C++ classes and objects can be accessed from .NET.
This tool generates C# wrapper DLLs with source code from existing native C++ DLLs and the associated header files which are required by the tool to build a C# wrapper DLL. The P/Invoke signatures and data marshaling are generated by the application. The resulting C# wrapper has the similar interface of the C++ counterpart with the parameter type converted to the .NET code.
This tool recognizes template classes which is not exported from the C++ DLL and instantiates the template class and export it in a supplement DLL and the corresponding C++ interface can be used in .NET.
See also
- Blittable types
- Java Native Interface, the standard way for Java programs to access native code
- Java Native Access, the Java equivalent of P/Invoke
- Windows library files
- J/Direct, the no-longer maintained equivalent API for Microsoft Java Virtual Machine
References
- ↑Parameter marshaling is not to be confused with the general term marshalling, meaning Serialization. Marshaled parameters are copied in the CLR stack after their conversion to CTS types, but are not serialized.
- ↑"Double Thunking (C++)". 3 August 2021.
- ↑"Initialization of Mixed Assemblies". 3 August 2021.
- ↑"The PInvoke problem". learn.microsoft.com. February 6, 2004.
External links
- .NET terminology
- Common Language Infrastructure
