Вам интересно, как же работают все эти чудесные программы(и игры)!
Как они переключаются в полноэкранный режим и что дальше происходит?
Это будет интересно в первую очередь тем, кто никогда не задумывался как написать «что-то», чтобы оно работало в точности как наши любимые программы.
- если есть опыт программирования на других языках(java, phpm javascript, ecmascript)
- если есть опыт использования графических приблуд типа 3dSMax
- если вы участвовали в game-проектах но никогда не лезли в нутряшки
Автор — перед написанием статьи занимался html-версткой и php.
Краткий экскурс будет достаточно туманным.
Итак. Как работает WIN? Есть ОС, есть драйверы, есть процессор, есть память.
На самом деле, при написании программ мы работаем лишь с памятью. Иными словами процессор не умеет рисовать пиксели на экране, он умеет лишь менять содержимое ячеек(а если совсем правильно то блоков/страниц) в памяти. Иногда процессор также посылает устройствам ввода-вывода через систему BIOS команды которые могут быть ими восприняты. На одну ножку подается напряжение — внутри меняется состояние транзисторов. Вроде бы все просто и очевидно.
Теперь рассмотрим как ОС мешает нам в достижении целей:
- ОС ограничивает и управляет ресурсами устройств ввода-вывода
- ОС если ей что-то не нравится смело убьет наш процесс
А что насчет помощи?
- Программа под ОС не тормозит выполнение фоновых программ
- ОС абстрагирует пользователя от бинарных данных
Процессы
Чтобы не тормозить выполнение фоновых программ ОС резервирует для программы некоторый объем памяти и выделяет в планировщике некий период процессорного времени. Таким образом процесс — это Ваша программа. Она оперирует только своей памятью и только своим процессором. Процессы это детище *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