Programování pro Windows v C++ – úvod
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu
Reklama
Reklama

Programování pro Windows v C++ – úvodProgramování pro Windows v C++ – úvod

 

Programování pro Windows v C++ – úvod

Google       Google       18. 6. 2014       16 982×

Ukážeme si, že psát aplikace pro Windows bez použití hotových knihoven či frameworků nemusí být tak frustrující, jak se zdá. V tomto a návazných článcích si ukážeme, jak efektivně vytvářet rychlé a relativně nenáročné aplikace v C++ a Win API.

Reklama
Reklama

Pár řádků na úvod

Těm, kteří čtou tento článek, je asi zbytečné připomínat sílu a výkon jazyka C/C++ (samozřejmě při vývoji nativních aplikací). V následujícím seriálu se přesvědčíme o tom, že ani pro vývoj standardních aplikací pro Windows s „okenním” uživatelským rozhraním není v mnoha případech třeba snižovat tento výkon a zvyšovat nároky aplikace používáním rozsáhlých knihoven (nadstaveb nad Win API), jako jsou například MFC, VCL nebo .NET Framework. O těch multiplatformních ani nemluvě, ty jsou z hlediska nároků na prostředky a výkonu kapitolou samy pro sebe.

Na úvod předesílám, že půjde o programování aplikací výhradně pro Windows, takže ve zdrojovém kódu nehodlám řešit otázky „přenositelnosti“ a dále budu využívat některá „Microsoft specifika“, například pohodlnou deklaraci (a současně definici) globálních proměnných pomocí __declspec(selectany).

Pokud jde o ukázku jakési knihovny, kterou si postupně vytvoříme, bude kompletně v hlavičkovém souboru (tento přístup používá například knihovna ATL).

Co by měl čtenář znát

Pro pochopení dále uvedeného by měl čtenář tohoto a budoucích návazných článků mít alespoň základní znalosti jazyka C++ a základních principů programování ve Win API (zejména co jsou to zprávy Windows, smyčka zpráv, procedura okna). Na téma „výuky“ Win API jsem před pár lety napsal sérii článků Učíme se Win API, popř. zde je  výpis celého seriálu v jednom dokumentu.

Vytvoření kostry aplikace ve Win API

V tomto úvodním článku si vytvoříme základ aplikace s jedním oknem, přičemž ta „frustrující“ část kódu (C++ a WinAPI) bude ve výše zmíněné knihovně. Odpovíme také na (dost často v diskusích kladenou) otázku, zda a jak je možné mít proceduru okna jako členskou funkci třídy a jak obsloužit více instancí této třídy (zapouzdřující okno Windows - tj. handle typu HWND).

Aplikaci založíme (v MS Visual Studiu) jako Win32 Windows aplikaci (pozor, nikoliv konzolovou). Protože chceme začínat s čistým štítem, odstraníme (v Solution exploreru) z projektu všechny soubory kromě stdafx.h, stdafx.cpp, targetver.h a souboru nazev_pojektu.cpp (ve kterém je vstupní bod aplikace, tj. funkce WinMain). V tomto souboru smažeme veškerý wizardem vygenerovaný zdrojový kód a ponecháme jen následující:

#include "stdafx.h"

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int) throw()
{
	return 0;
}

Dále si založíme základ knihovny. Do projektu přidáme nový hlavičkový soubor (já jsem ho nazval winapi.h), který umístíme nejlépe do nějaké složky vedle složky s projektem. Do tohoto souboru si vložíme hlavičkové soubory z Windows SDK a také přímo ve zdrojovém kódu přidáme do projektu příslušné statické knihovny (lib). Většinu dále uvedených hlaviček nebudeme zatím potřebovat, avšak připravíme si je pro další projekty.

#include <windows.h>
#include <comdef.h>
#include <commctrl.h>
#include <gdiplus.h>
#include <shlobj.h>
#include <strsafe.h>
#include <shlwapi.h>
#include <uxtheme.h>
#include <vssym32.h>
#include <process.h>
#include <time.h>
#include <lm.h>

#pragma warning(push)
#pragma warning(disable: 4995)
#pragma warning(disable: 4996)
#include <string>
#include <algorithm>
#include <vector>
#pragma warning(pop)

#pragma comment (lib, "comctl32.lib")
#pragma comment (lib, "gdiplus.lib")
#pragma comment (lib, "shlwapi.lib")
#pragma comment (lib, "UxTheme.lib")
#pragma comment (lib, "version.lib")
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Wininet.lib

