Platformaanroepservices - Platform Invocation Services
Platform Invocation Services , gewoonlijk P/Invoke genoemd , is een functie van Common Language Infrastructure- implementaties, zoals Microsoft 's Common Language Runtime , waarmee beheerde code native code kan aanroepen .
Beheerde code, zoals C# of VB.NET, biedt native toegang tot klassen, methoden en typen die zijn gedefinieerd in de bibliotheken die deel uitmaken van het .NET Framework. Hoewel het .NET Framework een uitgebreide set functionaliteit biedt, heeft het mogelijk geen toegang tot veel lagere besturingssysteembibliotheken die normaal in onbeheerde code zijn geschreven of bibliotheken van derden die ook in onbeheerde code zijn geschreven. P/Invoke is de techniek die een programmeur kan gebruiken om toegang te krijgen tot functies in deze bibliotheken. Aanroepen van functies binnen deze bibliotheken vinden plaats door de handtekening van de onbeheerde functie in beheerde code te declareren, die dient als de daadwerkelijke functie die kan worden aangeroepen zoals elke andere beheerde methode. De declaratie verwijst naar het bestandspad van de bibliotheek en definieert de functieparameters en return in beheerde typen die het meest waarschijnlijk impliciet worden gemarshald van en naar de onbeheerde typen door de Common Language Runtime (CLR). Wanneer de onbeheerde gegevenstypen te complex worden voor een eenvoudige impliciete conversie van en naar beheerde typen, stelt het raamwerk de gebruiker in staat om attributen voor de functie, het rendement en/of de parameters te definiëren om expliciet te verfijnen hoe de gegevens moeten worden gemarshald, zodat om tot uitzonderingen te leiden door dit impliciet te doen. Er zijn veel abstracties van programmeerconcepten op een lager niveau beschikbaar voor programmeurs van beheerde code in vergelijking met programmeren in onbeheerde talen. Als gevolg hiervan zal een programmeur met alleen ervaring met beheerde code programmeerconcepten zoals aanwijzers, structuren en doorverwijzingen moeten opfrissen om enkele van de meer basale, maar veelvoorkomende obstakels bij het gebruik van P/Invoke te overwinnen.
architectuur
Overzicht
Twee varianten van P/Invoke die momenteel in gebruik zijn, zijn:
expliciet
- Native code wordt geïmporteerd via dynamisch gekoppelde bibliotheken (DLL's)
-
Metadata ingebed in de assembly van de aanroeper definieert hoe de native code moet worden aangeroepen en hoe toegang tot gegevens moet worden verkregen ( meestal zijn toegewezen bronspecificaties vereist om de compiler te helpen bij het genereren van marshal-lijm )
- Deze definitie is het "Expliciete" deel
impliciet
- Door C++/CLI te gebruiken , kan een toepassing tegelijkertijd de beheerde heap gebruiken (door middel van tracking-pointers) en elke native geheugenregio, zonder de expliciete declaratie. (impliciet)
- Een belangrijk voordeel in dit geval is dat als onderliggende native datastructuren veranderen, zolang de naamgeving compatibel is, een brekende wijziging wordt vermeden.
- dwz het toevoegen/verwijderen/opnieuw ordenen van structuren in een native header wordt transparant ondersteund zolang de namen van de structuurleden niet ook veranderen.
Details
Bij gebruik van P/Invoke zorgt de CLR voor het laden van DLL 's en de conversie van de onbeheerde eerdere typen naar CTS- typen (ook wel parameter marshalling genoemd ). Om dit uit te voeren, gebruikt de CLR :
- Lokaliseert de DLL die de functie bevat.
- Laadt de DLL in het geheugen.
- Lokaliseert het adres van de functie in het geheugen en duwt de argumenten op de stapel , waarbij de gegevens indien nodig worden gegroepeerd.
P/Invoke is handig voor het gebruik van standaard (onbeheerde) C- of C++ DLL's . Het kan worden gebruikt wanneer een programmeur toegang moet hebben tot de uitgebreide Windows API , aangezien veel functies die door de Windows-bibliotheken worden geboden, geen beschikbare wrappers hebben . Wanneer een Win32-API niet wordt weergegeven door het .NET Framework, moet de wrapper naar deze API handmatig worden geschreven.
valkuilen
Het schrijven van P/Invoke-wrappers kan moeilijk en foutgevoelig zijn. Het gebruik van native DLL's betekent dat de programmeur niet langer kan profiteren van typeveiligheid en garbagecollection zoals gewoonlijk wordt geboden in de .NET-omgeving. Bij onjuist gebruik kan dit problemen veroorzaken zoals segmentatiefouten of geheugenlekken . Het kan moeilijk zijn om de exacte handtekeningen van de legacy-functies te krijgen voor gebruik in de .NET- omgeving, wat tot dergelijke problemen kan leiden. Voor dit doel bestaan er tools en websites om dergelijke handtekeningen te verkrijgen, waardoor problemen met handtekeningen worden voorkomen. [1]
Andere valkuilen zijn onder meer:
- Onjuiste gegevensuitlijning van door de gebruiker gedefinieerde typen in de beheerde taal: er zijn verschillende manieren waarop gegevens kunnen worden uitgelijnd, afhankelijk van compilers of compilerrichtlijnen in C en er moet op worden gelet dat de CLR expliciet wordt verteld hoe gegevens moeten worden uitgelijnd voor niet-blitbare typen . Een veelvoorkomend voorbeeld hiervan is wanneer u probeert een gegevenstype in .NET te definiëren om een unie in C weer te geven . Twee verschillende variabelen overlappen elkaar in het geheugen, en het definiëren van deze twee variabelen in een type in .NET zou ervoor zorgen dat ze zich op verschillende locaties in het geheugen bevinden, dus speciale attributen moeten worden gebruikt om het probleem te verhelpen.
- Interferentie met de locatie van gegevens door de garbagecollector van de beheerde taal: als een verwijzing lokaal is naar een methode in .NET en wordt doorgegeven aan een native functie, kan de garbagecollector die verwijzing terugvorderen wanneer de beheerde methode terugkeert. Er moet voor worden gezorgd dat de objectreferentie wordt vastgezet , zodat deze niet kan worden verzameld of verplaatst door de vuilnisman, wat zou resulteren in een ongeldige toegang door de native module.
Bij gebruik van C++/CLI is de uitgezonden CIL vrij om te communiceren met objecten die zich op de beheerde heap bevinden en tegelijkertijd met elke adresseerbare native geheugenlocatie. Een beheerd heap-resident object kan worden aangeroepen, gewijzigd of geconstrueerd met behulp van een eenvoudig "object->veld;" notatie om waarden toe te wijzen of methodeaanroepen te specificeren. Aanzienlijke prestatieverbeteringen zijn het gevolg van het elimineren van onnodige contextwisselingen en het verminderen van geheugenvereisten (kortere stapels).
Dit brengt nieuwe uitdagingen met zich mee:
- Code is vatbaar voor Double Thunking als deze niet specifiek wordt aangepakt
- Het probleem met de ladervergrendeling
Deze verwijzingen specificeren oplossingen voor elk van deze problemen als ze zich voordoen. Een belangrijk voordeel is de eliminatie van de structuurdeclaratie, de volgorde van velddeclaratie en uitlijningsproblemen zijn niet aanwezig in de context van C++ Interop.
Voorbeelden
Basisvoorbeelden
Dit eerste eenvoudige voorbeeld laat zien hoe u de versie van een bepaalde DLL kunt krijgen :
DllGetVersion functiehandtekening in de Windows API :
HRESULT DllGetVersion
(
DLLVERSIONINFO* pdvi
)
P/Invoke C# -code om de DllGetVersion- functie aan te roepen :
[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);
Het tweede voorbeeld laat zien hoe u een pictogram in een bestand kunt extraheren:
ExtractIcon- functiehandtekening in de Windows API:
HICON ExtractIcon
(
HINSTANCE hInst,
LPCTSTR lpszExeFileName,
UINT nIconIndex
);
P/Invoke C#-code om de ExtractIcon- functie aan te roepen :
[DllImport("shell32.dll")]
static extern IntPtr ExtractIcon(
IntPtr hInst,
[MarshalAs(UnmanagedType.LPStr)] string lpszExeFileName,
uint nIconIndex);
Dit volgende complexe voorbeeld laat zien hoe u een gebeurtenis kunt delen tussen twee processen in het Windows-platform :
CreateEvent- functiehandtekening:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
P/Invoke C#-code om de CreateEvent- functie aan te roepen :
[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateEvent(
IntPtr lpEventAttributes,
bool bManualReset,
bool bInitialState,
[MarshalAs(UnmanagedType.LPStr)] string lpName);
Een complexer voorbeeld
// native declaration
typedef struct _PAIR
{
DWORD Val1;
DWORD Val2;
} 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.
template<>
inline CLR_PAIR^ marshal_as<CLR_PAIR^, PAIR> (const PAIR&Src) { // Note use of de/referencing. It must match your use.
CLR_PAIR^ Dest = gcnew CLR_PAIR;
Dest->Val1 = Src.Val1;
Dest->Val2 = Src.Val2;
return Dest;
};
CLR_PAIR^ mgd_pair1;
CLR_PAIR^ mgd_pair2;
PAIR native0,*native1=&native0;
native0 = NativeCallGetRefToMemory();
// Using marshal_as. It makes sense for large or frequently used types.
mgd_pair1 = marshal_as<CLR_PAIR^>(*native1);
// Direct field use
mgd_pair2->Val1 = native0.Val1;
mgd_pair2->val2 = native0.val2;
return(mgd_pair1); // Return to C#
Hulpmiddelen
Er zijn een aantal tools die zijn ontworpen om te helpen bij het maken van P/Invoke-handtekeningen.
Het schrijven van een hulpprogramma dat C++-headerbestanden en native DLL- bestanden zou importeren en automatisch een interface-assemblage zou produceren, blijkt behoorlijk moeilijk te zijn. Het grootste probleem bij het maken van zo'n importeur/exporter voor P/Invoke-handtekeningen is de dubbelzinnigheid van sommige C++-functieaanroepparametertypen.
Brad Abrams heeft dit te zeggen over het onderwerp: Het P/Invoke-probleem .
Het probleem ligt bij C++-functies zoals de volgende:
__declspec(dllexport) void MyFunction(char *params);
Wat voor soort moeten we gebruiken voor de parameter params in onze P / Invoke handtekening? Dit kan een C++ null-beëindigde string zijn, of een char array of een output char parameter. Dus moeten we string , StringBuilder , char [] of ref char gebruiken ?
Ongeacht dit probleem zijn er een paar tools beschikbaar om de productie van P/Invoke-handtekeningen eenvoudiger te maken.
Een van de onderstaande tools, xInterop C++ .NET Bridge, heeft dit probleem opgelost door meerdere overschrijvingen van dezelfde C++-methode in de .NET-wereld te implementeren. Ontwikkelaars kunnen dan de juiste kiezen om de aanroep te doen.
PINvoke.net
PInvoke.net is een wiki met P/Invoke-handtekeningen voor een groot aantal standaard Windows API's. Het is eigendom van Redgate Software en heeft ongeveer 50000 hits per maand.
De handtekeningen worden handmatig aangemaakt door gebruikers van de wiki. Ze kunnen worden doorzocht met behulp van een gratis invoegtoepassing voor Microsoft Visual Studio .
PINvoker
PInvoker is een applicatie die native DLL's en C++ .h-bestanden importeert en volledig gevormde en gecompileerde P/Invoke interop-DLL's exporteert . Het lost het ambiguïteitsprobleem op door native pointer-functieparameters in PInvoker-specifieke .NET-interfaceklassen te verpakken. In plaats van standaard .NET-parametertypen te gebruiken in P/Invoke-methodedefinities ( char[] , string , enz.) gebruikt het deze interfaceklassen in de P/Invoke-functieaanroepen.
Als we bijvoorbeeld de bovenstaande voorbeeldcode beschouwen, zou PInvoker een .NET P/Invoke- functie produceren die een .NET-interfaceklasse accepteert die de native char * pointer omhult . De constructie van deze klasse kan zijn van een string of van een char [] array. De eigenlijke native geheugenstructuur voor beide is hetzelfde, maar de respectieve interfaceklasseconstructors voor elk type zullen het geheugen op verschillende manieren vullen. De verantwoordelijkheid om te beslissen welk .NET-type in de functie moet worden doorgegeven, wordt daarom overgedragen aan de ontwikkelaar.
Microsoft Interoperabiliteitsassistent
Microsoft Interop Assistant is een gratis tool die beschikbaar is met binaire bestanden en broncode die kunnen worden gedownload op CodePlex . Het is gelicentieerd onder de Microsoft Limited Public License (Ms-LPL).
Het heeft twee delen:
- Een converter die kleine delen van de native C++-headerbestandscode gebruikt die struct- en methodedefinities bevat. Het produceert vervolgens C# P/Invoke-code die u kunt kopiëren en plakken in uw toepassingen.
- Een doorzoekbare database met geconverteerde Windows API-constante, methode- en structdefinities.
Omdat deze tool C#-broncode produceert in plaats van een gecompileerde dll, staat het de gebruiker vrij om vóór gebruik de nodige wijzigingen aan de code aan te brengen. Het ambiguïteitsprobleem wordt dus opgelost door de toepassing een bepaald .NET-type te kiezen om te gebruiken in de P/Invoke-methodehandtekening en indien nodig kan de gebruiker dit wijzigen in het vereiste type.
P/Invoke Wizard
De P/Invoke-wizard gebruikt een vergelijkbare methode als de Microsoft Interop-assistent, omdat deze native C++ .h-bestandscode accepteert en C# (of VB.NET)-code produceert die u in uw .NET-toepassingscode kunt plakken.
Het heeft ook opties voor welk framework u zich wilt richten: .NET Framework voor de desktop of .NET Compact Framework voor Windows Mobile smart devices (en Windows CE).
xInterop C++ .NET Bridge
xInterop C++ .NET Bridge is een Windows-toepassing om C#-wrapper te maken voor native C++ DLL's en C++ bridge om toegang te krijgen tot .NET-assemblies, het wordt geleverd met een C#/.NET-bibliotheek die de standaard C++-klassen omvat, zoals string, iostream, enz. , C++ klassen en objecten zijn toegankelijk vanuit .NET.
Deze tool genereert C#-wrapper-DLL's met broncode van bestaande native C++ DLL's en de bijbehorende headerbestanden die de tool nodig heeft om een C#-wrapper-DLL te bouwen. De P/Invoke-handtekeningen en datamarshaling worden gegenereerd door de applicatie. De resulterende C#-wrapper heeft de vergelijkbare interface van de C++-tegenhanger met het parametertype dat is geconverteerd naar de .NET-code.
Deze tool herkent sjabloonklassen die niet zijn geëxporteerd uit de C++ DLL en instantieert de sjabloonklasse en exporteert deze in een aanvullende DLL en de bijbehorende C++-interface kan worden gebruikt in .NET.
Zie ook
- Blitbare typen
- Java Native Interface , de standaardmanier voor Java-programma's om toegang te krijgen tot native code
- Java Native Access , het Java-equivalent van P/Invoke
- Windows-bibliotheekbestanden
- J/Direct , de niet langer onderhouden equivalente API voor Microsoft Java Virtual Machine
Referenties
Externe links
- Een site gewijd aan P/Invoke
- J/Invoke Java-toegang tot Win32 API of Linux/Mac OS X gedeelde bibliotheken, vergelijkbaar met P/Invoke
- [2] Impliciete P/Invoke met speciale focus op technieken voor uitbreiding naar de marshaling-template
- 3 artikelen van Microsoft die deze methoden contrasteren , Expliciete PInvoke, Impliciete C++ Interop en "A Closer Look at Platform Invoke" gebruiken
- Microsoft Interop Assistant Hoofdpagina van Microsoft Interop Assistant.
- P/Invoke Wizard P/Invoke Wizard homepage.
- PInvoker PInvoker hoofdpagina.
- xInterop C++ .NET Bridge xInterop C++ .NET Bridge hoofdpagina