Ismerkedés a Windows API-val

 Mivel fognak többet tudni a prog
  1. Mire jó a Windows API?
    1. A Windows függvények működése
    2. A Windows rendszer felépítése
    3. A Windows illesztőprogram-rendszere
    4. Néhány ismeretes Windows modul/technológia
    5. A Windows API elnevezésrendeszere
    6. Minek ennyi paraméter?
  2. Részletes információk a számítógépünkről
    1. A mai dátum, és idő
    2. A számítógép fontos elérési útjai/szöveges adatai
    3. Információ a számítógép hardvereiről
    4. Az operációs rendszer verzió-információi
  3. Másik program elindítása, a sajátunkból
    1. A system utasítás
    2. A WinExec utasítás
    3. A ShellExecute utasítás
    4. A ShellExecuteEx utasítás
    5. A CreateProcess utasítás 

1, Mire jó a Windows API?

Mivel fognak többet tudni a programjaink, hogyha a Windows API-t használjuk?

Bizonyára mindennapi felhasználóként megismertük a Windows képességeit, pl. böngészés, filmnézés, zenehallgatás, nyomtatás, képszerkesztés, szövegszerkesztés, és még lehetne sorolni mennyi egyéb téren - amennyiben programjainkat ilyen képességekkel szeretnénk felvértezni - szükségünk lesz a Windows API-ra, vagy egy olyan rendszerre, ami ezt elérhetővé teszi számunkra (pl. C++ Builder esetén a VCL keretrendszer ilyen).

Ebben a fejezetben a nyers Windows API-val fogok foglalkozni, és ezen cikkben azon belül is azzal, hogy miként tudhatunk meg több információt a számítógépünkről mint eddig.

Megjegyzés: API = Advanced Programming Interface 

1.1, A Windows függvények működése

Amint elkészítjük majd az első programunkat, azt vesszük észre hogy nem több mint párszáz kilobyte - annak ellenére hogy elvileg az összes Windows függvény használatra készen áll a repertoárjában.

Az oka ennek az, hogy az új függvények ezúttal nem a programunkban lesznek tárolva egészen meglepő módon. Ez úgy történhet, hogy a Microsoft, a rendszer tervezésekor bevezette az ún.: "dinamikus csatolású függvénytár", vagy röviden DLL fogalmát. Ennek az a lényege hogy a függvények, egy külön fájlban lesznek tárolva (ezek *.dll fájlok), amiket a program futás közben dinamikusan tud megnyitni, használhatja a benne lévő függvényeket, és mikor már nincs rá szüksége akkor bezárhatja.

Az eddig megszokott séma szerint statikus csatolásról beszélhettünk, mivel a .h fájlokhoz mindig tartozott .c fájl, és ha pl. a math.h fájlt belefordítottuk a programba az ennek megfelelően megnövelte a méretét. A kérdés lehet helyénvaló - most már nincs szükség .h fájlokra?

Az az igazság hogy a dinamikus csatolás jó dolog, de lényegesebben komplikáltabb mint egy .h fájlt include-olni, ezáltal a statikus csatolás lehetőségét továbbra is megtartották - ilyenkor a program automatikusan nyitja meg a .dll fájlt, az összes függvényhez csinál elérést amire hivatkozunk egy .h fájlban, és bezáráskor a .dll fájlt is zárja. Így lehetővé válik az is, hogy a továbbiakban a dinamikus csatolás helyett, a klasszikus módon férjünk ezekhez a függvényekhez:

#include <windows.h>

Természetesen ezúttal is dinamikus csatolásról beszélhetünk, de ezt a programunk végzi majd a mi segítségünk nélkül indításkor, és leálláskor.

1.2, A Windows rendszer felépítése

A Windows alapvetően egy többrétegű rendszer, az egyes rétegek pedig pont ilyen .dll fájlokban vannak kidolgozva, és sokszor szoros kapcsolatban/kommunikációban állnak egymással (az ábrán a Windows NT felépítése látható):