Tento hlavičkový soubor pak vložíme do souboru stdafx.h s následující strukturou:

#pragma once

#include "targetver.h"

#include "..\\knihovna\\winapi.h"
using namespace winapi;

#ifdef _UNICODE
#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_IA64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif
#endif

Základ třídy zapouzdřující okno

V naší „knihovně“ (hlavičkovém souboru winapi.h) si vytvoříme základ třídy zapouzdřující okno Windows, tj. handle typu HWND. Prozatím bude obsahovat pouze minimum členských funkcí, které nás oprostí od nutnosti rozepisovat opakující se či nevyužité parametry volání WinAPI funkcí a také v DEBUG režimu pomocí _ASSERT pomohou zachytit případné chyby.

Ještě předtím si do knihovny přidáme globální funkci (resp. její prozatím 2 přetížené varianty), kterou budeme volat v případě výskytu takové chyby, při které je záhodno upozornit uživatele chybovou hláškou a ukončit běžící aplikaci, např. z důvodu možné kumulace neuvolněných objektů či paměti.

Také si přímo do knihovny přidáme globální proměnnou typu HINSTANCE, ve které si budeme po celou dobu běhu udržovat handle instance (tj. 1. parametr funkce WinMain), které budeme často v kódu potřebovat, například při načítání dat z prostředků (resource).

Úvodní část knihovny bude prozatím vypadat následovně:

__declspec(selectany) HINSTANCE _hinstance = NULL;

#pragma region globalni_funkce

// Volaná při kritické chybě, ukončí běžící exe aplikaci
__declspec(noinline) inline void __declspec(noreturn) _kriticka_chyba(const wchar_t* sz_text) throw()
{
	FatalAppExitW(0, sz_text);
}


// Volaná při kritické chybě, ukončí běžící exe aplikaci
__declspec(noinline) inline void __declspec(noreturn) _kriticka_chyba() throw()
{
	HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
	if (SUCCEEDED(hr))
		hr = E_UNEXPECTED;
	_kriticka_chyba(_com_error(hr).ErrorMessage());
}
#pragma endregion globalni_funkce

takto vypadá kód třídy okna:

#pragma region okno
//-------------------------------------------------------------------------------------------------
//	class okno
//-------------------------------------------------------------------------------------------------

class okno
{
protected:
	HWND m_hwnd;
	bool m_b_vytvorene_okno;

public:
	okno(HWND hwnd = NULL) throw() :
		m_b_vytvorene_okno(false),
		m_hwnd(NULL)
	{
		if (hwnd != NULL)
			nastavit(hwnd);
	}

public:
	~okno() throw()
	{
		uvolnit();
	}

public:
	const HWND hwnd() const throw()
	{
		return m_hwnd;
	}

public:
	operator HWND() const throw()
	{ 
		return m_hwnd; 
	}

public:
	void uvolnit() throw()
	{
		if (m_hwnd)
		{
			if (m_b_vytvorene_okno)
				if (IsWindow(m_hwnd))
					if (!DestroyWindow(m_hwnd))
						_kriticka_chyba();
			m_hwnd = NULL;
		}
		m_b_vytvorene_okno = false;
	}

public:
	void zrusit_okno() throw()
	{
		if (IsWindow(m_hwnd))
			if (!DestroyWindow(m_hwnd))
				_kriticka_chyba();
		m_b_vytvorene_okno = false;
	}

public:
	// V případě neplatného parametru vyvolá kritickou chybu
	void nastavit(HWND hwnd) throw()
	{
		_ASSERTE(IsWindow(hwnd));
		if (!IsWindow(hwnd))
			_kriticka_chyba();
		uvolnit();
		m_hwnd = hwnd;
		m_b_vytvorene_okno = false;
	}

public:
	__forceinline LRESULT send_message(UINT zprava, WPARAM wparam = 0, LPARAM lparam = 0) throw()
	{
		_ASSERTE(IsWindow(m_hwnd));
		return SendMessageW(m_hwnd, zprava, wparam, lparam);
	}

public:
	__forceinline BOOL post_message(UINT zprava, WPARAM wparam = 0, LPARAM lparam = 0) throw()
	{
		_ASSERTE(IsWindow(m_hwnd));
		return PostMessageW(m_hwnd, zprava, wparam, lparam);
	}

public:
	void zobrazit(int cmd_show = SW_SHOW) throw()
	{
		_ASSERTE(IsWindow(m_hwnd));
		ShowWindow(m_hwnd, cmd_show);
	}
};	// class okno
#pragma endregion okno

