WinAPI i platforma .NET
Jacek Matulewski
21 listopada 2016 Programowanie Windows
http://www.fizyka.umk.pl/~jacek/dydaktyka/winprog/
Mechanizm Platform Invoke w platformie .NET
Programowanie Windows
Trivia
• Platformy .NET, WinRT (W8), UWP (W10) tworzą osobną warstwę z własną „wirtualną maszyną”
• Platforma .NET ułatwia odwołania do
niezarządzanych DLL via mechanizm PInvoke
• Ważne źródło:
http://www.pinvoke.net/index.aspx
(deklaracja, przykłady użycia, zamienniki w .NET)
• Tutorial w MSDN: https://msdn.microsoft.com/en-
us/library/aa288468(v=vs.71).aspx
Trivia
• Czynności wykonywane przy Platform Invoke:
– lokalizacja biblioteki eksportującej funkcję
– załadowanie jej do pamięci (przy pierwszym uruchomieniu) – pobranie adresu funkcji
– wrzucanie argumentów na stos i wywołanie funkcji (przekazanie kontroli wątku do kodu niezarządzanego) – pobranie i konwersja typu zwracanej wartości
– powrót kontroli do kodu zarządzanego
Trivia
https://msdn.microsoft.com/en-us/library/aa719485(v=vs.71).aspx
Atrybut DllImport
• Atrybut DllImportAttribute pozwala na zadeklarowanie metody statycznej, która reprezentuje funkcję z biblioteki DLL
• Konstruktor przyjmuje nazwę niezarządzanej biblioteki DLL:
public DllImportAttribute(string dllName)
• Wymaga zadeklarowania przestrzeni nazw:
using System.Runtime.InteropServices;
• Składnia (w najprostszej formie):
[DllImport("Biblioteka.dll")]
static extern Funkcja(argumenty);
• Problem konwersji typów
Atrybut DllImport
• Oryginalna sygnatura funkcji WinAPI:
BOOL WINAPI MessageBeep(
_In_ UINT uType //sound type );
• Import (może być w klasie statycznej, ale niekoniecznie):
[DllImport("User32.dll")]
static extern bool MessageBeep(uint uType);
Odpowiednik w platformie .NET:
System.Media.SystemSounds.Beep.Play
Atrybut DllImport
• Przykład użycia:
private void button1_Click(
object sender, EventArgs e)
{
MessageBeep(0);
MessageBeep(48);
}
Atrybut DllImport
• Oryginalna sygnatura funkcji WinAPI:
int WINAPI MessageBox(
_In_opt_ HWND hWnd, _In_opt_ LPCTSTR lpText,
_In_opt_ LPCTSTR lpCaption, _In_ UINT uType
);
• Przykładowe wartości uType:
MB_OK (0), MB_YESNO (4), MB_ABORTRETRYIGNORE (2), MB_HELP (16384), MB_CANCELTRYCONTINUE (6),
MB_ICONERROR (16), MB_ICONQUESTION (32), ...
Atrybut DllImport
• Import:
[DllImport("user32.dll",
EntryPoint = "MessageBox", CharSet = CharSet.Unicode)]
static extern int _MessageBox(IntPtr hWnd, String text, String caption, uint type);
• Zmiana nazwy funkcji – własność EntryPoint
• Zestaw znaków w argumentach-łańcuchach – CharSet (w WinAPI – tablica znaków, wersja A i W)
Odpowiednik w platformie .NET:
System.Windows.Forms.MessageBox.Show
Atrybut DllImport
• Przykład użycia:
private void button1_Click(object sender, EventArgs e)
{
_MessageBox(this.Handle,
"Polskie litery: ąćęłńóśżź", "Tytuł",
64);
}
Odpowiednik w platformie .NET:
MessageBox.Show(
"Polskie litery: ąćęłńóśżź", "Tytuł",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
Użycie typów wyliczeniowych
• Oryginalna sygnatura funkcji WinAPI:
UINT WINAPI WinExec(
_In_ LPCSTR lpCmdLine, _In_ UINT uCmdShow );
• Wybrane wartości uCmdShow :
SW_HIDE (0), SW_MAXIMIZE (3), SW_MINIMIZE (6), SW_RESTORE (9), SW_SHOW (5), SW_DEFAULT (10)
• Te same wartości w ShowWindow , CreateProcess itd.
Typy wyliczeniowe
• Import:
[DllImport("kernel32.dll")]
static extern uint WinExec(string polecenie, uint stanOkna);
• To nie jest zgodne z duchem platformy .NET – typ wyliczeniowy
• Te sam problem w MessageBeep i MessageBox
Odpowiednik w platformie .NET:
System.Diagnostics.Process.Start
Typy wyliczeniowe
• Typ wyliczeniowy z określonym typem uint - konwersja:
public enum StanOkna : uint { Ukryte = 0,
Normalme, Zminimalizowane, Zmaksymalizowane, Nieaktywne, Domyślne = 10 };
• Import:
[DllImport("kernel32.dll")]
static extern uint WinExec(string polecenie, StanOkna stanOkna);
Atrybut DllImport
• Przykład użycia:
private void button1_Click(object sender, EventArgs e)
{
uint wynik = WinExec(textBox1.Text,
StanOkna.Normalme);
if (wynik <= 31)
MessageBox.Show("Błąd " + wynik + "! Nie udało się uruchomić " + textBox1.Text);
}
Atrybut DllImport
• Przykład użycia z listą wyboru ( ComboBox ):
private void button1_Click(object sender, EventArgs e)
{
uint wynik = WinExec(textBox1.Text,
(StanOkna)cbStanOkna.SelectedIndex);
if (wynik <= 31)
MessageBox.Show("Błąd " + wynik + "! Nie udało się uruchomić " + textBox1.Text);
}
Demo
• Funkcje MessageBeep , MessageBox , WinExec
• Import, wskazanie nazwy funkcji, typ wyliczeniowy
Zwracanie wartości przez referencje
• Oryginalna sygnatura funkcji WinAPI:
BOOL GetDiskFreeSpaceEx(
LPCTSTR lpDirectoryName,
PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes,
PULARGE_INTEGER lpTotalNumberOfFreeBytes );
• Typy użyte w sygnaturze (zob. MSDN Windows Data Types):
LPCTSTR = LPCWSTR (Unicode) lub LPCSTR (ASCII)
typedef __nullterminated CONST CHAR *LPCSTR;
PULARGE_INTEGER = wskaźnik do LARGE_INTEGER
Zwracanie wartości przez referencje
• Liczba całkowita 64-bitowa:
typedef union _LARGE_INTEGER {
struct { DWORD LowPart; LONG HighPart; };
struct { DWORD LowPart; LONG HighPart; } u;
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;
• Liczba całkowita 64-bitowa:
W przypadku, gdy kompilator obsługuje liczby 64-bitowe,
należy używać typu LONGLONG . Jeżeli nie – dwóch DWORD .
Zwracanie wartości przez referencje
• Import:
[DllImport("kernel32.dll")]
static extern bool GetDiskFreeSpaceEx(
string katalog,
ref long wolneMiejsceDlaUzytkownika, ref long rozmiarDysku,
ref long wolneMiejsceNaDysku);
Odpowiednik w platformie .NET:
System.IO.DriveInfo
• Przykład użycia:
private int wolneMiejsceNaDysku(
string katalogGlownyDysku) {
int wolneMiejsceNaDyskuProcenty;
long wolneMiejsceDlaUzytkownika = 0;
long rozmiarDysku = 0;
long wolneMiejsceNaDysku = 0;
if (GetDiskFreeSpaceEx(
katalogGlownyDysku,
ref wolneMiejsceDlaUzytkownika, ref rozmiarDysku,
ref wolneMiejsceNaDysku)) { ...
Zwracanie wartości przez referencje
• Przykład użycia:
private int wolneMiejsceNaDysku(
string katalogGlownyDysku) {
...
if (GetDiskFreeSpaceEx( ... ) { wolneMiejsceNaDyskuProcenty = (int)(100 * (rozmiarDysku – wolneMiejsceNaDysku) /
(double)rozmiarDysku);
} else wolneMiejsceNaDyskuProcenty = -1;
return wolneMiejsceNaDyskuProcenty;
}
Zwracanie wartości przez referencje
• Przykład użycia:
public Form1() {
InitializeComponent();
string katalogGłównyDysku =
System.Environment.GetLogicalDrives()[0];
int procentZajetosci =
wolneMiejsceNaDysku(katalogGlownyDysku);
if (procentZajetosci >= 0)
progressBar1.Value = procentZajetosci;
}
Zwracanie wartości przez referencje
Demo
• Funkcja GetDiskFreeSpaceEx
• Zwracanie wartości przez referencje
Tablica znaków – łańuchy
• Oryginalna sygnatura funkcji WinAPI:
UINT WINAPI GetWindowsDirectory(
_Out_ LPTSTR lpBuffer, //
_In_ UINT uSize );
• ASCII: typedef CHAR *LPSTR;
UNICODE: typedef WCHAR *LPWSTR;
• Import:
[DllImport("kernel32.dll")]
static extern uint GetWindowsDirectory(
StringBuilder bufor, uint rozmiarBufora);
Odpowiednik w platformie .NET:
System.Environment.SystemDirectory
• Przykład użycia ( StringBuilder wymaga trochę wysiłku):
private void button4_Click(object sender, EventArgs e)
{
const int MAX_PATH = 260;
StringBuilder katalogWindows = new StringBuilder(MAX_PATH);
uint rozmiar = (uint)katalogWindows.Capacity;
GetWindowsDirectory(katalogWindows, rozmiar);
MessageBox.Show(katalogWindows.ToString());
}
• Tak samo funkcje:
GetWindowsDirectory i GetSystemDirectory
Tablica znaków – łańuchy
Color Picker
• Cel: chcemy pobierać kolor piksela na ekranie znajdujący się pod kursorem myszy
• Wyłączamy skalowanie aplikacji – skalowanie pozycji myszy:
BOOL WINAPI SetProcessDPIAware(void);
• Pobieramy pozycję myszy:
BOOL WINAPI GetPhysicalCursorPos(
_Out_ LPPOINT lpPoint );
• Pobieramy obraz (bitmapę) ze wskazanego prostokąta (np. 1×1):
BOOL BitBlt(_In_ HDC hdcDest,
_In_ int nXDest, _In_ int nYDest, _In_ int nWidth, _In_ int nHeight,
_In_ HDC hdcSrc, _In_ int nXSrc, _In_ int nYSrc, _In_ DWORD dwRop);
Odpowiednik w platformie .NET:
System.Windows.Forms.Cursor.Position
Odpowiednik w platformie .NET:
System.Drawing.Graphics.CopyFromScreen
Color Picker
• Import:
[DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
[DllImport("user32.dll")]
public static extern bool GetCursorPos(ref Point lpPoint);
[DllImport("user32.dll")]
public static extern bool GetPhysicalCursorPos(ref Point lpPoint);
[DllImport("gdi32.dll", CharSet = CharSet.Auto,
SetLastError = true, ExactSpelling = true)]
public static extern int BitBlt(IntPtr hDC, int x, int y,
int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
• Przykład użycia (z projektu Huberta Wojtowicza):
private static Bitmap pikselEkranu = new Bitmap(
1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
public static Color PobierzKolorPiksela(Point położenie) {
//grafika źródła (ekran)
using (Graphics gd = Graphics.FromImage(pikselEkranu)) {
//grafika bitmapy, w której zostanie zapisany kolor using (Graphics gs = Graphics.FromHwnd(IntPtr.Zero))
{ ...
} }
return screenPixel.GetPixel(0, 0); //zwracany jedyny piksel }
Color Picker
• Przykład użycia (z projektu Huberta Wojtowicza):
private static Bitmap pikselEkranu = new Bitmap(
1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
public static Color PobierzKolorPiksela(Point położenie) {
...
{
IntPtr hsDC = gs.GetHdc(); //uchwyt do DC źródła IntPtr hdDC = gd.GetHdc(); //uchwyt do DC celu BitBlt(
hdDC, 0, 0, 1, 1, //cel (uchwyt i geometria)
hsDC, położenie.X, położenie.Y, //źródło (uchwyt i poł.) (int)CopyPixelOperation.SourceCopy); //tryb wklejania gd.ReleaseHdc(); gs.ReleaseHdc(); //zwalnianie uchwytów }
...
Color Picker
• Przykład użycia (wymaga kontrolek Timer , Label , Panel ):
private void timer1_Tick(object sender, EventArgs e) {
Point pozycjaKursora = Cursor.Position;
GetPhysicalCursorPos(ref pozycjaKursora);
label3.Text = pozycjaKursora.ToString();
panel1.BackColor = PobierzKolorPiksela(pozycjaKursora);
}
Color Picker
• Przykład użycia (zmodyfikowana funkcja pobierająca piksel):
public static Bitmap PobierzZrzutEkranu() {
RECT obszar = new RECT();
GetWindowRect(GetDesktopWindow(), out obszar);
Bitmap bitmap = new Bitmap(obszar.Right - obszar.Left, obszar.Bottom - obszar.Top);
using (Graphics gd = Graphics.FromImage(bitmap)) {
using (Graphics gs = Graphics.FromHwnd(IntPtr.Zero)) {
...
} }
return bitmap;
}
Zrzut całego ekranu
• Przykład użycia (zmodyfikowana funkcja pobierająca piksel):
public static Bitmap PobierzZrzutEkranu() {
...
{
IntPtr hsDC = gs.GetHdc();
IntPtr hdDC = gd.GetHdc();
BitBlt(
hdDC, 0, 0, obszar.Right - obszar.Left, obszar.Bottom - obszar.Top, hsDC, 0, 0,
(int)CopyPixelOperation.SourceCopy);
gd.ReleaseHdc();
gs.ReleaseHdc();
} ...
}
Zrzut całego ekranu
• Deklarowanie struktur danych na potrzeby PInvoke:
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Zrzut całego ekranu
• Zamiast funkcji BitBlt można użyć PrintWindow
(służy do kopiowania zawartości okna do wskazanego DC)
• Nie pozwala na operacje bitowe przy kopiowaniu
• Oryginalna sygnatura funkcji WinAPI:
BOOL PrintWindow(HWND hwnd, HDC hdcBlt, UINT nFlags);
• Import:
[DllImport("user32.dll")]
static extern bool PrintWindow(
IntPtr hWnd, IntPtr hDC, int flags);
Zrzut ekranu w obrębie okna
• Przykład użycia:
public static Bitmap PobierzZrzutOkna(IntPtr uchwytOkna,
bool tylkoObszarKlienta = false) {
RECT obszar = new RECT();
GetWindowRect(uchwytOkna, out obszar);
Bitmap bitmap = new Bitmap(obszar.Right - obszar.Left, obszar.Bottom - obszar.Top);
using (Graphics gd = Graphics.FromImage(bitmap)) {
PrintWindow(uchwytOkna, gd.GetHdc(), tylkoObszarKlienta?1:0);
}
return bitmap;
}
Zrzut ekranu w obrębie okna
Demo
• Funkcja BitBlt , PrintWindow
• Pobieranie zrzutów ekanu, color picker
Demo
• Funkcja EnumWindows, EnumChildWindows
• Zadanie domowe: na zrzucie ekranu zaznaczyć położenia wszystkich okien (w tym kontrolek) z tytułem i uchwytem
Atrybut DllImport
• Niektóre pola i własności atrybutu DllImportAttribute :
– CallingConvention (Cdecl, StdCall, ThisCall, Winapi) – CharSet (zestaw znaków: Ansi, Unicode)
– EntryPoint (nazwa importowanej funkcji)
– SetLastError (wywoływana jest metoda SetLastError, jej wynik można pobrać funkcją GetLastError)
• MarshalAsAttribute – określa niezarządzany typ argumentu lub wartości zwracanej przez funkcję;
zwykle zbędny; jedyny wyjątek to niejednoznaczność łańcuchów
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool MessageBeep(uint uType);
• Atrybuty In i Out określają kierunek konwersji argumentów
Atrybut MarshalAs
• Przykład dla argumentów (zał. Unicode):
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.I4)]
static extern Int32 MessageBoxEx(
IntPtr hWnd, //unicode
[param: MarshalAs(UnmanagedType.LPTStr)] String lpText, [param: MarshalAs(UnmanagedType.LPTStr)] String lpCaption, //32-bitowa (4-bajtowa) liczba całkowita bez znaku
[param: MarshalAs(UnmanagedType.U4)] UInt32 uType, //16-bitowa (2-bajtowa) liczba całkowita bez znaku
[param: MarshalAs(UnmanagedType.U2)] UInt16 wLanguageId);
• https://www.codeproject.com/articles/66245/marshaling-with-
csharp-chapter-1-introducing-marsh.aspx
Komunikaty Windows
Programowanie Windows
• Funkcja FindWindow (już poznaliśmy)
zwraca uchwyt okna o podanej klasie lub tytule
HWND WINAPI FindWindow(
_In_opt_ LPCTSTR lpClassName, _In_opt_ LPCTSTR lpWindowName );
• Import:
[DllImport("user32.dll")]
static extern HWND FindWindow(string nazwaKlasy, string nazwaOkna);
Identyfikacja adresata komunikatu
• Funkcje SendMessage i PostMessage (także już znane) wysyłają komunikaty do okna o podanym uchwycie
LRESULT WINAPI SendMessage(
_In_ HWND hWnd, _In_ UINT Msg,
_In_ WPARAM wParam, _In_ LPARAM lParam);
• Import:
[DllImport("user32.dll")]
static extern int SendMessage(
HWND hwnd, uint Msg,
int wParam, int lParam);
Wysyłanie komunikatu
• Stałe:
public const int WM_CLOSE = 0x0010;
public const int WM_SYSCOMMAND = 0x0112;
public const int WM_NCMOUSEMOVE = 0x00A0;
public const int WM_PAINT = 0x000F;
public const int WM_KEYDOWN = 0x0100;
public const int WM_KEYUP = 0x0101;
public const int WM_CHAR = 0x0102;
• Lista komunikatów:
http://www.pinvoke.net/default.aspx/Enums/WindowsMessages.html
Wysyłanie komunikatu
• Przykład użycia:
private void button1_Click(object sender, EventArgs e)
{
string nazwaOkna = textBox1.Text;
HWND uchwyt = FindWindow(null, nazwaOkna);
if (uchwyt != HWND.Zero)
SendMessage(uchwyt, WM_CLOSE, 0, 0);
else
MessageBox.Show("Nie ma okna o tytule \"" + nazwaOkna + "\"");
}
Wysyłanie komunikatu
• Nadpisywanie procedury okna
( ListBox , MultiColumn = true, ColumnWidth = 30):
protected override void WndProc(ref Message m) {
if (m.Msg!=308) listBox1.Items.Add(m.Msg);
//308 = WM_CTLCOLORLISTBOX base.WndProc(ref m);
}
Odbieranie komunikatu
• Nadpisywanie procedury okna
( ListBox , MultiColumn = true, ColumnWidth = 30):
switch(m.Msg) {
case WM_NCMOUSEMOVE:
long lParam=(long)m.LParam;
long x=lParam & 0x0000FFFF;
long y=(lParam & 0xFFFF0000) >> 16;
label1.Text = "(Komunikat) Mysz poza obszarem klienta: " + x + "," + y;
break;
}