A Windows egyik nagy különbsége a korábbi operációs rendszerekhez képest, hogy képes több programot is egyszerre futtatni, azaz ez egy multitasking rendszer. Mivel a Microsoft igyekezett a kompatibilitást megtartani a régebbi rendszerekkel, így sok MS-DOS jellegű dolog is megmaradt, mint ezt később látni fogjuk - de alapvetően elmondható, hogy a multitasking kivitelt úgy éri el a rendszer, hogy minden alkalmazás külön címterületet kap a memóriában, ami programozási szempontból azt jelenti hogy az alkalmazások nem férnek hozzá közvetlenül egymás adataihoz - így ha két, vagy több alkalmazás összehangolására kerülne a sor (interprocess communcation, IPC), az mindig elég bonyolult lesz.

Az is elmondható hogy a harver, és a programok között a rendszermag (kernel) fog összhangot biztosítani. A kernel módú oldalon lévő dolgok egy átlagos felhasználónak nem mindig látszanak, ilyen feladatok pl.:

  •  a processzoridő elosztása, hiszen pl. egy processzor, egy időben három programot képtelen futtatni, ezért az idejét el kell osztani

  • az egyes erőforrásokhoz való hozzáférés korlátozása - ne állhasson neki két program egyszerre nyomtatni, a második várja meg az elsőt

  • az imént említett memóriaszervezés

  • a hardverek kezelése - pl. a billentyűleütések, egérmozdulatok továbbítása az illetékes programoknak, stb...

  • stb...

Természetesen amint az ábrán is látható a kernel módú oldal is több alegységből áll ezek közül mi főként kettővel fogunk foglalkozni a programozás során - az felhasználói felület kezelőjével (user32.dll), és a grafikai eszközinterfésszel (gdi32.dll).

Azért lesz ezekre szükségünk, mert felhasználói módú alkalmazásokat fogunk írni, amik programozása során a rendszermaggal közvetlen kapcsolatba nem kell lépni, ezen két modullal is csak azért, mivel hát mégis csak a grafikával/ablakokkal kapcsolatos.

1.2, A Windows illesztőprogram rendszere

Hogyan lehetséges az, hogy beteszünk egy új videókártyát a gépbe - aminek az előzőtől teljesen más a felépítése, és egy internetről letöltött / CD-n mellékelt telepítőcsomag után már is játszhatunk a legújabb játékokkal?

A korábbiakban, pl. a Borland C/C++ grafikai képességeit elemezve láthattuk, hogy MS-DOS alatt minden programnak, minden hardverhez egyedi kezelőprogramokat kellett magával hoznia (nálunk ezek a .BGI fájlok voltak), - és ha a hardverünk ritka volt mint a fehér holló, akkor elég kevés program volt ami képes volt kiaknázni a képességeit.

A Windows alatt ez egy kicsit máshogy történik. MS-DOS alatt, az operációs rendszer közvetlen hozzáférést adott a hardverhez, így azt lehetett csinálni amit akartunk, nyugodtan betölthető volt tetszőleges kezelőprogram pl. egy játékhoz - néha ez hibát is okozott. Korábban már említésre került, hogy a Windows egy védett módú rendszer, azaz a hardverekhez való hozzáférés is korlátozva van, bármikor beavatkozhat - ezt viszont csak egy módon teheti meg - ha rajta keresztül végezzük a különféle műveleteket.

Eszközkezelő

Ez egy nagyon nagy dolog, mert ez azt jelenti hogy bármilyen hardver is van a gépben, ugyanazokkal a függvényhívásokkal, ugyanazokat az eredményeket kapjuk. Ezt úgy éri el a rendszer, hogy szabványosított illesztőprogramokat írhatnak a gyártók a saját termékükhöz, amelynek mint egy adott fogaskeréknek egy gépben, illeszkednie kell a Windows rendszerbe. Ezek az illesztőprogramok már közvetlen hozzáféréssel, elvégezhetik azokat az utasításokat (pl. egy vonal rajzolását), amit az adott program kér a rendszeren keresztül.