Procedura okna jako členská funkce třídy C++

Velice častou otázkou na diskusních fórech bývá, jak mít proceduru okna jako členskou funkci třídy C++, která zapouzdřuje handle okna (HWND). Problém je v tom, že adresa této funkce (tzv. procedury okna) musí být známa již v době sestavení programu, neboť ji zadáváme při registraci třídy okna jako prvek lpfnWndProc struktury WNDCLASSEX.

Protože adresu „normální“ členské funkce třídy kompilátor znát nemůže, znamená to, že procedurou okna může být buď globální funkce (tj. mimo jakoukoliv třídu) nebo statická funkce třídy C++.

Pokud tedy použijeme jednu z uvedených možností (v našem případě statickou funkci třídy, kterou si podědíme od výše uvedené třídy okno), vyvstane otázka, jak obsloužit více současně existujících oken, tj. instancí této třídy. Možných řešení je více a různé knihovny (ATL, MFC, VCL to řeší po svém).

Já osobně využívám způsobu uložení ukazatele na konkrétní instanci třídy do tzv. uživatelských dat okna, což je poslední parametr funkce CreateWindowEx, která okno vytvoří.

Do naší knihovny si tedy přidáme třídu okno_impl odvozenou od třídy okno, která bude implementovat proceduru okna a zajistí volání (virtuální) funkce window_proc, ve které budeme mít obsluhu zpráv Windows. Celý kód této třídy vypadá následovně:

#pragma region okno_impl
//-------------------------------------------------------------------------------------------------
// class okno_impl
//-------------------------------------------------------------------------------------------------
// Při vytvoření pomocí CreateWindowEx je nutné nastavit jako poslední parametr this!
//-------------------------------------------------------------------------------------------------

class okno_impl : public okno
{
protected:
	virtual const wchar_t* trida() const throw() = 0;

protected:
	static LRESULT CALLBACK okno_impl_window_proc(HWND hwnd, UINT zprava, WPARAM wparam, LPARAM lparam) throw()
	{
		okno_impl* p_objekt = (okno_impl*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
		if (zprava == WM_CREATE)
			SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)((LPCREATESTRUCTW)lparam)->lpCreateParams);
		if (p_objekt)
			return p_objekt->window_proc(zprava, wparam, lparam);
		else
			return DefWindowProc(hwnd, zprava, wparam, lparam);
	}

public:
	// Vrátí true pokud byla neexistující třída nově zaregistrovaná
	// Pokud už byla zaregistrovaná před voláním funkce, neudělá nic a vrátí false
	// Při neúspěchu vyvolá kritickou chybu -> havarijní ikončení aplikace
	bool zaregistrovat_tridu() throw()
	{
		_ASSERTE(trida() != NULL);
		WNDCLASSEX wc;
		memset(&wc, 0, sizeof(wc));
		wc.cbSize = sizeof(wc);
		if (GetClassInfoEx(_hinstance, trida(), &wc))
			return false;
		memset(&wc, 0, sizeof(wc));
		wc.cbSize = sizeof(WNDCLASSEX);
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = okno_impl_window_proc;
		wc.hInstance = _hinstance;
		wc.hIcon = NULL;
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);
		wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		wc.lpszClassName = trida();
		wc.hIconSm = NULL;
		if (!RegisterClassEx(&wc))
			_kriticka_chyba();
		return true;
	}

public:
	// Vrátí true, pokud byla existující třída odregistrovaná
	// Pokud třída nebyla zaregistrovaná před voláním funkce, neudělá nic a vrátí false
	bool odregistrovat_tridu() throw()
	{
		_ASSERTE(trida() != NULL);
		WNDCLASSEX wc;
		memset(&wc, 0, sizeof(wc));
		wc.cbSize = sizeof(wc);
		if (!GetClassInfoEx(_hinstance, trida(), &wc))
			return false;
		if (!UnregisterClassW(trida(), _hinstance))
			_kriticka_chyba();
		return true;
	}

