Быстрый старт. Как не бояться Win API или «Нельзя просто так взять, и начать программировать»

в 3:51, , рубрики: c++, windows, Песочница

Вам интересно, как же работают все эти чудесные программы(и игры)!

Как они переключаются в полноэкранный режим и что дальше происходит?

Это будет интересно в первую очередь тем, кто никогда не задумывался как написать «что-то», чтобы оно работало в точности как наши любимые программы.

  • если есть опыт программирования на других языках(java, phpm javascript, ecmascript)
  • если есть опыт использования графических приблуд типа 3dSMax
  • если вы участвовали в game-проектах но никогда не лезли в нутряшки

Автор — перед написанием статьи занимался html-версткой и php.

Краткий экскурс будет достаточно туманным.

Итак. Как работает WIN? Есть ОС, есть драйверы, есть процессор, есть память.

На самом деле, при написании программ мы работаем лишь с памятью. Иными словами процессор не умеет рисовать пиксели на экране, он умеет лишь менять содержимое ячеек(а если совсем правильно то блоков/страниц) в памяти. Иногда процессор также посылает устройствам ввода-вывода через систему BIOS команды которые могут быть ими восприняты. На одну ножку подается напряжение — внутри меняется состояние транзисторов. Вроде бы все просто и очевидно.

Теперь рассмотрим как ОС мешает нам в достижении целей:

  1. ОС ограничивает и управляет ресурсами устройств ввода-вывода
  2. ОС если ей что-то не нравится смело убьет наш процесс

А что насчет помощи?

  1. Программа под ОС не тормозит выполнение фоновых программ
  2. ОС абстрагирует пользователя от бинарных данных
Процессы

Чтобы не тормозить выполнение фоновых программ ОС резервирует для программы некоторый объем памяти и выделяет в планировщике некий период процессорного времени. Таким образом процесс — это Ваша программа. Она оперирует только своей памятью и только своим процессором. Процессы это детище *nix.

Потоки

Чтобы программа могла обеспечивать внутри ресурсов процесса разные действия процесс может быть разбит на под-процессы(потоки). При этом любой из потоков может оперировать полной памятью процесса или временем выделенным процессу ЦП. Т.е. потоки по сути сами разбираются — что и когда им делать.
Таким образом, также как процессы являются абстракцией аппаратной связки «память+процессор» — так и потоки являются абстракцией программной связки «память процесса+время ЦП выделенного процессу»
Продолжать можно до бесконечности, но реально дальше абстракции вида Железо->процесс->поток пока не доходило.
С точки зрения *nix ОС является процессом и программы являются процессом, но с указателем на тот процесс из которого эти программы были вызваны(стандарт POSIX). С точки зрения Win — ОС является процессом, а все программы в ней являются одним или несколькими потоками.

пользователь: — Глупости, ведь в таск-менеджере Win мы видим процессы а не потоки!

Еще раз перечитываем то что написано выше — любой процесс это виртуализация железячной памяти, т.е. это объем памяти который может использоваться им, им, и еще раз только им и никем еще!

Чтобы программы обменивались данными они должны либо:

  • Linux: открыть сокет
  • Win: быть потоками одного главного процесса

Таким образом любой процесс в Win является потоком. И все действия программ Win можно рассматривать с точки зрения потоков.
Потоки взаимодействуют между собой посредством сообщений.
Итого имеет вашу программу. В Win это выглядит так:
Процесс: Система ->
1. Поток Система
2. Поток Программа

Программа взаимодействует с системой посредством обмена сообщениями. Сообщения — это куски памяти которые ОС распределяет между потоками. Бесспорно код Win закрыт, и мы можем лишь гадать как все это внутри происходит. Не потому что это плохо и от нас что-то скрывают, а потому что нам, если честно, не очень то и нужно знать в какие регистры процессора что там и когда перемещается.

Переходим к практике

качаем Netbeans для С/С++. По инструкции устанавливает среду для компиляции(компилятор). Я выбрал первый вариант — cygwin(gcc и пара либ).
У меня уже был установлен Netbeans для php, так что я через меню расширения просто доставил туда оба плагинчика которые позиционировались как что-то для C/C++.
Открыл программу начал писать код а-ля С и бабах — никаких автоподсказок, никакого автодополнения разьве что синтаксис подсвечивался вроде как корректно. Недолго думая скачал дистр Netbeans для C/C++ — он поставился в ту же папочку и после перезапуска автоподсказки заработали.

После этого 2 дня ушло на поиск литературы — MSDN пользовался примерно 10% времени, т.к. там сейчас вообще не очень то практикуется юзабельная информация по С/С++ WinAPI. Конечно ведь уже есть C#, WPF, Win8 и т.д.