Manapság ez az eszköztár egészen széleskörűvé duzzadt pl. egy videókártya esetén (nagyságrendben nagyobb mint amit a Borland C/C++ grafikában tudtunk csinálni), illetve megjelentek olyan szabványok is (mint pl. a VGA) ami szerint egy általános illesztőprogramnak megfelel az adott hardver, azzal működik (még akkor is ha a képességeinek a töredékét használja ki).

Ezek az illesztőprogramok teszik lehetővé azt, hogy a Windows rendszer lényegében képes legyen bármilyen hardverösszeállítás mellett elfutni - hogy ennek mik a korlátai, az csak a gyártókra van bízva.

Megjegyzés: Ahhoz hogy egy gyártó piacra dobhasson egy illesztőprogramot, melyet mindenki könnyedén telepíthet a gépére, több szempontnak is meg kell felelnie:

  • az illesztőprogramnak digitális aláírással kell rendelkeznie

  • az illesztőprogramnak át kell esnie a WHQL szigorú tesztelésén, melyen nem omolhat össze

Ezek a szigorú feltételek garantálják azt, hogy a Windows ne fulladjon kékhalálba minden második program/játék elindításakor - így megfelelő illesztőprogramok esetén, a kékhalál legfeljebb konfigurációs/hardveres hiba miatt állhat elő.

Megjegyzés: A kékhalál (blue screen of death BSoD) akkor jön elő, amikor a kernel módú oldalon valamilyen hiba történik. Amikor egy alkalmazás hibát okoz, legrosszabb esetben morgunk egyet a piros X jelű ablak miatt, és az OK gomb hatására eltűnik. A BSoD esetén, a hiba hardverhez közeli, rendszerszintű, pl. egy adott hardveren végzett érvénytelen művelet miatt - ezért általában ilyenkor a rendszer futását meg kell szakítani, pl. azzal hogy újraindul a gép.

Megjegyzés: A képen látható eszközkezelő, mindenkinek ott van a számítógépén - több helyen is fellelhető:

  1. Vezérlőpult -> Rendszer tulajdonságai -> Eszközkezelő

  2. Sajátgép/Számítógép/Ez a gép -> jobbklikk az ikonjára, majd Tulajdonságok -> Eszközkezelő

Amik ezen ablakban látható hardverek, azokhoz mind vagy Microsoft által készített, vagy külső gyártó által előállított illesztőprogram van telepítve.

1.3, Néhány ismeretes Windows modul/technológia

Csak néhány (akár általunk is a későbbiekben használt) technológia/modul, a teljesség igénye nélkül:

  • GDI (Graphics Device Interface): különféle rajzolási műveletek megvalósítása, mint a Borland C/C++ grafikai képességeinél - de jelen esetben ez történhet nyomtatóra, .bmp fájlba, stb...

  • GDI+: a GDI továbbfejlesztése, már részleges videókártyás gyorsítással bír, kezeli a tényleges átlátszóságot, a transzformációkat, stb...

  • DWM (Desktop Window Manager): Windows Vista óta van jelen, ez rajzolja ki azokat a látványos effekteket az ablakok lecsukásakor, stb.., illetve az átlátszó ablakkereteket

  • DDE: a programok közötti kommunikációt hivatott megkönnyíteni (IPC), elég elavult technológia, de a mai napig működőképes - a Windows 3.1 programkezelőjével pl. DDE-n keresztül lehetett kommunikálni

  • OLE (Object Linking and Embbedding): alkalmazások közötti együttműködést biztosít, pl. mikor egy WordPad dokumentumba, beszúrunk egy Paint képet - manapság jelentősége kisebb, de a Word 2017-ben is van mind a mai napig Objektum beszúrása... gomb

  • COM (Component Object Model): az OLE kiterjesztése, általa objektumok használhatóak sok programon keresztül is (IPC), pl. egy Word példányt 5 program irányít - hálózati kiterjesztése a DCOM, helyi kiterjesztése a COM+

  • MCI (Media Control Interface): a legelső technológiája a Windowsnak, hang/videó felvételre, lejátszásra, stb... - ennek már van sokkal modernebb változata is a DirectX-en keresztül, amit a Windows Media Player is használ, de annak a programozása is bonyolultabb

  • DirectX: egy nagyon komplex csomag, amelyet a játékok is használnak mind a mai napig - videókártyásan gyorsított médialejátszás, rajzolás, vagy 3D mozgókép előállítása történhet meg általa - több almodulból is áll mint a Direct2D, Direct3D, DirectPlay, DirectShow, ...

Ezen felül a rendszer rengeteg alrészt tartalmaz még, a teljes lista itt található meg:

Mellesleg, a Windows API-val kapcsolatban bármilyen kérdésünk merül fel, azt 90% hogy az MSDN-en (Microsoft Developer Network) megtaláljuk. A jó hír, hogy mivel maga a Windows is C++ nyelven íródott (eredetileg), ezért a példaprogramok, az adatszerkezetek, stb... cuccok leírásait is C++ nyelven fogjuk megtalálni. 

1.4, A Windows API elnevezésrendeszere

A Windows API egy saját elnevezésrendszerrel dolgozik, amelyet nem baj ha értünk - azt szolgálná hogy megkönnyítse a dolgunkat. Az utasítások általában nagyon sok paraméterrel dolgoznak majd, sok lesz a struktúra, és a halmaz is - de ettől még nem kell megijedni teljesen. Kezdésként nézzük meg az CreateProcess függvény MSDN-es leírását (másik program indítható el vele):

BOOL WINAPI CreateProcess(
_In_opt_    LPCTSTR               lpApplicationName,
_Inout_opt_ LPTSTR                lpCommandLine,
_In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_        BOOL                  bInheritHandles,
_In_        DWORD                 dwCreationFlags,
_In_opt_    LPVOID                lpEnvironment,
_In_opt_    LPCTSTR               lpCurrentDirectory,
_In_        LPSTARTUPINFO         lpStartupInfo,
_Out_       LPPROCESS_INFORMATION lpProcessInformation
);

Igen, ez elsőre sokkolónak tűnhet, de ha egy kicsit átnézzük akkor lényegében nem az.

A visszatérési érték az első sorból látható hogy BOOL WINAPI. Ez nem jelent többet, mint hogy a visszatérési értéke logikai típus, azaz használható egy if-else szerkezetben - a WINAPI jelző pedig magára a függvényhívásra jellemző (*.dll készítése esetén ajánlott használni, így a legtöbb programnyelv fogja tudni használni a függvényünket). A legtöbb esetben ilyen típusú visszatérési érték mellett az oldalon ezt találjuk - néhol kiegészítésekkel:

Return Value
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.

Ez a továbbiakban a legtöbb BOOL típusú függvényre igaz lesz - de sose baj ha elolvassuk a Return Value részt.

A paraméterek ezúttal jelzőt is kaptak a típus és név mellett, méghozzá hogy milyen jellegű paraméterről beszélünk:

  • _In_: kötelező bemenő paraméter, innen csak olvasni fog - lehet pl. const int i = 5; jellegű változó is

  • _Out_: kötelező kimenő paraméter, ide csak írni fog, ezért olyan változó címét kell megadni, amibe írhat is

  • _Inout_: kötelező kétirányú paraméter, innen írni/olvasni is fog, ezért olyan változó címét kell megadni, amibe írhat is

  • _In_opt_: nem kötelező bemenő paraméter, ha nem akarjuk megadni az értéke lehet NULL

  • _Out_opt_: nem kötelező kimenő paraméter, ha megadjuk egy változó címét amibe írhat is, akkor plusz információkkal leszünk gazdagabbak, de ha ezekre nincs szükségünk lehet az értéke NULL is

  • _Inout_opt_: nem kötelező kétirányú paraméter, megadható változó címe amibe írhat, vagy NULL

A paraméterek típusa egy kicsit érdekesebb - néhány típusunk a BOOL módjára új nevet kapott, néhány pedig struktúrára utal. Sokszor a nevük is elárulja hogy micsodák is azok:

  • LP*STR: ezek valamilyen szöveges típusú adatok, lényegében char*, vagy wchar_t*, vagy egyéb variációk - az LP a local pointer rövidítése (helyi mutató), azt jelenti hogy a saját programban kell legyen a változó, illetve az STR a string, vagyis karakterlánc rövidítése, a köztes betűk adják meg hogy milyen típusú karakterekből álljon a szöveg

  • LPSECURITY_ATTRIBUTES: biztonsági tulajdonságok struktúrája, helyi mutató (LP), tehát ha van is ilyen struktúránk, cím szerint kell átadni - még szerencse hogy opcionális paraméter

  • DWORD: unsigned long int lényegében

  • LPVOID: void* lényegében

  • LPSTARTUPINFO: az indítási adatok struktúrája, helyi mutató (LP), tehát cím szerint kell átadni

  • LPPROCESS_INFORMATION: ebbe fogja elmenteni az elindított program adatait, helyi mutató (LP), tehát cím szerint kell átadni

A paraméterek elnevezése is segít, az első pár betű mindig a típusra utal - ez hasznos ha automata kiegészítéssel dolgozunk. Pl.: bInheritHandles típusa BOOL, dwCreationFlags típusa DWORD - itt a flag szó utal arra hogy halmazról van szó, lejjebb le szokták írni, milyen értékek használhatóak itt.

1.5, Minek ennyi paraméter?

Ugyebár az elmúlt szakaszban éltettem egy kicsit, hogy milyen sokoldalú a rendszer. Mivelhogy ilyen sokoldalú, nem célszerű ugyanarra a feladatra 195 függvényt megírni, elég egy is - de az minden elképzelésre legyen jó. Ennek az a hátránya, hogy ilyen bonyolult paraméterlistákat kapunk, ahol átlag programozóként a paraméterek 60-70%-ka NULL lesz úgyis. 

De hát valamit valamiért....

2, Részletes információk a számítógépünkről

Természetesen nem lehet egyből a húrok közé csapni, ez még a C++ nyelven belül is egy külön állatfajta, így egyelőre maradunk a konzol alkalmazásoknál - de most már Windows API függvényekkel is fogunk dolgozni. Az első cikkben, még csak adatot gyűjtünk a saját gépünkről - lényegesen többet mint a beépített C++ függvényekkel tudnánk.

2.1, A mai dátum, és idő

Természetesen erre van beépített C függvény is, de a Windows API-val való barátkozás jegyében nem baj ha megnézünk egy ilyet is, a használt függvény a GetSystemTime lesz: 

#include <stdio.h>
#include <windows.h>

const char* aszHetNapjai[] = {"vasarnap", "hetfo", "kedd", "szerda", "csutortok", "pentek", "szombat"};


int main()
{
	SYSTEMTIME ido;

	GetSystemTime(&ido);
	printf("A pillanatnyi datum/ido: %04d.%02d.%02d, %s, %02d:%02d:%02d.%04d",
		ido.wYear,
		ido.wMonth,
		ido.wDay,
		aszHetNapjai[ido.wDayOfWeek],
		ido.wHour,
		ido.wMinute,
		ido.wSecond,
		ido.wMilliseconds);

	getchar();
	return 0;
} 

A jó hír hogy a Windows API-ban megírt kódok lényegében minden programnyelvben ugyanúgy néznek ki - íme ugyanez Pascal nyelvben (csak a kiíratás, stb... kapcsolatos parancsok térnek el szinte):

program prjDatumIdo;

uses
  SysUtils, Windows;

const
  aszHetNapjai: array [0..6] of string =
    ('vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat');

var
  ido: SYSTEMTIME;

begin
  GetSystemTime(ido);
  WriteLn(format('A pillanatnyi datum/ido: %04d.%02d.%02d, %s, %02d:%02d:%02d.%04d', [
    ido.wYear,
    ido.wMonth,
    ido.wDay,
    aszHetNapjai[ido.wDayOfWeek],
    ido.wHour,
    ido.wMinute,
    ido.wSecond,
    ido.wMilliseconds]));
  ReadLn;
end.

Tehát eddig végül is nem volt sokkal bonyolultabb, mint egy beépített C/C++ függvényt használni erre a célra - az újdonság a .h fájlban, és a típusokban van most még - nagyjából a következő példákra is igaz lesz ez.

2.2, A számítógép fontos elérési útjai/szöveges adatai

A teljesség igénye nélkül, íme hogy tudhatjuk meg pl. hova helyezhetünk el ideiglenes fájlokat, vagy hova van telepítve a Windows rendszer. Habár az utasítások LPSTR típust kérnek, jól látható hogy ez a megszokott char*-al egyenérékű. Mellesleg az általunk használt utasítások a GetWindowsDirectory, a GetTempPath, és a GetSystemDirectory - de még jópár ilyet lehet találni a rendszerben.

#include <stdio.h>
#include <windows.h>

int main()
{
	char szWinDir[4096] = {0}, szTempPath[4096] = {0}, szSystemRoot[4096] = {0};

	GetWindowsDirectory(szWinDir, sizeof(szWinDir));
	GetTempPath(sizeof(szTempPath), szTempPath);
	GetSystemDirectory(szSystemRoot, sizeof(szSystemRoot));

	printf("A Windows telepitesi mappaja: \"%s\"", szWinDir);
	printf("\nAz ideiglenes fajlok mappaja: \"%s\"", szTempPath);
	printf("\nA rendszermappa helye: \"%s\"", szSystemRoot);

	getchar();
	return 0;
}

Jelen esetben a fordítási beállítások miatt a program végig ANSI karakterkódolással dolgozik, így pl. egy orosz/kínai Windows alatt nem futna le helyesen. Ilyenkor wchar_t típust kell használni és az utasítások W jelű változatát, lásd GetWindowsDirectoryW, .... - ha pedig mindenféleképpen ki szeretnénk követelni az ANSI kódolást akkor az utasítások A jelű változata, ezt garantálni fogja pl. GetWindowsDirectoryA. Ez a jelölés csak akkor áll fenn, ha az utasítás szövegekkel is dolgozik.

Sok esetben az ilyen szövegekkel kapcsolatos utasítások képesek arra is hogy megmondják, hogy mennyi helyet kell lefoglalnunk a szövegnek. Erre lesz a következő példa, a PATH környezeti változó lekérése, hibakezeléssel - a használt a utasítás, a GetEnvironmentVariable függvény lesz:

#include <stdio.h>
#include <windows.h>

int main()
{
	char* szPath;
	unsigned long int dwRetVal;

	dwRetVal = GetEnvironmentVariable("PATH", NULL, 0);
	szPath = new char[dwRetVal];

	if (!GetEnvironmentVariable("PATH", szPath, dwRetVal + 1))
	{
		fprintf(stderr, "Hiba tortent a lekerdezes soran, hibakod: %d", GetLastError());

		delete [] szPath;
		return 1;
	}
	else
	{
		printf("A PATH kornyezeti valtozo erteke: %s", szPath);
	}

	getchar();

	delete [] szPath;
	return 0;
}

Zárásként pedig érdemes megnézni azt is, hogy egyéb szöveges adatok miként kérhetőek le a rendszertől - mint pl. a számítógépnév (GetComputerName), illetve az aktuálisan bejelentkezett felhasználó neve (GetUserName):

#include <stdio.h>
#include <windows.h>

int main()
{
	char szComputerName[4096] = {0}, szUserName[4096] = {0};
	unsigned long int dwBufferLen = 4095;

	GetComputerName(szComputerName, &dwBufferLen);

	dwBufferLen = 4095;
	GetUserName(szUserName, &dwBufferLen);

	printf("A szamitogep neve: %s\nA jelenlegi felhasznalo neve: %s",
		szComputerName, szUserName);

	getchar();
	return 0;
} 

Ami itt érdekes, hogy a karaktertömb méretét, változóba kellett menteni - mivel cím szerinti átadást kellett végezni. Ez azért van így, mert a függvények visszaírnak a dwBufferLen változóba, méghozzá azt hogy hány karaktert írt bele a tömbünkbe (jelenleg a zárókarakter nélkül).

2.3, Információ a számítógép hardvereiről

