Služby operačného systému Windows
Volania služieb Windows sú implementováné z hľadiska programátora
ako volania funkcií zo "systémových" zdielaných knižníc (DLL,
Dynamic-link libraries).
Najbežnejšie z nich sú v knižnici Kernel32.dll.
Niektoré informácie sú špecifické pre 32-bitové Windows
aplikácie. Ukážky sú 32-bitové aplikácie.
Základným zdrojom informácií o službách Windows je stránka:
https://msdn.microsoft.com/library
(použiť hľadanie pomocou ikony lupy vpravo hore)
Keď programujeme v C, tak pridávame do programu:
#include "windows.h"
Súbor windows.h obsahuje deklarácie volaní Windows a aj
špecifických typov a makier, ktoré sú pritom potrebné.
Pri programovaní volaní Windows je typické, že aj typy, pre ktoré má
jazyk C svoje meno, majú svoj zvlášntny "Windowsovský"
identifikátor.
Niekoľko príkladov typov a makier:
- DWORD je 32-bitové celé číslo bez znamienka
- LPVOID je void * teda smerník na neurčený typ (LP
znaemná Long Pointer, slovo Long je historický pozostatok zo
16-bitovej éry keď existovali dlhé a krátke smerníky).
- WINAPI je makro pre spôsob odovzdávania parametrov __stdcall.
- HANDLE je technicky tiež void *, označujú sa ním
objekty operačného systému (napríklad súbor, proces, thread,
semafór, ...), kde pri ich vytvorení dostaneme hodnotu typu
HANDLE, ktorou potom pri každom použití objektu (napríklad
zápise do súboru, čakanie na ukončenie procesu, ...)
identifikujeme daný objekt.
- LPSTR je CHAR *, kde CHAR je char, teda osembitové
číslo so znamienkom, ktoré môže slúžiť mimo iného na uloženie
kódu znaku v ASCII alebo inom 8-bitovom kóde.
Mnohé ďalšie typy nájdete tu:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
Reťazce, ktoré posielame ako parametre do služieb Windows môžu byť
kódované buď v niektorom 8-bitovom kóde (ktorý je definovaný jazykom
Windows resp. nastavením character set for non-unicode
applications, napríklad to môže byť Windows-1251 alebo
Windows-1250 a pod., často sa im všetkým hovorí ANSI kódovanie, aj
keď skutočné ANSI je len jedno z nich), alebo v UTF-16.
Každá funkcia, ktorá má aspoň jeden parameter typu reťazec (alebo
štruktúru, ktorá obsahuje reťazec alebo smerník na reťazec), má
preto tri mená:
MenoFunkcie alebo MenoFunkcieW musí dostať parametre
v UTF-16 (typ LPWSTR resp LPTSTR pri zapnutom UNICODE).
MenoFunkcieA musí dostať parametre v 8-bitovom kódovaní (typ
LPSTR).
Viac o používaní stringov vo Windows nájdete tu:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd374131(v=vs.85).aspx
Ukážka 1 - Vytvorenie procesu
Základná služba je CreateProcess, viď https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
Má 10 parametrov, z ktorých ale väčšinu nebudeme vysvetlovať.
Odporúčaný spôsob vytvorenia nového projektu v Microsoft Visual
Studiu:
- Rozbehnite na počítači v učebni prostredie Microsoft Visual
Studio
- Vytvorte vo Visual Studiu nový projekt.
- File/New/Project,
- vyberte Win32 Console Application,
- dole prípadne zmeňte Name a Location (nepovinné), stlačte
OK,
- v ďalšom okne stačí stlačiť Finish, nič iné tam nemeňte.
- Ak bude Váš program niečo vypisovať a potrebujete to pred jeho
ukončením vidieť, tak do main dopíšte pred return
0 ešte aj
getchar();
Nasledujúci program vytvorí súbor d:\text.txt (nezabudnite
zmeniť ak nemáte disk d:) a napíše doňho "test" (pritom
používa bežné súborové operácie jazyka C s Microsoftovou špecialitou
fopen_s). Potom na daný súbor vyvolá program Notepad, počká
kým ten skončí a súbor (možno zmenený v notepade a uložený na disk)
prečíta a vypíše.
Používam ANSI volania (CreateProcessA a FormatMessageA).
#include "stdafx.h"
#include "windows.h"
int main()
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
HANDLE proc;
DWORD exitCode;
DWORD err;
FILE* f;
char buff[1025];
fopen_s(&f,"d:\\text.txt", "w");
fprintf(f, "test");
fclose(f);
ZeroMemory(&pi, sizeof pi); // inicializacia premennej pi
ZeroMemory(&si, sizeof si); // inicializacia premennej si
si.cb = sizeof si;
if (CreateProcessA(NULL, "notepad d:\\text.txt", NULL, NULL, false, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) // vytvorime novy proces s beznymi nastaveniami
{
proc = pi.hProcess; // v pi.hProcess sme dostali identifikacne cislo (handle) noveho procesu
if (WaitForSingleObject(proc, INFINITE) != WAIT_FAILED) // cakame na skoncenie procesu
{
GetExitCodeProcess(proc, &exitCode); // ak cakanie neskoncilo chybou, tak prevezmeme ukoncovaci kod
printf("ExitCode = %d\n", exitCode); // a vypiseme ho
}
CloseHandle(proc); // oznamime op. systemu, ze uz nepotrebujeme cislo procesu
CloseHandle(pi.hThread); // oznamime op. systemu, ze uz nepotrebujeme cislo jeho hlavneho threadu
fopen_s(&f, "d:\\text.txt", "r");
while (fgets(buff, sizeof buff, f) != NULL)
printf("%s",buff);
fclose(f);
}
else {
err = GetLastError();
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buff, sizeof buff, NULL) > 0)
printf("%s", buff);
else
printf("chyba %d\n", err);
}
getchar();
return 0;
}
Ukážka 2 - Vytvorenie threadov a ich jednoduchá
synchronizácia
Máme pole celých čísel front ktorý je už naplnený
číslami a budeme z neho čísla len vyberať. Celočíselná premenná prvy
obsahuje index prvku, ktorý je práve prvý vo fronte (teda ho
máme vybrať). Funkcia vyber implementuje výber z frontu.
V hlavnom programe naplníme pole front číslami 0 až 2*N. Potom
vyvoríme dva thready, ktorých telom je funkcia MojThread. Tá
vyberá z frontu prostredníctvom volania funkcie vyber() N čísiel do
svojho poľa, ktoré dostala ako parameter.
Funkcia testuj zisťuje, či je výsledok spolupráce threadov
"dobrý", teda, či thready vybrali práve 2*N rôznych čísel.
Hlavička funkcie MojThread obsahuje aj makro WINAPI, ktoré je makrom
pre __stdcall. To je spôsob prenosu parametrov (calling
convention) odlišný od bežného spôsobu v jazyku C (ten sa označuje
ako __cdecl). Všetky volania Windows používajú volanie typu
WINAPI. Pri funkciách, ktoré priamo vyvolávame, nás to netrápi lebo
hlavička funkcie je vo windows.h. Keď ale programujeme callback
funkciu, ktorú bude nejaká služba vyvolávať (naša funkcia
MojThread, ktorej adresu posielame ako parameter do CreateThread),
tak musíme mať presnú hlavičku podľa dokumentácie aj s WINAPI.
Ďalšie čitanie o calling convetions:
https://en.wikipedia.org/wiki/Calling_convention
https://en.wikipedia.org/wiki/X86_calling_conventions
Keď v programe nepoužijeme synchronizáciu threadov, tak funkcia
testuj väčšnou zistí chyby vo výsledných poliach (vypisuje
ich do súboru d:\test.txt).
Keď pridáme synchronizáciu (premenná cs typu
CRITICAL_SECTION), tak už bude vždy výsledok 'dobre'.
Ďalšie čistanie o synchronizácii vo Windows:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms686364(v=vs.85).aspx
#include "stdafx.h"
#include "windows.h"
#define N 100000
int front[2 * N];
int prvy;
CRITICAL_SECTION cs;
int vyber() {
EnterCriticalSection(&cs);
int res = front[prvy];
prvy++;
LeaveCriticalSection(&cs);
return res;
}
DWORD WINAPI MojThread(_In_ LPVOID lpParameter) {
int *pole = (int *)lpParameter;
for (int i = 0; i < N; i++) {
pole[i] = vyber();
}
return 0;
}
bool testuj(int p1[], int p2[], int prvy) {
int i1, i2, narade;
i1 = 0;
i2 = 0;
narade = 0;
FILE *f;
fopen_s(&f, "d:\\test.txt", "w");
bool zle = false;
if (prvy != 2 * N)
fprintf(f, "nespravne prvy: %d\n", prvy);
while (narade < 2 * N) {
if (i1 < N && p1[i1] == narade) {
i1++;
}
else if (i2 < N && p2[i2] == narade)
i2++;
else {
fprintf(f, "nie je: %d\n", narade);
zle = true;
}
while (i1 < N && p1[i1] == narade) {
i1++;
fprintf(f, "viacnasobne: %d\n", narade);
zle = true;
}
while (i2 < N && p2[i2] == narade) {
i2++;
fprintf(f, "viacnasobne: %d\n", narade);
zle = true;
}
narade++;
}
fclose(f);
return zle;
}
int p1[N];
int p2[N];
int main()
{
HANDLE hh[2];
LPSTR x;
CHAR v;
for (int i = 0; i < 2 * N; i++) {
front[i] = i;
}
prvy = 0;
InitializeCriticalSection(&cs);
HANDLE t1 = CreateThread(NULL, 0, &MojThread, &p1, CREATE_SUSPENDED, NULL);
HANDLE t2 = CreateThread(NULL, 0, &MojThread, &p2, CREATE_SUSPENDED, NULL);
ResumeThread(t1);
ResumeThread(t2);
hh[0] = t1;
hh[1] = t2;
WaitForMultipleObjects(2, hh, true, INFINITE);
DeleteCriticalSection(&cs);
if (testuj(p1, p2, prvy))
printf("zle\n");
else
printf("dobre\n");
getchar();
return 0;
}
Ukážka 3 - Súborové operácie a asynchrónny vstup
Nasledujúca ukážka je mierne zjednodušená ukážka zo stránky:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb540534(v=vs.85).aspx
Ukazuje vytvorenie súboru pomocou volaní operačného systému Windows
(teda nie pomocou volaní knižnice jazyka C), synchróony zápis do
súboru, zatvorenie súboru, jeho následné otvorenie na čítanie a asynchrónne
čítanie.
Asynchrónne vstupno/výstupné operácie sú také, že ich jedným volaním
inicializujeme, ale náš program bude pokračovať ďalej a keď operácia
skončí, tak sa vyvolá nami definovaná funkcia. Asynchrónne
vstupno/výstupné operácie používame vtedy keď chceme počas čakania
na skončenie operácie niečo iné robiť a nechceme či nemôžeme použiť
viac threadov.
V ukážke sú použité UTF-16 (Wide string) volania (CreateFile a
FormatMessage). LPTSTR je LPWSTR keď je zapnuté UNICODE (a to je
bežne zapnuté).
#include "stdafx.h"
#include "windows.h"
void DisplayError(LPTSTR lpszFunction);
int zapis()
{
HANDLE hFile;
char DataBuffer[] = "This is some test data to write to the file.";
DWORD dwBytesToWrite = (DWORD)strlen(DataBuffer);
DWORD dwBytesWritten = 0;
BOOL bErrorFlag = FALSE;
int err;
printf("\n");
hFile = CreateFile(TEXT("D:\\testfile2.txt"), // name of the write
GENERIC_WRITE, // open for writing
0, // do not share
NULL, // default security
CREATE_NEW, // create new file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (hFile == INVALID_HANDLE_VALUE)
{
DisplayError(TEXT("CreateFile"));
return 1;
}
bErrorFlag = WriteFile(
hFile, // open file handle
DataBuffer, // start of data to write
dwBytesToWrite, // number of bytes to write
&dwBytesWritten, // number of bytes that were written
NULL); // no overlapped structure
err = 0;
if (FALSE == bErrorFlag)
{
DisplayError(TEXT("WriteFile"));
err = 1;
}
else
{
if (dwBytesWritten != dwBytesToWrite) {
printf("Error: dwBytesWritten != dwBytesToWrite\n");
err = 1;
}
else
printf("Wrote %d bytes successfully.\n", dwBytesWritten);
}
CloseHandle(hFile);
return err;
}
#define BUFFERSIZE 50
DWORD g_BytesTransferred = 0;
VOID CALLBACK FileIOCompletionRoutine(
__in DWORD dwErrorCode,
__in DWORD dwNumberOfBytesTransfered,
__in LPOVERLAPPED lpOverlapped)
{
printf("Error code:%d\n", dwErrorCode);
printf("Number of bytes:%d\n", dwNumberOfBytesTransfered);
g_BytesTransferred = dwNumberOfBytesTransfered;
}
int citanie() {
HANDLE hFile;
DWORD dwBytesRead = 0;
char ReadBuffer[BUFFERSIZE] = { 0 };
OVERLAPPED ol = { 0 };
int err;
hFile = CreateFile(TEXT("D:\\testfile.txt"), // file to open
GENERIC_READ, // open for reading
FILE_SHARE_READ, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // normal file
NULL); // no attr. template
if (hFile == INVALID_HANDLE_VALUE)
{
DisplayError(TEXT("CreateFile"));
return 1;
}
// Read one character less than the buffer size to save room for
// the terminating NULL character.
if (!ReadFileEx(hFile, ReadBuffer, BUFFERSIZE - 1, &ol, FileIOCompletionRoutine))
{
DisplayError(TEXT("ReadFile"));
CloseHandle(hFile);
return 1;
}
SleepEx(5000, TRUE);
dwBytesRead = g_BytesTransferred;
// This is the section of code that assumes the file is ANSI text.
// Modify this block for other data types if needed.
err = 0;
if (dwBytesRead > 0 && dwBytesRead <= BUFFERSIZE - 1)
{
ReadBuffer[dwBytesRead] = '\0'; // NULL character
printf("Data read from (%d bytes): \n", dwBytesRead);
printf("%s\n", ReadBuffer);
}
else if (dwBytesRead == 0)
{
printf("No data read from file\n");
}
else
{
printf("\n ** Unexpected value for dwBytesRead ** \n");
err = 1;
}
// It is always good practice to close the open file handles even though
// the app will exit here and clean up open handles anyway.
CloseHandle(hFile);
return err;
}
int main() {
// vysledok funksie main je ExitCode procesu, nula znamena uspesne ukoncenie
int err = zapis();
if (err != 0)
return err;
return citanie();
}
void DisplayError(LPTSTR lpszFunction)
// Routine Description:
// Retrieve and output the system error message for the last-error code
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL);
_tprintf(TEXT("ERROR: %s\n"), (LPTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
}