IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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žití Windows API v C# .NET - 1. díl

Metody a funkce v samotném API Windows jsou používány na nejnižší úrovni .NET frameworku. Zde bych zdůraznil, že platforma .NET je sice rozsáhlá, ale někdy potřebujeme volat API Windows přímo, kdy hledanou funkčnost nemůžeme najít. V současné době je ve Windows API k disposici zhruba 1000 funkcí a několik komponent COM, liší se to podle verze.

Dokumentaci microsoftu najdeme na webové stránce API Windows

Volání API funkcí pomocí Plnvoke

Plnvoke umožňuje volání funkcí v nespravovaných dynamických knihovnách (Dynamic Link Library, Dll) jako v knihovnách API pro Windows a je k dispozici ve jmenném prostoru:

using System.Runtime.InteropServices

Tato API se deklaruje atributem DllImport (třída DllImportAttribute) a do konstruktoru této třídy předáme jméno souboru s dll. Funkce se deklarují modifikátory Extern a Static.

Na příklad:

//API PlaySound je v dynamické knihovně winmm.dll
[DllImport("winmm.dll" , SetLastError=true)]
public static extern int PlaySound(string pszSound,long hmod,int fdwSound);

//tuto funkci pak v C# voláme jako metodu
const int SND_FILENAME = 0x00020000;
PlaySound("C:\\windows\\media\\chimes.wav",0,SND_FILENAME);

Jak hledat hodnoty konstant API vysvětlím později. Atribut DllImport může nabývat těchto vlastností:

Vlatnosti API - Programování služeb ve Windows

Deklarační metody API

Marshalling - konverze typů dat

Pokud provádíme konverzi z API, pak musíme typy dat zadat tak, aby byly identické s datovými typy dané funkce. Špatně zadané typy dat vedou k výjimce, popřípadě ke zhroucení programu. Velká většina datových typů, které jsou psané v C nebo C++, odpovídá typům v C#. Některé typy v jazyce C# lze převést na více typů v jazyce C++. Příkladem může být string, který může být převeden na LPSTR, LPWSTR, LPTSTR nebo BStr. Typ string se implicitně převádí na typ BStr. Atributem MarhsalAs můžeme toto chování ovlivnit. V konstruktoru tohoto atributu zadáme hodnotu výpočtu UnmanagedType (cílový typ).

Na příklad:

//originální deklarace funkce
/*
Bool GetDiskFreeSpace(
    LPCTSTR  lpDirectoryName,
    PULARGE_INTEGER lpFreeBytesAvailable,
    PULARGE_INTEGER lpTotalNumberOfBytes,
    PULARGE_INTEGER lpTotalNumberOfFreeBytes
    );
*/

[DllImport ("Kernel32.dll" , SetLastError = true , CharSet = CharSet.Auto)]
public static extern int GetDiskFreeSpaceEx(
    [MarshalAs(UnmanagedType.LPTSR )] string lpDirectoryName ,
    ref unlong lpFreeBytesAvailable,
    ref unlong lpTotalNumberOfBytes,
    ref unlong lpTotalNumberOfFreeBytes );

Typy .NET a odpovídající typy z API Windows - vracející řetězec

Většina typů v API Windows je stejná jako v .NET. Zastavím se u string, System.Text a StringBuilder. Tyto řetězce se musí správně konvertovat, mohou se vyskytovat v mnoha variantách. Funkcím, vracejícím řetězec, je třeba při volání předat objekt typu StringBuilder. Tyto funkce nevracejí řetězce jako vrácenou hodnotu, nýbrž v argumentech. Je to z důvodu, že některé programovací jazyky, na příklad VB6, nepracují s ukazatelem na pole typu char. Řetězce se z těchto důvodů zapisují do oblasti paměti zadávaných řetězových proměnných.

Jako příklad ukáži na funkci :

/*
UINT GetWindowsDirectory(
    LPTSTR lpBuffer,    //buffer for windows directory
    UINT uSize      //size of directory buffer
    );
*/

Tato funkce upravená pro použití v C# .NET:

[DllImport ("Kernel32.dll" , SetLastError = true , CharSet = CharSet.Auto)]
public static extern uint GetWindowsDirectory(
    StringBuilder lpBuffer , uint uSize);

Zde bych chtěl upozornit, že musíme instanci StringBuilder přidělit dostatečně velkou paměť, musíme rezervovat dostatek místa pro vrácený řetězec. Rezervujeme o jeden znak navíc než je maximální vrácený řetězec. Všechny funkce API uzavírají vždy řetězec znakem 0.

Důležité: Pokud píšete program v C#, nemůžete se dostat mimo oblast paměti, která není vaše. To ale neplatí u API. Pokud nepřidělíte dostatečně velkou oblast paměti, bude API StringBuilder obsazovat paměť, která není jeho a tato činnost vede k obtížně hledaným chybám. (Cizí paměť bude přepsána a co bude přepsáno není definovatelné).

Velikost paměti u dané funkce se zadává v parametru uSize. Oblast paměti, kterou zabírá instance typu string, nelze změnit. Pokud k řetězci přidáte znak, vytvoří CLR novou oblast paměti a do ní zkopíruje starý řetězec a na jeho konec připojí nový znak. Podobně postupuje CLR i tehdy, pokud uložíte do řetězové proměnné novou hodnotu. Funkce API, které vrací řetězec, vrací též délku získaného řetězce.

StringBuilder buffer = new StringBuilder(261);  // alokujeme paměť
if(GetWindowsDirectory(buffer,261) > 0)
    Console.WriteLine("Složka windows:{0}" , buffer.ToString());
else
    Console.WriteLine("Chyba" );

Booleovské argumenty, pole a vrácené hodnoty

API funkce, které mají booleovské argumenty nebo vrácené hodnoty nebo očekávají ve strukturách booleovská pole, pracují s typem BOOL z jazyka C. Je to 4 bitová hodnota, 0 pro false a nenulová pro true. V jazyce C# je velikost pouze 1 bajt. Dokumentace praví, že se bool převede na hodnotu 1, 2 nebo 4 bajty, hodnota true se konvertuje na -1 nebo 1. Pokud se deklaruje booleovský argument nebo složka struktury, musíme pomocí atributu MarshalAs explicitně určit způsob převodu na typ BOOL.

Na příklad:

//SHFILEOPSTRUCT tuto používá SHIFTileOperation
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
Public struct SHFILEOPSTRUCT
{
    public IntPtr hwnd ;
    ...další typy
    [MarshalAs(UnmanagedType Bool)]
    public bool fAnyOperationsAborted;
    public IntPtr hNameMapings;
    ...další typy
}

Výslednou hodnotu funkcí vracejících hodnotu BOOL převádět nemůžeme. Musíme deklarovat vrácenou hodnotu těchto funkcí jako int a provést kontrolu, zda se vrácená hodnota nerovná 0. Toto je též doporučeno Microsoftem.

Předávání řetězců do API Windows

API Windows u funkcí, které řetězce dostávají nebo je vracejí, se vyskytují zhruba ve třech variantách. Jako bezpečný přístup budeme používat při deklaraci CharSet = CharSet.Auto. Dalším problémem, který nastává, je převod řetězců. Standardně se typ string převede na BStr (Basic String, je to řetězec podle pravidel jazyka Basic). Důvodem je, že v COM, zde se také používá PInvoke, se používají řetězce výlučně BStr. Tento řetězec v závislosti na nastavení obsahuje buď znaky ASCII nebo znaky Unicode a je ukončen znakem 0 a jako předponu má 32bitovou celočíselnou hodnotu, kterou je vyjádřena délka řetězce. Proměnná BStr je ukazatel na začátek řetězce, ale ne na začátek hodnoty jeho délky. Z těchto důvodů je kompatabilní s LPWStr nebo LPStr.

Z těchto důvodů je BStr ideální pro volání funkcí API.

[DllImport ("Kernel32.dll" , SetLastError = true , CharSet = CharSet.Auto)]
public static extern int GetDiskFreeSpaceEx(
    [MarshalAs(UnmanagedType.LPWSR)] string lpDirectoryName ,
    ref unlong lpFreeBytesAvailable,
    ref unlong lpTotalNumberOfBytes,
    ref unlong lpTotalNumberOfFreeBytes);

O správně zadané řetězce se postaráme prostřednictvím atributu MarshalAs a v závislosti na cílovém systému od Windows NT a vyšším budeme používat MarshalAs (UnmanagedType­.LPWSR).

Argumenty předávané odkazem

Mnoho funkcí vrací v parametrech předávaných odkazem také jiná data než řetězce. V deklaraci to většinou poznáme tak, že typ parametru začíná LP (Long Pointer).

//Jako příklad uvedu funkci GetUserName a je deklarována takto
BOOL GetUserName(LPTSTR lpBuffer,LPWORD nSize);

Pokud se u těchto parametrů jedná o hodnotové typy jazyka C#, budeme je deklarovat pomocí ref a budou se předávat odkazem. Pokud se bude jednat o referenční typy (teoreticky k tomu nedochází), pak se pomocí ref deklarovat nebudou.

[DllImport ("Advapi32.dll" , SetLastError = true )]
public static extern int GetUserName{
    StringBuilder lpBuffer,
    ref uint nSize
};
//test funkce
StringBuilder userName = new StringBuilder(1024);
uint size = 1024;
if(GetUserName(userName , ref size)!= 0)
    Console.WriteLine(userName.ToString());
else
    Console.WriteLine("Chyba volání funkce");

Po volání proměnná size obsahuje délku vráceného řetězce, v této ukázce neřešíme. Příště budeme pokračovat.


 

Všechny články v sekci
Programování služeb ve Windows
Článek pro vás napsal zpavlu
Avatar
Uživatelské hodnocení:
1 hlasů
C# , C++ a assembler
Aktivity