Nézzük meg pl. az alábbiakat:

  • Van-e csatlakoztatva egér a géphez?

  • Mekkora a jelenlegi képernyőfelbontás?

  • Mennyi a gép fizikai memóriája, és ebből mennyi elérhető?

Ezek a kérdések már egy vacak szintű játék készítésekor is érdekesek lehetnek - pl. hogy betöltheti-e a 300MB-os zenét a memóriába, vagy nem, esetleg hogy hova helyezze a képernyőn az ablakot, stb...

Íme hát a példaprogram - a használt függvényeink a GetSystemMetrics, és a GlobalMemoryStatusEx:

#include <stdio.h>
#include <windows.h>

int main()
{
	if (GetSystemMetrics(SM_MOUSEPRESENT))
		printf("A szamitogephez van eger csatlakoztatva.");
	else
		printf("A szamitogephez nincs eger csatlakoztatva.");

	printf("\nA kepernyo jelenlegi felbontasa: %dx%d",
	GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));

	MEMORYSTATUSEX memoria = {0};
	memoria.dwLength = sizeof(memoria);
	GlobalMemoryStatusEx(&memoria);

	printf("\nA teljes fizikai memoria: %.2f GB", memoria.ullTotalPhys / 1024. / 1024. / 1024.);
	printf("\nAz elerheto fizikai memoria: %.2f GB", memoria.ullAvailPhys / 1024. / 1024. / 1024.);

	getchar();
	return 0;
}

Itt megint két érdekességgel lehet találkozni. Az első az az, hogy a GetSystemMetrics rengeteg mindent tud - nem csak ez a 3db SM_ kezdetű konstans található, az MSDN oldalon látható hogy elképesztő mennyiségű van - vagyis megint rengeteg hasonló dolog össze lett gyűjtve egy utasítás alá.

A másik érdekesség a memória lekérésénél a struktúra inicializálása. Erre akkor van szükség, ha a struktúra cbLength, dwSize, stb... mezőkkel indít, ilyenkor:

  • fel kell tölteni nullákkal, erre alkalmas a példában látható megoldás, illetve a memset(), vagy ZeroMemory()utasítás

  • a következő pedig hogy az első mezőt, ami a méretre vonatkozik ki kell tölteni

Erre azért van szükség, mert a Windows fejlesztése során ezeket a struktúrákat tovább bővíthetik új mezőkkel, miközben a régieket meghagyják. Ilyenkor ha egy régebbi typedef szerint programozunk, az így kapott struktúra mérete kisebb lesz, mint az újé. Ebből fogja tudni a Windows, hogy meddig írhat bele a struktúrába, mivel az első mezeje garantáltan a struktúra méretét tartalmazza. Az ilyen húzások garantálják hogy a régebbi programok is futni fognak a gépen egy-egy rendszerfrissítés után.

2.4, Az operációs rendszer verzió-információi

Mi van akkor ha meg akarjuk tudni pl. hogy Windows XP, 7, vagy 10 alól futtatjuk a programot? Természetesen erre is van lehetőség, a megoldást a GetVersionEx függvény nyújtja:

#include <stdio.h>
#include <windows.h>

int main()
{
	OSVERSIONINFOA verzio = {0};
	verzio.dwOSVersionInfoSize = sizeof(verzio);
	if (GetVersionEx(&verzio))
	{
		printf("A rendszer verzioszama: %d.%d",
			verzio.dwMajorVersion, verzio.dwMinorVersion);

		switch (verzio.dwPlatformId)
		{
			case VER_PLATFORM_WIN32s:
				printf("\nA program Win32s alol fut.");
				break;
			case VER_PLATFORM_WIN32_WINDOWS:
				printf("\nA program Windows 9x alapu rendszer alol fut.");
				break;
			case VER_PLATFORM_WIN32_NT:
				printf("\nA program Windows NT alapu rendszer alol fut.");
				break;
		}
	}

	getchar();
	return 0;
}

Nézzük meg az eredmény mit is jelent, az alábbi táblázatban összefoglaltam néhány lehetséges kombinációt:

Verziószám Termékcsalád Megnevezés
3.0 Win32s Windows 3.0
3.1 Windows 3.1
3.1 Windows NT Windows NT 3.1
3.51 Windows NT 3.51
4.0 Windows NT 4
4.0 Windows 9x Windows 95
4.1 Windows 98
4.2 Windows Millennium Edition
5.0 Windows NT Windows 2000
5.1 Windows XP
6.0 Windows Vista
6.1 Windows 7
6.2 Windows 8
6.3* Windows 8.1
10.0* Windows 10

A csillaggal jelölt verziószámoknál a rendszer attól függően, hogy mennyire régi az alkalmazás dönthet úgy is hogy 6.2-őt ad vissza, és Windows 8-nak tetteti magát.

3, Másik program elindítása, a sajátunkból

Már korábban említettem, hogy ezt meg lehet oldani a CreateProcess segítségével - de az már alapból komplikáltnak tűnt - az is. Nézzünk pár példát arra, milyen egyéb lehetőségeink vannak. A cél minden példában a Jegyzettömb megnyitása lesz.

Igazából ez a témakör egy külön fejezetet is megérne, itt csak úgy nagjából írom le a dolgokat. 

3.1, A system utasítás

A parancssornak kiadja a parancsot és megvárja annak lefutását. Beépített utasítás, nem a Windows része.

#include <stdio.h>

int main()
{
	system("notepad");

	getchar();
	return 0;
}

 

3.2, A WinExec utasítás

Amit a Futtatás ablakba (Win+R) beírnánk, ugyanazt kell megadni, és lefuttatja, de nem várja meg. Nem a legtestreszabhatóbb, és nagyon régi.

#include <windows.h>

int main()
{
	WinExec("notepad", SW_SHOWNORMAL);
	return 0;
}

A Microsoft helyette a CreateProcess utasítást javasolja.

3.3, A ShellExecute utasítás

Egy remek alternatívája a WinExec-nek, modern, és nem csak programok, hanem fájlok, weboldalak megnyitására, nyomtatására, stb... is alkalmas - nem várja meg a másik program bezárását.

#include <windows.h>
#include <shellapi.h>

int main()
{
	ShellExecute(0, "open", "notepad", NULL, NULL, SW_SHOWNORMAL);
	return 0;
}

Ez a sokminden azért lehetséges, mivel az indítás a rendszerhéjon (shell) keresztül történik, így szükséges az ezzel kapcsolatos .h fájl használata is: 

#include <shellapi.h>

3.4, A ShellExecuteEx utasítás

A ShellExecute kiterjesztése, itt már pl. eldönthetjük hogy megvárjuk-e a másik programot. 

#include <windows.h>
#include <shellapi.h>

int main()
{
	SHELLEXECUTEINFO inditas = {0};
	inditas.cbSize = sizeof(inditas);
	inditas.fMask = SEE_MASK_NOCLOSEPROCESS;
	inditas.lpVerb = "open";
	inditas.lpFile = "notepad";
	inditas.nShow = SW_SHOWNORMAL;
	ShellExecuteEx(&inditas);

	WaitForSingleObject(inditas.hProcess,INFINITE);

	CloseHandle(inditas.hProcess);
	return 0;
}

3.5, A CreateProcess utasítás

Íme a rettenet maga, mivel ez a legteljeskörűbb, szintén eldönthető a várakozás. Ez a legegyszerűbb használata a parancsnak:

#include <windows.h>

int main()
{
	STARTUPINFO inditas = {0};
	inditas.cb = sizeof(inditas);

	PROCESS_INFORMATION proc_info = {0};

	if (CreateProcess(NULL, "notepad", NULL, NULL, true, NULL, NULL, NULL, &inditas, &proc_info))
	{
		WaitForSingleObject(proc_info.hProcess, INFINITE);
		CloseHandle(proc_info.hProcess);
		CloseHandle(proc_info.hThread);
	}

	return 0;
}

Előnye, hogy nem muszáj az alkalmazásnak ".exe" kiterjesztésűnek lennie, pl. ".tmp" kiterjesztéssel is futtatható az ideiglenes mappából vele állomány, ha az egyébként futtatható. Erre egyik sem képes a fentiek közül.