Použití Windows API v C# .NET - 4. díl
Doposud jsme řešili implementaci API do programu spíše intuicí a v řadě případů zavádíme do programu chyby, které se velmi těžko hledají. Pokud nahlédneme do implementace API do .NET Frameworku, zjistíme, že zhruba 20% API je řešeno pomocí ukazatelů (pointerů) a tak zvaným nebezpečným kódem (unsafe). Důvod je prostý, lepší optimalizace a rychlost programu. V dalších tutoriálech se naučíme jak programovat tento unsafe kód. Pro lepší efektivitu naší práce doinstalujeme do Visual Studia balíček PInvoke.net, který nám velice usnadní práci.
PInvoke.net
Instalace balíčku přes správce rozšíření je velmi jednoduchá a myslím si, že není nutný další popis.

Pokud zadáme například API ReadFile(), jsou nám nabídnuty čtyři varianty v C# a dvě varianty pro VB.NET:

Varianta dvě a čtyři používá nové klíčové slovo unsafe a to znamená, že funkce bude používat nebezpečný kód a bude využívat ukazatele. Také příklad s uvedenou API používá nebezpečný kód.
public class Class1 { //1 [DllImport("kernel32.dll", SetLastError = true)] static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); //2 [DllImport("kernel32.dll", SetLastError = true)] static extern unsafe int ReadFile(IntPtr handle, IntPtr bytes, uint numBytesToRead, IntPtr numBytesRead, NativeOverlapped* overlapped); //3 [DllImport("kernel32.dll", SetLastError=true)] static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [In] ref System.Threading.NativeOverlapped lpOverlapped); //4 [DllImport(@"kernel32.dll", SetLastError = true)] static extern unsafe bool ReadFile( SafeFileHandle hFile, // handle to file byte* pBuffer, // data buffer, should be fixed int NumberOfBytesToRead, // number of bytes to read IntPtr pNumberOfBytesRead, // number of bytes read, provide IntPtr.Zero here NativeOverlapped *lpOverlapped // should be fixed, if not IntPtr.Zero ); // This is taken from the USBSharp.cs class public unsafe byte[] CT_ReadFile(int InputReportByteLength) { int BytesRead = 0; byte[] BufBytes = new byte[InputReportByteLength]; if (ReadFile(HidHandle, BufBytes, InputReportByteLength, ref BytesRead, null)) { byte[] OutBytes = new byte[BytesRead]; Array.Copy(BufBytes, OutBytes, BytesRead); return OutBytes; } else { return null; } } }
Velkou výhodou je automatické načtení kódu do Visual Studia.
Nahlédnutí pod pokličku .NET frameworku
Podívejme se pod pokličku .NET frameworku. Nejprve provedeme dekompilaci například System.Core:
// Type: System.IO.Pipes.AnonymousPipeServerStream // Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // MVID: BA37026F-1371-4849-854A-E2CCE7CAD02B // Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll using Microsoft.Win32.SafeHandles; using System; using System.IO; using System.Runtime; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; namespace System.IO.Pipes { [HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)] public sealed class AnonymousPipeServerStream : PipeStream { private SafePipeHandle m_clientHandle; private bool m_clientHandleExposed; //nějaký kód [SecurityCritical] private void Create(PipeDirection direction, Microsoft.Win32.UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs, int bufferSize) { SafePipeHandle hSourceHandle; if (!(direction != PipeDirection.In ? Microsoft.Win32.UnsafeNativeMethods.CreatePipe(out this.m_clientHandle, out hSourceHandle, secAttrs, bufferSize) : Microsoft.Win32.UnsafeNativeMethods.CreatePipe(out hSourceHandle, out this.m_clientHandle, secAttrs, bufferSize))) __Error.WinIOError(Marshal.GetLastWin32Error(), string.Empty); SafePipeHandle lpTargetHandle; if(!Microsoft.Win32.UnsafeNativeMethods.DuplicateHandle( Microsoft.Win32.UnsafeNativeMethods.GetCurrentProcess(), hSourceHandle, Microsoft.Win32.UnsafeNativeMethods.GetCurrentProcess(), out lpTargetHandle, 0U, false, 2U)) __Error.WinIOError(Marshal.GetLastWin32Error(), string.Empty); hSourceHandle.Dispose(); this.InitializeHandle(lpTargetHandle, false, false); this.State = PipeState.Connected; } } }
Zde vidíme neznámé using Microsoft.Win32.UnsafeNativeMethods. Pokud tuto třídu dekompilujeme:
// Type: Microsoft.Win32.UnsafeNativeMethods // Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // MVID: BA37026F-1371-4849-854A-E2CCE7CAD02B // Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll using Microsoft.Win32.SafeHandles; using System; using System.Diagnostics.Eventing; using System.Diagnostics.Eventing.Reader; using System.IO; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading; namespace Microsoft.Win32 { internal static readonly IntPtr NULL = IntPtr.Zero; internal const string KERNEL32 = "kernel32.dll"; internal const string ADVAPI32 = "advapi32.dll"; internal const string WEVTAPI = "wevtapi.dll"; internal const int CREDUI_MAX_USERNAME_LENGTH = 513; internal const int ERROR_SUCCESS = 0; internal const int ERROR_FILE_NOT_FOUND = 2; internal const int ERROR_PATH_NOT_FOUND = 3; internal const int ERROR_ACCESS_DENIED = 5; //pokračují další konstanty [SecurityCritical] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FreeLibrary(IntPtr hModule); [SecurityCritical] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool RevertToSelf(); [SecurityCritical] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ImpersonateNamedPipeClient(SafePipeHandle hNamedPipe); //Další funkce API použité v System.Core
Důležitý poznatek tedy je, že vývojáři .NET frameworku mají k dispozici neuvedené třídy, které obsahují Pinvoke všech API funkcí.
Importované funkce mají dodané atributy, pokusím se je ve stručnosti popsat.
[HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
- HostProtectionAttribute - Tato třída umožňuje používat deklarované zabezpečení akce a určit požadavky na ochranu hostitele.
- SecurityAction.LinkDemand (podporováno rozhraním XNA) - Nepoužívat v .NET Framework 4. Přidělí oprávnění volajícímu.
- MayLeakOnAbort = true - Kód ukončení může způsobit únik paměti, pokud nejsou chráněna bezpečným popisovačem nebo není jiným způsobem zajištěno uvolnění paměti.
[SecurityCritical]
- Je ekvivalentní pro odkaz na úplný vztah důvěryhodnosti.
- Typ nebo člena označeného SecurityCriticalAttribute lze volat pouze prostřednictvím plně důvěryhodného kódu. Nemůže být volán z částečně důvěryhodného kódu.
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
- Kód může volat držitel speciálního oprávnění.
SecuritySafeCritical
- Typy nebo členy označené SecuritySafeCriticalAttribute - Atribut pro přístup k částečně důvěryhodnému typu a členu.
Pro milovníky dobrého kódu je zajímavé uvolňování alokované paměti ve třídě System.IO.Pipes
Ukázka překladu:

Závěr
Na závěr jsem do zdrojových kódů přidal celou třídu class UnsafeNativeMethods a několik tříd, které ji volají. Pro milovníky kódu je to velmi poučná pasáž, ukázka řeší jak programují profesionálové. Tím ale nemyslím sebe. V dalších částech popíši nebezpečný kód založený na použití pointerů a uvedu několik příkladů. Příjemné počtení.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 72x (17.08 kB)
Aplikace je včetně zdrojových kódů v jazyce C#