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:
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:
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);
}