Sök…


Anmärkningar

Arbeta med Win32 API med C #

Windows visar många funktioner i form av Win32 API. Med hjälp av dessa API kan du utföra direkt operation i windows, vilket ökar prestandan för din applikation. Källa Klicka här

Windows exponerar ett brett utbud av API. För att få information om olika API: er kan du kolla webbplatser som pinvoke .

Importera funktion från icke-hanterad C ++ DLL

Här är ett exempel på hur du importerar en funktion som är definierad i en okontrollerad C ++ DLL. I C ++ källkod för "myDLL.dll" funktionen add definieras:

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
    return a + b;
}

Sedan kan det inkluderas i ett C # -program enligt följande:

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

Se Samtalskonventioner och C ++ -namnsblandning för förklaringar om varför extern "C" och __stdcall är nödvändiga.

Hitta det dynamiska biblioteket

När den externa metoden först åberopas kommer C # -programmet att söka efter och ladda rätt DLL. För mer information om var man söker för att hitta DLL och hur du kan påverka sökplatserna se denna stackoverflow-fråga .

Enkel kod för att exponera klass för com

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 ++ namn mangling

C ++ -kompilatorer kodar ytterligare information i namnen på exporterade funktioner, till exempel argumenttyper, för att möjliggöra överbelastningar med olika argument. Denna process kallas namngling . Detta orsakar problem med att importera funktioner i C # (och interop med andra språk i allmänhet), eftersom namnet på int add(int a, int b) -funktionen inte längre add , det kan vara ?add@@YAHHH@Z , _add@8 eller något annat beroende på kompilatorn och samtalskonventionen.

Det finns flera sätt att lösa problemet med namngling:

  • Exportera funktioner med extern "C" att växla till C extern länkning som använder C-namngling:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    Funktionsnamnet kommer fortfarande att vara manglat ( _add@8 ), men StdCall + extern "C" -namn mangling känns igen av C # -kompileraren.

  • Specificera exporterade funktionsnamn i myDLL.def modulens definitionsfil:

    EXPORTS
      add
    
    int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    Funktionsnamnet kommer att vara rent add i detta fall.

  • Importerar blandat namn. Du behöver lite DLL-visare för att se det blandade namnet, då kan du ange det uttryckligen:

    __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
    

Samtalskonventioner

Det finns flera konventioner om samtalsfunktioner, som specificerar vem (uppringare eller callee) som hoppar argument från bunten, hur argument skickas och i vilken ordning. C ++ använder Cdecl som standard, men C # förväntar sig StdCall , som vanligtvis används av Windows API. Du måste byta det ena eller det andra:

  • Ändra StdCall till StdCall i C ++:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    
  • Eller ändra Cdecl till Cdecl i C #:

    extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
    
    [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    

Om du vill använda en funktion med Cdecl och ett skiftat namn ser din kod så här:

__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
           EntryPoint = "?add@@YAHHH@Z")]
  • thiscall ( __thiscall ) används huvudsakligen i funktioner som är medlemmar i en klass.

  • När en funktion använder thiscall ( __thiscall ), skickas en pekare till klassen som den första parametern.

Dynamisk laddning och lossning av icke-hanterade DLL-filer

När du använder attributet DllImport måste du känna till korrekt dll och metodnamn vid sammanställningstiden . Om du vill vara mer flexibel och besluta vid körtid vilken dll och metoder som ska laddas kan du använda Windows API-metoderna LoadLibrary() , GetProcAddress() och FreeLibrary() . Detta kan vara till hjälp om biblioteket att använda beror på körtid.

Pekaren som returneras av GetProcAddress() kan kastas till en delegat med hjälp av Marshal.GetDelegateForFunctionPointer() .

Följande kodprov visar detta med myDLL.dll från föregående exempel:

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

Att hantera Win32-fel

När du använder interop-metoder kan du använda GetLastError API för att få ytterligare information om dina API-samtal.

DllImport-attribut SetLastError-attribut

SetLastError = true

Indikerar att callee kommer att ringa SetLastError (Win32 API-funktion).

SetLastError = false

Indikerar att callee inte kommer att ringa SetLastError (Win32 API-funktion), därför får du ingen felinformation.

  • När SetLastError inte är inställt är den inställd på falsk (standardvärde).

  • Du kan få felkoden med Marshal.GetLastWin32Error Method:

Exempel:

[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);

Om du försöker öppna mutex som inte finns, kommer GetLastError tillbaka ERROR_FILE_NOT_FOUND .

var lastErrorCode = Marshal.GetLastWin32Error();

if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
    //Deal with error         
}

Systemfelkoder kan hittas här:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx

GetLastError API

Det finns ett ursprungligt API för GetLastError som du också kan använda:

[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
  • När du ringer Win32 API från hanterad kod måste du alltid använda Marshal.GetLastWin32Error .

Här är varför:

Mellan ditt Win32-samtal som ställer in felet (anropar SetLastError) kan CLR ringa andra Win32-samtal som också kan ringa SetLastError , detta beteende kan åsidosätta ditt felvärde. Om du ringer GetLastError i det här scenariot kan du få ett ogiltigt fel.

Att ställa in SetLastError = true , ser till att CLR hämtar felkoden innan den kör andra Win32-samtal.

Fäst objekt

GC (Garbage Collector) ansvarar för rengöring av vårt skräp.

Medan GC städar vårt skräp tar han bort de oanvända föremålen från den hanterade högen som orsakar fragmentering av högen. När GC är klar med borttagningen utför den en heapkomprimering (defragmintation) som involverar rörliga föremål på högen.

Eftersom GC är inte deterministisk, när de passerar hanterat objekt referens / pekare till ursprunglig kod, kan GC sparka in när som helst, om det sker strax efter Inerop samtal, det finns en mycket bra möjlighet att objektet (som referens skickas till infödda) kommer flyttas på den hanterade högen - som ett resultat får vi en ogiltig referens på den hanterade sidan.

I det här scenariot bör du fästa objektet innan du skickar det till inbyggd kod.

Fäst objekt

Fäst objekt är ett objekt som inte får flytta av GC.

Gc-fästhandtag

Du kan skapa ett stiftobjekt med Gc.Alloc- metoden

GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned); 
  • Att få en fastgjord GCHandle till hanterat objekt markerar ett specifikt objekt som ett som inte kan flyttas av GC , förrän frigör handtaget

Exempel:

[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()
    }
}

försiktighetsåtgärder

  • När du fastnar (speciellt stora) objekt försöker du släppa den fästade GcHandle så snabbt som möjligt, eftersom det avbryter högdefragmentering.
  • Om du glömmer att frigöra GcHandle kommer ingenting att göra. Gör det i ett säkert kodavsnitt (som slutlig)

Läser strukturer med Marshal

Marshal klass innehåller en funktion som heter PtrToStructure , den här funktionen ger oss förmågan att läsa strukturer av en okontrollerad pekare.

PtrToStructure- funktionen fick många överbelastningar, men alla har samma avsikt.

Generisk PtrToStructure :

public static T PtrToStructure<T>(IntPtr ptr);

T - strukturtyp.

ptr - En pekare till ett okontrollerat minnesblock.

Exempel:

NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);       
  • Om du hanterar hanterade objekt medan du läser inbyggda strukturer, glöm inte att fästa ditt objekt :)
 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;
    }


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow