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žitie Windows API v C # .NET - 1. diel

Metódy a funkcie v samotnom API Windows sú používané na najnižšej úrovni .NET frameworku. Tu by som zdôraznil, že platforma .NET je síce rozsiahla, ale niekedy potrebujeme volať API Windows priamo, kedy hľadanú funkčnosť nemôžeme nájsť. V súčasnej dobe je vo Windows API k dispozícii zhruba 1000 funkcií a niekoľko komponentov COM, líši sa to podľa verzie.

Dokumentáciu microsoftu nájdeme na webovej stránke API Windows

Volanie API funkcií pomocou Plnvoke

Plnvoke umožňuje volanie funkcií v nespravovaných dynamických knižniciach (Dynamic Link Library, Dll) ako v knižniciach API pre Windows a je k dispozícii v mennom priestore:

using System.Runtime.InteropServices

Táto API sa deklaruje atribútom DllImport (trieda DllImportAttribute) a do konstruktoru tejto triedy odovzdáme meno súboru s dll. Funkcia sa deklarujú modifikátory Extern a Static.

Na prí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);

Ako hľadať hodnoty konštánt API vysvetlím neskôr. Atribút DllImport môže nadobúdať týchto vlastností:

vlatnosti API - C # - Pre pokročilých

Deklaračné metódy API

Marshalling - konverzie typov dát

Ak vykonávame konverziu z API, potom musíme typy dát zadať tak, aby boli identické s dátovými typmi danej funkcie. Zle zadané typy dát vedú k výnimke, prípadne k zrúteniu programu. Veľká väčšina dátových typov, ktoré sú písané v C alebo C ++, zodpovedá typom v C #. Niektoré typy v jazyku C# možno previesť na viacerých typov v jazyku C ++. Príkladom môže byť string, ktorý môže byť prevedený na LPSTR, LPWSTR, LPTSTR alebo BSTR. Typ string sa implicitne prevádza na typ BSTR. Atribútom MarhsalAs môžeme toto správanie ovplyvniť. V konstruktoru tohto atribútu zadáme hodnotu výpočtu UnmanagedType (cieľový typ).

Na prí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 zodpovedajúce typy z API Windows - vracajúci reťazec

Väčšina typov v API Windows je rovnaká ako v .NET. Zastavím sa u string, System.Text a StringBuilder. Tieto reťazce sa musia správne konvertovať, môžu sa vyskytovať v mnohých variantoch. Funkciám, vracajúcim reťazec, je potrebné pri volaní odovzdať objekt typu StringBuilder. Tieto funkcie nevracajú reťazce ako vrátenú hodnotu, ale v argumentoch. Je to z dôvodu, že niektoré programovacie jazyky, na príklad VB6, nepracujú s ukazovateľom na polia typu char. Reťazce sa z týchto dôvodov zapisujú do oblasti pamäte zadávaných reťazových premenných.

Ako príklad ukážem na funkciu:

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

Táto funkcia upravená pre použitie v C# .NET:

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

Tu by som chcel upozorniť, že musíme inštanciu StringBuilder umiestňuje dostatočne veľkú pamäť, musíme rezervovať dostatok miesta pre vrátený reťazec. Rezervujeme o jeden znak naviac ako je maximálna vrátený reťazec. Všetky funkcie API uzatvárajú vždy reťazec znakom 0.

Dôležité: Ak píšete program v C #, nemôžete sa dostať mimo oblasť pamäti, ktorá nie je vaša. To ale neplatí u API. Ak nepridelíme dostatočne veľkú oblasť pamäte, bude API StringBuilder obsadzovať pamäť, ktorá nie je jeho a táto činnosť vedie k ťažko hľadaným chybám. (Cudzie pamäť bude prepísaná a čo prepíše sa nie je definovateľné).

Veľkosť pamäte u danej funkcie sa zadáva v parametri uSize. Oblasť pamäte, ktorú zaberá inštancie typu string, nemožno zmeniť. Pokiaľ k reťazcu pridáte znak, vytvorí CLR novú oblasť pamäti a do nej skopíruje starý reťazec a na jeho koniec pripojí nový znak. Podobne postupuje CLR aj vtedy, ak uložíte do reťazovej premennej novú hodnotu. Funkcie API, ktoré vracia reťazec, vracia tiež dĺžku získaného reťazca.

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

Boolovské argumenty, polia a vrátené hodnoty

API funkcie, ktoré majú boolovské argumenty alebo vrátenej hodnoty alebo očakávajú v štruktúrach boolovská poľa, pracujú s typom BOOL z jazyka C. Je to 4 bitová hodnota, 0 pre false a nenulové pre true. V jazyku C# je veľkosť iba 1 bajt. Dokumentácia hovorí, že sa bool prevedie na hodnotu 1, 2 alebo 4 bajty, hodnota true sa konvertuje na -1 alebo 1. Ak sa deklaruje Boolovský argument alebo zložka štruktúry, musíme pomocou atribútu MarshalAs explicitne určiť spôsob prevodu na typ BOOL.

Na prí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ýslednú hodnotu funkcií vracajúcich hodnotu BOOL prevádzať nemôžeme. Musíme deklarovať vrátenú hodnotu týchto funkcií ako int a vykonať kontrolu, či sa vrátená hodnota nerovná 0. Toto je tiež odporúčané Microsoftom.

Odovzdávanie reťazcov do API Windows

API Windows u funkcií, ktoré reťazca dostávajú alebo je vracajú, sa vyskytujú približne v troch variantoch. Ako bezpečný prístup budeme používať pri deklarácii charset = CharSet.Auto. Ďalším problémom, ktorý nastáva, je prevod reťazcov. Štandardne sa typ string prevedie na BSTR (Basic String, je to reťazec podľa pravidiel jazyka Basic). Dôvodom je, že v COM, tu sa tiež používa PInvoke, sa používajú reťazca výlučne BSTR. Tento reťazec v závislosti od nastavenia obsahuje buď znaky ASCII alebo znaky Unicode a je ukončený znakom 0 a ako predponu má 32-bitovú celočíselnú hodnotu, ktorú je vyjadrená dĺžka reťazca. Premenná BSTR je ukazovateľ na začiatok reťazca, ale nie na začiatok hodnoty jeho dĺžky. Z týchto dôvodov je kompatibilné s LPWStr alebo LPStr.

Z týchto dôvodov je BSTR ideálny pre volanie funkcií 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ávne zadanej reťazca sa postaráme prostredníctvom atribútu MarshalAs a v závislosti na cieľovom systéme od Windows NT a vyššom budeme používať MarshalAs (UnmanagedType­.LPWSR).

Argumenty odovzdávané odkazom

Mnoho funkcií vracia v parametroch odovzdávaných odkazom tiež iné dáta než reťazca. V deklarácii to väčšinou spoznáme tak, že typ parametra začína LP (Long Pointer).

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

Ak sa u týchto parametrov jedná o hodnotovej typy jazyka C #, budeme ich deklarovať pomocou ref a budú sa odovzdávať odkazom. Pokiaľ sa bude jednať o referenčné typy (teoreticky k tomu nedochádza), potom sa pomocou ref deklarovať nebudú.

[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 volaní premenná size obsahuje dĺžku vráteného reťazca, v tejto ukážke neriešime. Nabudúce budeme pokračovať.


 

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