public:
	virtual void vytvorit() throw()
	{
		_ASSERTE(m_hwnd == NULL);
		_ASSERTE(trida() != NULL);
		zaregistrovat_tridu();
		m_hwnd = CreateWindowEx(0, trida(), L"Implementace okna", WS_OVERLAPPEDWINDOW,
			150, 120, 640, 480, NULL, NULL, _hinstance, this);
		if (m_hwnd == NULL)
			_kriticka_chyba();
		zobrazit();
	}

protected:
	virtual LRESULT window_proc(UINT zprava, WPARAM wparam, LPARAM lparam) throw()
	{
		return DefWindowProc(m_hwnd, zprava, wparam, lparam);
	}
}; // class okno_impl
#pragma endregion okno_impl

Když máme toto vše připravené, můžeme snadno vytvořit základní aplikaci s jedním oknem. V kódu aplikace si vytvoříme třídu odvozenou od třídy okno_impl, jednu její instanci jako globální proměnnou. Pak stačí jen přepsat virtuální funkci window_proc, ve které musíme (protože jde o hlavní okno aplikace) obsloužit zprávu WM_DESTROY, při níž musíme zajistit, aby po zrušení hlavního okna byla ukončena smyčka zpráv a tím celá aplikace.

S využitím naší knihovny je pak celý kód aplikace velmi jednoduchý  – pro zjednodušení jsem v případě takto jednoduchého kódu vše umístil do jediného zdrojového souboru, ve kterém je vstupní funkce WinMain:

#include "stdafx.h"

class okno_hlavni : public winapi::okno_impl
{
private:
	const wchar_t* trida() const throw()
	{
		return L"moje_okno";
	}

private:
	LRESULT window_proc(UINT zprava, WPARAM wparam, LPARAM lparam) throw()
	{
		switch (zprava)
		{
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		}
		return DefWindowProc(m_hwnd, zprava, wparam, lparam);
	}
};

__declspec(selectany) okno_hlavni _okno_hlavni;


int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int) throw()
{
	MSG msg;
	_hinstance = hInstance;
	_okno_hlavni.vytvorit();
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;
}

V dalších pokračováních si vytvoříme další třídy, které nám výrazně zjednoduší programování ve WinAPI a také se dostaneme k některým technikám, na které se programátoři ptají v různých diskusních fórech.

Zde je ukázkový projekt (ve Visual Studiu 2008 Professional) a  kód knihovny.

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

3 názory  —  3 nové  
Hlasování bylo ukončeno    
15 hlasů
Google
(fotka) Radek ChalupaRadek je na volné noze jako vývojář software, konzultant a lektor v oblasti vývoje software.
Web     Twitter    

Nové články

Obrázek ke článku Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Touto roční dobou, kdy je zem pokrytá barevným listím a prsty křehnou v mrazivých ránech, se obvykle těšíme na zbrusu novou verzi RAD Studia. Letos si však ale budeme muset počkat na Godzillu a Linux až do jara. Vezměme tedy za vděk alespoň updatem 2 a jelikož dle vyjádření pánů z Embarcadero se budou nové věci objevovat průběžně, pojďme se na to tedy podívat.

Reklama
Reklama
Obrázek ke článku Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Stále rostoucí zájem o cloudové služby i maximální důraz na pružnost, spolehlivost a bezpečnost IT vedou k výrazným inovacím v datových centrech. V infrastruktuře datových center hraje stále významnější roli software a stále častěji se lze setkat s hybridními přístupy k jejich budování i provozu.

Obrázek ke článku Konference: Mobilní technologie mají velký potenciál pro byznys

Konference: Mobilní technologie mají velký potenciál pro byznys

Firmy by se podle analytiků společnosti Gartner měly  rychle přizpůsobit skutečnosti, že mobilní technologie už zdaleka nejsou horkou novinkou, ale standardní součástí byznysu. I přesto - nebo možná právě proto - tu nabízejí velký potenciál. Kde tedy jsou ty největší příležitosti? I tomu se bude věnovat již čtvrtý ročník úspěšné konference Mobilní řešení pro business.

Obrázek ke článku Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres HCPP16 pořádá od 30. září do 2. října nezisková organizace Paralelní Polis již potřetí, a to ve stejnojmenném bitcoinovém prostoru v pražských Holešovicích. Letos přiveze na třídenní konferenci přes 40 většinou zahraničních speakerů – lídrů z oblastí technologií, decentralizované ekonomiky, politických umění a aktivismu. Náměty jejich přednášek budou také hacking, kryptoměny, věda, svoboda nebo kryptoanarchie.

loadingtransparent (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })();
Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý