paź/090
[C#] Własny ciąg wynikowy z daty i czasu
Tabela ze skrótami umożliwiającymi formatowania daty i czasu z wykorzystaniem…
DateTime.Now.ToString("ddMMyyyyHHmmss") ;
// Wynik: 01102009094315
… znajduje się tutaj: http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
sie/090
[C#] Niskopoziomowe przechwytywanie klawiszy klawiatury
Aby przechwycić naciśnięcie klawiszy na niskim poziomie (np. przed wszystkimi innymi aplikacjami) należy skorzystać z InteropSys aby „wpiąć” się do API systemu operacyjnego. Poniższy przykład z bloga Stephena Touba pokazuje jak to szybko i prosto zrobić:
using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class InterceptKeys
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static void Main()
{
_hookID = SetHook(_proc);
Application.Run();
UnhookWindowsHookEx(_hookID);
}
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
Console.WriteLine((Keys)vkCode);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
„Problem” zaczyna się w przypadku gdy chcemy obsłużyć naciśnięcie kombinacji klawiszy, a w szczególnym przypadku ALT i jakiegoś innego klawisza. ALT nie jest „przechwytywany” przez hook, przez co funkcję private static IntPtr HookCallback należy nieco przerobić, korzystając ze wcześniej zdefiniowanej struktury KeyboardHookStruct w której to możemy sprawdzić również flagi naciśniętych przycisków (w tym przypadku ALT+ESC):
public struct KeyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
};
// ...
KeyboardHookStruct hookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
if (hookStruct.vkCode == ESCAPE && (hookStruct.flags & LLKHF_ALTDOWN) == hookStruct.flags)
MessageBox.Show("I pressed ALT + ESC!");
Kilka uwag:
- Należy zauważć że w tym przypadku najpierw sprawdzamy drugi klawisz z kombinacji, a potem fakt czy jest wciśnięty ALT.
- Druga istotna rzecz to fakt zastosowania porównania wartości klawiszy do znanych z WinAPI nazw klawiszy wirtualnych.
- Listę kodów klawiszy wirtualnych można znaleźć na tej stronie Microsoft MSDN
- Łatwo napisać aplikację która przechwytuje klawisze, ale nie przekazuje ich dalej do kolejnych aplikacji zarejestrowanych w systemie w ramach łańcucha uchwytów. Wystarczy usunąć wywołania do funkcji: CallNextHookEx
- Jeżeli wciśniemy klawisz i przytrzymamy, informacje o jego naciśnięciu (KEYDOWN) będą w kółko (w odpowiednim odstępie czasowym) przesyłane. Należy to odpowiednio obsłużyć.
- Przykład z klawiszem ALT będzie wykonywał się dwa razy, ze względu na fakt że w nacisnięcie klawisza to w rzeczywistości dwie akcje. Naciśnięcie (KEY_DOWN) oraz podniesienie (KEY_UP) klawisza. Można to naprawić poprzez rozpoznanie jaka aktualnie akcja (DOWN/UP) jest wykonywana (poprzez porównanie z wParam) lub poprzez dodanie prostego semaforu
lip/090
[.NET] Klikanie myszą w danej lokalizacji
Klikanie myszą w danej lokalizacji (W punkcie p) zrealizowane w C#. Niby proste, jednak nie oczywiste. Poniższy kod umożliwia wykonanie kliknięcia lewym przyciskiem myszy. Należy zauważyć że składa się on z dwóch eventów które można przetłumaczyć na „LeftMouseButtonDown” oraz „LeftMouseButtonUp”:
using System.Runtime.InteropServices;
//...
[DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
public void DoMouseClick(Point p)
{
mouse_event(2, p.X, p.Y, 0, 0);
mouse_event(4, p.X, p.Y, 0, 0);
}
Więcej tutaj: http://msdn.microsoft.com/en-us/library/ms646260(VS.85).aspx
lip/090
[.NET] Zwalnianie nadmiarowej pamięci zaalokowanej przez CLR
Poniżej przedstawiony kawałek kodu osobiście uważam za „Świętego Grala” .NET, chociaż pochodzi prosto z WinApi. Umożliwia wymuszenie na aplikacji napisanej w .NET na zwolnienie za alokowanej pamięci w której aktualnie nie przechowuje obiektów.
.NET wykonując swoje programy zawsze alokuje więcej pamięci niż potrzebuje, gdyż polityka kompilatora zakłada że proces alokowania pamięci jest czasochłonny. Faktycznie tak jest, jednak w zależności od sytuacji ta czasochłonność może nie być zauważona. Wielu użytkowników panicznie interpretuje zapisy w Menedżerze zadań (taskmgr.exe) w którym to zwykła aplikacja posiadająca pojedynczą formatkę i przycisk zajmuje 11MB pamięci. Wskazanie to jest nieprawidłowe, jednak w tej chwili nie będę wdawał się w szczegóły, poza krótkim stwierdzeniem, że obiekty aplikacji zajmują mniej pamięci, a znaczna większość z tych 11MB to za alokowana przestrzeń pamięci dla aplikacji, która pozostaje pusta (a można ją zwrócić do systemu).
Do zalet stosowania poniższej funkcji dotychczas mogę zaliczyć dwie rzeczy:
- Możliwość pisania aplikacji rezydentnych w .NET (aplikacja co jakiś wywołuje zwolnienie pamięci, przez co jest w stanie zminimalizowanym może zajmować nawet 500KB)
- Możliwość uniknięcia wyjątku „OutOfMemoryException”, który jest generowany na podstawie wiadomości wysyłanej przez system operacyjny do wszystkich aplikacji, gdy ilość pamięci spadnie do 32MB (Sprawdzone w kodzie produkcyjnym).
using System.Runtime.InteropServices;
//(....)
[DllImport("kernel32.dll")]
public static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
public void CutRamUsage()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
Więcej informacji pod adresem:
http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx
cze/090
[.NET] Ukrywanie okna konsoli
Aby schować okno konsoli, w ramach Console Window Application należy skorzystać z interops i biblioteki systemu windows:
using System.Runtime.InteropServices
//...[DllIMport("user32.dll")]
private static extern IntPtr FindWindow(IntPtr hWnd, int nCmdShow);
[DllIMport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
//...
if(hWnd != IntPtr.Zero)
{
ShowWindow(hWnd, 0) // Ukryj okno
//ShowWindow(hWnd, 1) // Pokaż okno
}
mar/090
[MySQL] Importowanie danych z CSV w UTF8
W poprzedniej notce opisywałem importowanie danych do MySQL z wykorzystaniem plików CSV i zapytania IMPORT DATA INFILE. Okazuje się że „zabawny problem z kodowaniem” który kiedyś mi się zdażył w ramach zapytania wykorzystującego CONCAT, jest dość „globalnym” problemem MySQL.
Jeżeli posiadamy dane w kodowaniu UTF8, tabele w UTF8 i kolumny w UTF8, to próba importowania danych w sposób opisany wcześniej … nie powiedzie się. Okazuje się że MySQL (od 2005 roku! od 4 lat!) posiada bug, który importuje dane w kodowaniu bazy danych w zależności od systemu na jakim się znajduje.
Aby importować dane zawierające polskie znaki należy rozszerzyć zapytanie o „character set”, ale nie o utf8 (co byłoby logiczne!) ale o… latin2. (W przypadku innych kodowań np. hiszpańskich, należy próbować z latin1);
Rozszerzamy nasze zapytanie o
character set latin2
przez co nasze zapytanie wyglada tak:
LOAD DATA INFILE 'C:/data.csv' INTO TABLE `tbl1` CHARACTER SET latin2 FIELDS TERMINATED BY ';' ENCLOSED BY '"' ESCAPED BY '\\' LINES TERMINATED BY '\n' (col1, col2);
mar/090
[MySQL] Importowanie danych z CSV
Dawno temu napisałem skrypt który wczytywał plik CSV do bazy MySQL. Można to zrobić bezpośrednio z poziomu bazy danych.
Przypadek testowy:
1) Tabela o nazwie tbl1
2) Tabela posiada 2 kolumny o nazwie col1, col2 (int i varchar)
3) plik z danymi: data.csv
4) lokalizacja pliku z danymi: c:\
5) Część danych zawarta jest pomiędzy cudzysłowami („)
6) Znacznik kolejnej linii jest typu UNIXowego**
Przykład pliku z danymi wygląda następująco:
1;"some data 1"2;"some data 2"3;"some data x"
W imporcie powyższego pliku pomoże nam zapytanie:
LOAD DATA INFILE 'C:/data.csv' INTO TABLE `tbl1` FIELDS TERMINATED BY ';' ENCLOSED BY '"' ESCAPED BY '\\' LINES TERMINATED BY '\n' (col1, col2);
Wywołanie powyższego zapytania wypełni kolumny danymi. MySQL dba o to aby dane zostały odpowiednio zinterpretowane (text jako text, liczby jako liczby, itd);
** -Należy zauważyć fakt że każdy system operacyjny różnie interpretuje koniec linii. W rzeczywistości każdy plik ma tylko jedną linię. Jednak aby użytkownikowi zasymulować przejście do nowej linii wstawiane są „znaki nowej linii”. System Windows, w przeciwieństwie np. do MacOS czy Unixów, stosuje podwójny znak końca linii jakim jest \r\n. Unix natomiast stosuje tylko \n a w przypadku MacOS jest to \r. Aby płynnie zmieniać rodzaj znaku kończącego linię można wykorzystać edytor tekstowy PsPad lub zmienić zapis w LINES TERMINATED na ‘\n\r’
mar/090
[C#] Aplikacja transparentna (click-thru)
Ostatnio zacząłem się zastanawiać jak napisać aplikację przez którą można kliknać na inne aplikacje, ikony pulpitu itp itd. Są to aplikacje tzw. „click-thru”. Mogą zostać wykorzystane jako dodatek do elementów pulpitu, które jednak nie będą przeszkadzać bardzo użytkownikowi. Aby zrealizować taką aplikację należy odwołać się do WindowsAPI:
using System.Runtime.InteropServices;
//(...)[DllImport("user32.dll", SetLastError = true)]
private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
public const int GWL_EXSTYLE = -20;
public const int WS_EX_LAYERED = 0x80000;
public const int WS_EX_TRANSPARENT = 0x20;
public const int LWA_ALPHA = 0x2;
public const int LWA_COLORKEY = 0x1;
/* * Aby dane okno było Click-Thru najlepiej kod wywołujący taką funkcjonalność
* umieścić w konstruktorze obiektu okna. Tuż po "InitializeComponents()";
*/
public Form1()
{
InitializeComponent();
SetWindowLong(this.Handle, GWL_EXSTYLE, (IntPtr)(GetWindowLong(this.Handle, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT)); this.TopMost = true;
}
Więcej informacji na forum MSDNu
mar/090
MySQL CONCAT Utrata kodowania przy konkatenacji z liczbą
Ostatnio dłubiąc przy projekcie który wykorzystuje bazę danych MySQL natrafiłem na „ciekawy” błąd. Błąd na tyle ciekawy że jest znany od 2005 roku. W 2005 roku MySQL w wersji 3.3 posiadał ten bug. Ówcześni twórcy stwierdzili „poprawimy to w wersji ..5.1″. Nadeszła wersja 5.1, a poprawki jak nie ma tak nie ma. Ale o co chodzi?
Załóżmy że mamy tabelę z dwoma kolumnami. Pierwsza kolumna jest typu INTEGER o nazwie myNumber, a druga typu VARCHAR o nazwie myText. Kolumna VARCHAR jest ustawiona na porównywanie ciągów z kodowanie UTF-8 (UTF-8_general_ci). Jeżeli teraz wykonamy zapytanie:
SELECT CONCAT(myNumber, myText) as myResult FROM myTable;
otrzymamy w efekcie zcalony ciąg który … utracił kodowanie UTF8. Pomimo tego że dane zostały wprowadzone i są w UTF-8, operacja CONCAT kolumny CHAR (lub VCHAR) i INTEGER (lub innej numerycznej) powoduje utracenie kodowania.
Rozwiązaniem tego problemu jest castowanie zmiennej liczbowej
SELECT CONCAT(CAST(myNumber as CHAR), myText) as myResult FROM myTable;
To umożliwi nam otrzymanie prawidłowego ciągu.
sty/090
services.msc, compmgmt.msc … – Odmowa dostępu (Access denied)
Ostatnio natrafiłem na problem. Mianowicie przy próbie uruchomienia jakiejkolwiek przystawki administracyjnej (o rozszerzeniu msc), np. gpedit.msc, compmgmt.msc, services.msc etc. otrzymywałem komunikat o błędzie: services.msc. Odmowa dostępu pomimo tego że posiadałem uprawnienia administratora. Rozwiązanie było proste.
Wystarczyło przypisać pliki *.msc do programu obsługującego je, czyli mmc.exe (C:\windows\system32\mmc.exe). Przypisanie następuje w prosty sposób. Klikamy prawym przyciskiem myszy na np. services.msc i wybieramy „Otwórzy przy pomocy…”, po czym klikamy „Przeglądaj” i wybieramy mmc.exe
Jeżeli to rozwiązanie nie działa, należy jeszcze sprawdzić Uprawnienia dla dysku C.