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ž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.Se­quential. 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­.InteropServi­ces 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í GetLastWin32E­rror(...) 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_SYS­TEM (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_HMO­DULE (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 92x (5.64 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

Všechny články v sekci
Programování služeb ve Windows
Článek pro vás napsal zpavlu
Avatar
Uživatelské hodnocení:
Ještě nikdo nehodnotil, buď první!
C# , C++ a assembler
Aktivity