Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Použitie Windows API v C # .NET - 2. diel

V minulom dieli sme načal prácu s Windows API z C# .NET. Dnes budeme pokračovať.

Odovzdávanie štruktúr a pevných reťazcov

Väčšina funkcií API Windows pracuje aj so štruktúrami. V tých môžu byť zložky, ktoré pracujú so znakovými reťazcami. Štruktúry vo funkciách API musí mať vždy definovanú veľkosť, sú reťazové zložky definované s pevnou dĺžkou.

Vezmeme na príklad funkciu:

//deklarace funkce
BOOL GetVersionEx(LPOSVERSIONINFO lpVersioninfo);
//očekávaná struktura
typedef struct _OSVERSIONINFO{
    DWORD dwdwOSVersionInfoSize;
    DWORD dwMajorVersion;
    DWORD dwMinorVersion;
    DWORD dwBuildNumber ;
    DWORD dwPlatformId;
    TCHAR szCSVersion[128];
}  OSVERSIONINFO;

Vykonáme napodobenie tejto štruktúry do C# .NET. Najskôr stanovíme v atribúte StructLayout, že zložky štruktúry majú byť uložené sekvenčne v pamäti. Stanovíme si StructLayout.Se­quential. Atribútom MarshalAs stanovíme, že reťazec sa odovzdá hodnotou a to tak, že do konstruktoru zadáme UnmanagetType­.ByValTStr a pomocou zložky Size definujeme pevnú veľkosť reťazca.

Deklarácia v C# .NET bude vyzerať takto:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct OSVERSIONINFO{
    public uint dwdwOSVersionInfoSize;
    public uint dwMajorVersion;
    public uint dwMinorVersion;
    public uint dwBuildNumber ;
    public uint dwPlatformId;
    [MarshalAs(UnmanagedType.ByValTstr,SizeConst = 128)]
    public string szCSVersion;
};

** Dôležité: **

Veľkosť pevných reťazcov sa vždy udáva v bajtoch. V tomto prípade je veľkosť reťazca 128 bajtov. Preto musíme reťazce v štruktúrach odovzdávať ako ASCI. Ak odovzdáte reťazec v Unicode, pole pre reťazec by bolo malé a volanie funkcie by skončilo chybou API122 (The dáta area passed to system call is too small).

Aj keď charset = charset. Ansi je default hodnota a je vždy lepšie ju zadať.

//Deklarace funkce GetVersionEx
[DllImport("kernel32.dll" ]
public static extern int GetVersionEx(ref OSVERSIONINFO lpVersionInfo);

Argument lpVersionInfo musíme deklarovať pomocou ref, pretože štruktúra sa odovzdáva odkazom. Volanie danej funkcie bude vykonané takto:

OSVERSIONINFO veri = new OSVERSIONINFO();

CLR automaticky vytvorí pevný reťazec, my musíme vopred zadať skutočnú veľkosť pamäte, aby mohla API funkcie zložku štruktúry správne vyhodnotiť.

veri.dwdwOSVersionInfoSize = Marshal.Sizeof(typeof(OSVERSIONINFO));
//voláme funkci
if(GetVersionEx(ref veri) != 0)
{
    Console.WriteLine("Verze windows:{0} ,{1},{2}" ,
        veri.dwMajorVersion, veri.dwMinorVersion , veri.dwBuildNumber );
    Console.WriteLine("Service-Pack:{0}"  , veri.szCSVersion);
}
else
    Console.WriteLine("Chyba při volání funkce GetVersionEx");
}

Konštanty a ich hodnoty v API

Problém konštánt je v tom, že často nie sú udávané. My tieto hodnoty potrebujeme pre deklaráciu API. Poku máme nainštalované Visual štúdio C ++ alebo .NET C ++, sú tieto konštanty v hlavičkových súboroch. Sú tiež SDK pre Windows. Vo Visual Studiu nájdeme v zložke:

../Program Files/Microsoft visual studio 10/VC/include/
../Program Files/Microsoft visual studio 13/VC/include/

Tieto konštanty musíme previesť z C ++ do C# .NET

Na príklad:

//C++
#define FO_MOVE     0x001
#define FO_COPY     0x002
#define FO_DELETE   0x003
#define FO_RENAME   0x004

//C#
private const int FO_MOVE = 0x001;
private const int FO_COPY = 0x002;
private const int FO_DELETE = 0x003;
private const int FO_RENAME = 0x004;

Chyby API Windows

Funkcia API negenerujú žiadne výnimky, vracia chybový kód, ktorý je numerický. Niektoré priamo vracia kód ako vrátenú hodnotu, iné vracia hodnotu BOOL. Väčšina funkcií volá pri chybe interne SetLastError () a táto funkcia zabezpečuje prostredníctvom Windows, že chyby sú ukladané.

Volaním funkcie GetLastError () môže programátor v C ++ daný chybový kód zistiť. V jazyku C# je lepšie volať metódu GetLastWin32Error triedy Marshal zo jmeného priestoru System.Runtime­.InteropServi­ces z dôvodu, že .NET Framework dokáže interne zavolať ďalšie funkcie z API, ktoré môžu chybu odstrániť. Podmienkou pre zavolanie API SetLastError je nutné nastaviť zložku SetLastError v atribúte DllImport na true.

Ako príklad uvediem funkciu GetDiskFreeSpaceEx informácie o diskovej jednotke:

[DllImport("Kernel32.dll" , SetLastError=true , CharSet=CharSet.Auto)]
public static extern int GetDiskFreeSpaceEx(
    string lpDirectoryName,
    ref unlong lpFreeBytesAvailable,
    ref unlong lpTotalNumberBytes,
    ref unlong lpTotalNumberOffFreeBytes);

// pokud při volání zadáte diskovou jednotku , která není
unlong userSpace = 0,totalSize = 0 ,totalSpace = 0;
if(GetDiskFreeSpaceEX("x" , ref userSpace , ref totalSize , ref totalSpace) == 0)
{
//vrací tato funkce 0 a chyba API
int apiError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
}

Vrátená hodnota je 3, podľa dokumentácie "The specified path could not be found" (Systém nemôže nájsť zadanú cestu).

Prevod chybových kódov API na opis chyby

Číselné vyjadrenie chyby je nepraktické a pre užívateľov skoro nepoužiteľné. Profesionálne programy by mali podávať výstižná chybové hlásenia. Vrátený chybový kód funkcií GetLastWin32Error (...) môžeme väčšinou previesť na opis chyby funkcií FormatMessage.

[DllImport("Kernel32.dll")]
private static extern int FormatMessage(int dwFlags , IntPtr lpSource , int dwMessageId,
    int dwLanguageId , StringBuilder lpBuffer , int nSize , string[] Arguments);

Argument dwFlags väčšinou používa konštantu FORMAT_MESSAGE_FROM_SYS­TEM (0x1000), opis chyby je v tabuľke hlásení systému. Ak je chybové hlásenie v súbore Dll, ktorý k systému nepatrí, zadáme príznak
FORMAT_MESSAGE_FROM_HMO­DULE (0x08000), čím vyberieme tabuľku hlásenie chyby nejakého modulu (súboru Dll).
V tomto prípade zadáme do lpSource číslo handle tohto modulu. Zistíme ho pomocou API GetModuleHandle, ktorú deklarujeme takto:

[DllImport("Kernel32.dll")]
static extern IntPtr GetModuleHandle(string lpFileName);

Predpoklad je, že príslušný modul je zavedený do adresného priestoru aplikácie. Váš program volá funkciu zo súboru Dll, ktorá zhodnotenú chybu spôsobuje.

Pre poriadok lpSource je vo funkcii deklarovaný ako ukazovateľ na void, pre C ++ má argument int.

Do dwMessageID zadáme chybový kód. Do dwLanguageId zadáme identifikátor jazyka, ak zadáme 0, výstup bude v jazyku nášho systému. Na príklad 0x405 je Čeština, hodnoty konštánt sú v dokumentácii .NET.

V argumente lpBuffer zadáme dostatočne veľký priestor pre text chybového hlásenia.

V argumente nSize zadáme počiatočnú veľkosť objektu StringBuider a nSize musia byť tak veľké ako veľkosť StringBuider. Inak bude API zapisovať do nedefinovateľné pamäti a hrozí pád programu.

Posledný parameter nie je pre nás zaujímavý a nahradíme ho null. Výber hlásenia chyby API, ktoré je uložené v systémovej tabuľke:

public void Test()
{
    try
    {
        ulong userSpace = 0;
        ulong totalSize = 0;
        ulong totalSpace = 0;
        if (GetDiskFreeSpaceEx("d:\\", ref userSpace, ref totalSize, ref totalSpace) == 0)
        {
            int apiError = Marshal.GetLastWin32Error();
            StringBuilder errorMessage = new StringBuilder(1024);
            string formatExpression = "%1,%2%!";
            IntPtr formatPtr = Marshal.StringToHGlobalAnsi(formatExpression);
            const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
            const uint FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
            const uint FORMAT_MESSAGE_FROM_STRING = 0x00000400;
            uint length=FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
            formatPtr, 0, 0, errorMessage, 1024, null);
            if (length == 0)
            {
                FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero,
(uint) Marshal.GetLastWin32Error(), 0, errorMessage, 1024, null);
                Console.WriteLine("Chyba API " + errorMessage.ToString());
            }
            else
            Console.WriteLine("Format result:" + errorMessage.ToString() + ", length:" +
length.ToString());
        }
        else
            Console.WriteLine("UserSpace:" + userSpace.ToString() + ", TotalSize:" +
totalSize.ToString() + ", TotalSpace:" + totalSpace.ToString());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

Vyhodnotenie kódu chyby API, ktoré je uložené v systémovej tabuľke hlásení (prípad súborov Dll, ktoré k vlastnému systému nepatrí):

[DllImport("wininet.dll")]
public static extern int InternetGetConnectedState(out int flags, int reserved);
.....
int flags;
if(InternetGetConnectedState(out flags, 0) == 0 )
{
    int apiError = Marshal.GetLastWin32Error();
    //získání popisu chyby
    IntPtrhModule hModule =  GetModuleHandle("wininet.dll");
    StringBuilder errorMessage = new StringBuilder(1024);
    const int FORMAT_MESSAGE_FROM_HMODULE = 0x08000;
    if(FormatMessage(FORMAT_MESSAGE_FROM_HMODULE , hModule,apiError , 0,ErrorMessage,1024,null) > 0)
    {
        throw new Exception(message.ToString());
    }
    else
    {
        throw new Exception("Chyba API " + ErrorMessage);
    }
}

Záver

V tomto tutoriále som v stručnosti ukázal využití Windows API v C #. NET. Ukážka triedy je priložená v súbore "API windows.cs".


 

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 92x (5.64 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

 

Všetky články v sekcii
C # - Pre pokročilých
Článok pre vás napísal zpavlu
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
C# , C++ a assembler
Aktivity