Použití Windows API v C# .NET - 2. díl
V minulém dílu jsme načali práci s Windows API z C# .NET. Dnes budeme pokračovat.
Předávání struktur a pevných řetězců
Většina funkcí API Windows pracuje také se strukturami. V těch mohou být složky, které pracují se znakovými řetězci. Struktury ve funkcích API musí mít vždy definovanou velikost, jsou řetězové složky definovány s pevnou délkou.
Vezmeme na příklad funkci:
//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;
Provedeme napodobení této struktury do C# .NET. Nejdříve stanovíme v atributu StructLayout, že složky struktury mají být uloženy sekvenčně v paměti. Stanovíme si StructLayout.Sequential. Atributem MarshalAs stanovíme, že řetězec se předá hodnotou a to tak, že do konstruktoru zadáme UnmanagetType.ByValTStr a pomocí složky Size definujeme pevnou velikost řetězce.
Deklarace v C# .NET bude vypadat 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é: **
Velikost pevných řetězců se vždy udává v bajtech. V tomto případě je velikost řetězce 128 bajtů. Proto musíme řetězce ve strukturách předávat jako ASCI. Pokud předáte řetězec v Unicode, pole pro řetězec by bylo malé a volání funkce by skončilo chybou API122 (The data area passed to system call is too small).
I když CharSet = CharSet. Ansi je default hodnota a je vždy lepší ji zadat.
//Deklarace funkce GetVersionEx [DllImport("kernel32.dll" ] public static extern int GetVersionEx(ref OSVERSIONINFO lpVersionInfo);
Argument lpVersionInfo musíme deklarovat pomocí ref, protože struktura se předává odkazem. Volání dané funkce bude provedeno takto:
OSVERSIONINFO veri = new OSVERSIONINFO();
CLR automaticky vytvoří pevný řetězec, my musíme předem zadat skutečnou velikost paměti, aby mohla API funkce složku struktury správně vyhodnotit.
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"); }
Konstanty a jejich hodnoty v API
Problém konstant je v tom, že mnohdy nejsou udávány. My tyto hodnoty potřebujeme pro deklaraci API. Poku máme nainstalované Visual studio C++ nebo .NET C++, jsou tyto konstanty v hlavičkových souborech. Jsou také SDK pro Windows. Ve Visual Studiu najdeme ve složce:
../Program Files/Microsoft visual studio 10/VC/include/ ../Program Files/Microsoft visual studio 13/VC/include/
Tyto konstanty musíme převést z C++ do C#.NET
Na pří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
Funkce API negenerují žádné výjimky, vrací chybový kód, který je numerický. Některé přímo vrací kód jako vrácenou hodnotu, jiné vrací hodnotu BOOL. Většina funkcí volá při chybě interně SetLastError() a tato funkce zabezpečuje prostřednictvím Windows, že chyby jsou ukládány.
Voláním funkce GetLastError() může programátor v C++ daný chybový kód zjistit. V jazyce C# je lepší volat metodu GetLastWin32Error třídy Marshal ze jmeného prostoru System.Runtime.InteropServices z důvodu, že .NET Framework dokáže interně zavolat další funkce z API, které mohou chybu odstranit. Podmínkou pro zavolání API SetLastError je nutné nastavit složku SetLastError v atributu DllImport na true.
Jako příklad uvedu funkci GetDiskFreeSpaceEx informace o diskové jednotce:
[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ácená hodnota je 3, podle dokumentace "The specified path could not be found" (Systém nemůže najít zadanou cestu).
Převod chybových kódů API na popis chyby
Číselné vyjádření chyby je nepraktické a pro uživatele skoro nepoužitelné. Profesionální programy by měly podávat výstižná chybová hlášení. Vrácený chybový kód funkcí GetLastWin32Error(...) můžeme většinou převést na popis chyby funkcí 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ětšinou používá konstantu FORMAT_MESSAGE_FROM_SYSTEM
(0x1000), popis chyby je v tabulce hlášení systému. Pokud je chybové
hlášení v souboru Dll, který k systému nepatří, zadáme příznak
FORMAT_MESSAGE_FROM_HMODULE (0x08000), čímž vybereme tabulku hlášení
chyby nějakého modulu (souboru Dll).
V tomto případě zadáme do lpSource číslo handle tohoto modulu. Zjistíme
ho pomocí API GetModuleHandle, kterou deklarujeme takto:
[DllImport("Kernel32.dll")] static extern IntPtr GetModuleHandle(string lpFileName);
Předpoklad je, že příslušný modul je zaveden do adresního prostoru aplikace. Váš program volá funkci ze souboru Dll, která vyhodnocovanou chybu způsobuje.
Pro pořádek lpSource je ve funkci deklarován jako ukazatel na void, pro C++ má argument int.
Do dwMessageID zadáme chybový kód. Do dwLanguageId zadáme identifikátor jazyka, pokud zadáme 0, výstup bude v jazyce našeho systému. Na příklad 0x405 je čeština, hodnoty konstant jsou v dokumentaci .NET.
V argumentu lpBuffer zadáme dostatečně velký prostor pro text chybového hlášení.
V argumentu nSize zadáme počáteční velikost objektu StringBuider a nSize musí být tak velké jak velikost StringBuider. Jinak bude API zapisovat do nedefinovatelné paměti a hrozí pád programu.
Poslední parametr není pro nás zajímavý a nahradíme ho null. Výběr hlášení chyby API, které je uloženo v systémové tabulce:
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); } }
Vyhodnocení kódu chyby API, které je uloženo v systémové tabulce hlášení (případ souborů Dll, které k vlastnímu systému nepatří):
[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ávěr
V tomto tutoriálu jsem ve stručnosti ukázal využití Windows API v C#. NET. Ukázka třídy je přiložena v souboru "API windows.cs".
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 93x (5.64 kB)
Aplikace je včetně zdrojových kódů v jazyce C#