На всё про всё изучение ушло около двух недель. В него вошли 5 видеокурсов по C/C++, 2 видеокурса по ОС,
Книги:

  • K&R в 3 издании
  • Прата С. — Язык программирования С++
  • Литвиненко Н.А. — Технология программирования на С++
  • Ф.Хилл 'OpenGL. Программирование компьютерной графики
  • Ю.Щупак. Win32 API. Эффективная разработка приложений
  • Гальченко В.Г. — Системное программирование в среде WIN32
  • Сучкова Л.И. — Win32 API. Основы программирования

Из которых использовались только в, общем то, первые две и видеокурсы.

То что удалось почерпнуть из этой чудесной экранной- и видое- макулатуры:

  • WinAPI имеет свою отдельную точку входа WinMain вместо main
  • вызовы функций WinAPI работает по протоколу __stdcall (аргументы функции размещаются в стеке, причем справа-налево)
  • программа обычно представлена главным окном и уже потенциально прочими которые от нее зависят
  • программа имеет большое количество кодов системных сообщений, через которые поток программы и работает с потоком ОС
  • код 99% примеров из книг по winAPI невменяем и абсолютно не соответствует тому что мы бы писали на С++
  • всегда есть некая «оконная функция» которая обрабатывает системные сообщения

Ну а вот классический пример базовой полноэкранной программы WinAPI:


#include <windows.h>
#include "windowsx.h"
HINSTANCE g_hInst = NULL;
HWND g_hWnd = NULL;

HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow); // Создание окна
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Функция окна
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
if (FAILED(InitWindow(hInstance, nCmdShow)))
return 0;
// Главный цикл сообщений
MSG msg = {0};
while (WM_QUIT != msg.message) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} else // Если сообщений нет
{
}
}
return (int) msg.wParam;
}

HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow) {
WNDCLASSEX wcex;
memset(&wcex,0,sizeof(WNDCLASSEX));
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = GetStockBrush(BLACK_BRUSH);
wcex.lpszClassName = "FS_window";
wcex.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
if (!RegisterClassEx(&wcex)) {
return E_FAIL;
}
// Создание окна
g_hInst = hInstance;
RECT rc = {0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
g_hWnd = CreateWindow(wcex.lpszClassName, "FS test", WS_POPUP | WS_MAXIMIZE, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL);
if (!g_hWnd) {
return E_FAIL;
}
ShowWindow(g_hWnd, nCmdShow);
return S_OK;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hdc;
switch (message) {
case WM_SIZE:
InvalidateRect(hWnd,NULL,true);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
SetTextColor(hdc, SetPixel(hdc,1,1,RGB(0,255,0)));
SetBkColor(hdc, SetPixel(hdc,1,1,RGB(0,0,0)));
TextOutA(hdc, GetSystemMetrics(SM_CXSCREEN)/2, GetSystemMetrics(SM_CYSCREEN)/2, "Simple text", strlen("Simple text"));
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

Вы тоже ничего не поняли?
Вот и я ничего… Все дальнейшие действия для меня свелись к распутыванию этого чудо-г**о-кода:

— Все мы здесь не в своём уме — и ты, и я! -Откуда вы знаете, что я не в своём уме? — спросила Алиса.

в WinAPI используется венгерская нотация. Это значит что первые пара букв в переменной обычно указывают на тип. Очень хорошая таблица есть в книге Ю.Щупака( Win32 API. Эффективная разработка приложений) на с. 29.
Дальше началось шаманство с макросами. И в дальнейшем Вы увидите, что их распутывание, хоть и является нетривиальной задачей — но вполне возможно. И позволяет вместо подключения windows.h писать тот код-который вы сами хотите и вызывать те функции которые сами хотите.

Первым открытие посреди компиляции стало то что объявлять свои функции для использования winAPI лучше в блоке extern C, дабы С++ компилятор не пытался их оптимизировать и заменять непонятно-на-что. Без этого просто программа не заработает.
Итак:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

по сути является ничем иным как

int __stdcall WinMain(void * hInstance, void * hPrevInstance, char * lpCmdLine, int nCmdShow) {

Далее:
WNDCLASSEX wcex;
memset(&wcex,0,sizeof(WNDCLASSEX));
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = GetStockBrush(BLACK_BRUSH);
wcex.lpszClassName = "FS_window";
wcex.hIconSm = LoadIcon(NULL, IDI_WINLOGO);

по сути является ничем иным как

struct {
unsigned int cbSize;
unsigned int style;
long (__stdcall *lpfnWndProc)(void *, unsigned int, unsigned int, long);
int xtraBytesCls;
int xtraBytesAfterWndInst;
void * inst;
void * icon;
void * cursor;
void * brushBG;
const char * menuName;
const char * className;
void * smallIcon;
} wndCl = {
sizeof (wndCl),
1 | 2,
WndProc,
0,
0,
thisInst,
NULL,
NULL,
NULL,
NULL,
"FS_window",
NULL
};

Чудо-функция CreateWindow является ничем иным чем оберткой над нормальной нотацией системной функции
void * __stdcall CreateWindowExA(unsigned long dwExStyle, const char *, const char *, unsigned long, int x, int y, int w, int h, void *, void *, void *, void *);

ShowWindow при этом на самом деле есть:
int __stdcall ShowWindow(void *, int);

DefWindowProc есть не что иное как обертка над
long __stdcall DefWindowProcA(void *, unsigned int msg, unsigned int data, long data2);

Продолжать можно до бесконечности пока не переберем весь windows.h
Как вы уже поняли — Win API не является тайной «в черном ящике» — ей является лишь эта самая реализация __stdcall данных функций.

И вот финальный пример того, что было бы понятно любому С++ программисту при создании окошка:

#include <iostream>

typedef struct MSG {
void * hwnd;
unsigned int message;
unsigned int wParam;
long lParam;
unsigned long time;

struct {
long x;
long y;
} pt;
};

typedef struct PAINTSTRUCT {
void * hdc;
int fErase;
struct tagRECT {
long left;
long top;
long right;
long bottom;
} rcPaint;
int fRestore;
int fIncUpdate;
unsigned char rgbReserved[32];
};

typedef struct RECT {
long left;
long top;
long right;
long bottom;
};

extern "C" {
int __stdcall WinMain(void *, void *, char *, int);
long __stdcall WndProc(void *, unsigned int, unsigned int, long);
unsigned short __stdcall RegisterClassExA(const void *);
void * __stdcall CreateWindowExA(unsigned long dwExStyle, const char *, const char *, unsigned long, int x, int y, int w, int h, void *, void *, void *, void *);
int __stdcall ShowWindow(void *, int);
long __stdcall DefWindowProcA(void *, unsigned int msg, unsigned int data, long data2);
int __stdcall GetMessageA(MSG *lpMsg, void * hWnd, unsigned int wMsgFilterMin, unsigned int wMsgFilterMax);
long __stdcall DispatchMessageA(const MSG *lpMsg);
void * __stdcall BeginPaint(void *, PAINTSTRUCT *pPs);
int __stdcall EndPaint(void *, const PAINTSTRUCT *pPs );
int __stdcall GetClientRect(void *, RECT *pRect);
int __stdcall DrawTextA(void *, const char *, int txt, RECT *, unsigned int format);
}

int __stdcall WinMain(void * thisInst, void * prevInst, char * cmd, int wndShowType) {

struct {
unsigned int cbSize;
unsigned int style;
long (__stdcall *lpfnWndProc)(void *, unsigned int, unsigned int, long);
int xtraBytesCls;
int xtraBytesAfterWndInst;
void * inst;
void * icon;
void * cursor;
void * brushBG;
const char * menuName;
const char * className;
void * smallIcon;
} wndCl = {
sizeof (wndCl),
1 | 2,
WndProc,
0,
0,
thisInst,
NULL,
NULL,
NULL,
NULL,
"myClass",
NULL
};
RegisterClassExA(&wndCl);
void * wndHandle = CreateWindowExA(0, "myClass", "title of wnd", 0, 10, 10, 500, 500, NULL, NULL, thisInst, NULL);
ShowWindow(wndHandle, wndShowType);
MSG msg;
while (GetMessageA(&msg, NULL, 0, 0)) {
DispatchMessageA(&msg);
}
return msg.wParam;
}

long WndProc(void * wndDescr, unsigned int code, unsigned int data, long data2) {
switch (code) {
case 0x000F:
PAINTSTRUCT ps;
RECT rect;
BeginPaint(wndDescr, &ps);
GetClientRect(wndDescr, &rect);
DrawTextA(ps.hdc, static_cast<char *>(wndDescr), -1, &rect, 32 | 1 | 4);
EndPaint(wndDescr, &ps);
return 0;
}
return DefWindowProcA(wndDescr, code, data, data2);
}

Этот код создает окошко без подключения всяких там windows.h
Таким образом, все что требуется от программиста это не тупое заучивание чудо-макросов WinAPI, а умением на основе библотечных(_stdcall) функций winAPI строить правильное ООП — но никак не копирование жутко-кода из windows.h(& Ko) макросов в своих проектах!

Всем спасибо, все свободны. Microsoft задуматься над рефакторингом WinAPI перед выбросом в массы очередных чудесных «WPF»

Автор: Arks

Источник

Поделиться

* - обязательные к заполнению поля