Исходные тексты любого приложения windows

#include
«stdafx.h»

#include
<windows.h>

#include
<commctrl.h>

#include
<math.h>

LRESULT
CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

LRESULT
CALLBACK DiagramProc(HWND, UINT, WPARAM, LPARAM);

LRESULT
CALLBACK DiagramProc0(HWND, UINT, WPARAM, LPARAM);

DWORD
WINAPI ThreadProcDraw(LPVOID param);

int
power(int
x,int
y);

void
demo();

bool
scale(void);

int
start();

int
ConnectToCOM(HANDLE port);

int
Get_Res(HANDLE port,unsigned
char
* res);

int
pRead(HANDLE port,unsigned
char
* c);

int
pWrite(HANDLE port,unsigned
char
c);

#define
ID_Mode 15

#define
ID_Port 55

#define
ID_START 140

#define
ID_KILL 666

#define
ID_DEMO 500

#define
ID_Samples 1000

#define
ID_tmax 1001

#define
ID_Umax 1002

HINSTANCE
hInst_hWnd, hInst1, hInst0;

HWND
hWnd, hGrWnd1, hGrWnd;

HWND
hCombo, hCombo2, hComboCOM, startbut, exitbut, demobut, textbFreq,
textbSample, Scale_t, Scale_U;

char
ClassName[]=»Window»;

char
AppTitle[]=»Grapher»;

int
i = 0;

int
height, width;

int
graph_xsize = 340, graph_ysize = 290;

int
dheight, dwidth, graph=0;

WORD
buffer[32768];

BYTE
Result[4000];

int
Uscale=2200, Tscale=200, samp=10000, freq = 100;

char
Uscalech[]=»2200″,
Tscalech[]=»200″,
sampch[]=»1024″,
freqch[] = «5»;

WORD
EditULength = 4, EditTLength = 3, EditsampLength = 4, EditfreqLength
= 1;

CHAR
EditTLine[16], EditULine[16], EditsampLine[16], EditfreqLine[16];

bool
IfData, flagCOM = false;

char
ModeADC;

HANDLE
DataProcess_Thread, COM;

int
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int
nCmdShow){

MSG
msg;

WNDCLASS
wc;

hInst_hWnd
= hInstance;

//регистрация
настроек главного окна

memset(&wc,
0, sizeof(wc));

wc.lpszClassName
= ClassName;

wc.lpfnWndProc
= (WNDPROC)WndProc;

wc.style
= CS_HREDRAW|CS_VREDRAW;

wc.hInstance
= hInst_hWnd;

wc.hIcon
= LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor=LoadCursor(NULL,IDC_ARROW);

wc.hbrBackground
= CreateSolidBrush(RGB(140,160,191));

wc.lpszMenuName=NULL;

wc.cbClsExtra=0;

wc.cbWndExtra=0;

RegisterClass(&wc);

//создание
главного
окна

hWnd
= CreateWindow(ClassName,
AppTitle,WS_BORDER|WS_MINIMIZEBOX|WS_SYSMENU,120,120,640,430,NULL,NULL,hInst_hWnd,
NULL);

if(!hWnd) //если
окно не было создано

{

MessageBox(NULL,»Create:error»,AppTitle,MB_OK|MB_ICONSTOP);//сообщение
об
ошибке

return
FALSE;

}

ShowWindow(hWnd,
nCmdShow);//показ
окна

UpdateWindow(hWnd);//обновление
окна

while(GetMessage(&msg,
NULL, 0, 0))//бесконечный
цикл
обработки
событий

{

graph
= 1;

TranslateMessage(&msg);//перевод
сообщения виртуальных клавиш в символьные
сообщения.

DispatchMessage(&msg);//доставка
сообщения, извлеченного функцией
GetMessage

graph
= 0;

}

return
msg.wParam;

}

LRESULT
CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{ //функция
обработки событий главного окна

bool
error_scale; //флаг
ошибки введенных значений масштаба

switch(msg)

{

case
WM_COMMAND: //используется
для отслеживания нажатий на кнопки
«Start», «Demo» и «Exit»

if
(LOWORD(wParam) == ID_START){ //
если
нажата
кнопка
«Start»

error_scale
= scale(); //
проверка правильности введенного
масштаба

if
(error_scale == 0) //
если при ввводе была допущена ошибка

break; //
выход

start(); //
функция, которая считывает введенные
настройки, проверяет их,

//
создает подключение к COM-порту, проверяет,
готов ли порт к получению данных,

//
создает поток для рисования графика.

}

if
(LOWORD(wParam) == ID_KILL){ //
если нажата кнопка «Exit»

DestroyWindow(hWnd); //
уничтожение главного окна

PostQuitMessage(0); //
отправка сообщения выхода

break; //
выход

}

if
(LOWORD(wParam) == ID_DEMO){ //
если нажата кнопка «Demo»

error_scale
= scale(); //
проверка правильности введенного
масштаба

if
(error_scale == 0) //
если при ввводе была допущена ошибка

break; //
выход

demo(); //
функция заполнения буфера значениями
по умолчанию

}

break;

case
WM_PAINT: //
используется, когда необходимо
перерисовать графические элементы в
окне

DefWindowProc(hWnd,
msg, wParam, lParam); //
обеспечивает обработку по умолчанию
любого сообщения окна,

//
которые приложение не обрабатывает

break;

case
WM_SIZE: //
используется при изменении размеров
окна

width
= LOWORD(lParam);

height
= HIWORD(lParam);

break;

case
WM_SETFOCUS: //
используется окном, когда оно получило
фокус клавиатуры.

UpdateWindow(hWnd); //
обновление окна

break;

case
WM_DESTROY: //
при закрытии окна

DestroyWindow(hWnd); //
уничтожение главного окна

PostQuitMessage(0); //
отправка сообщения выхода

break;

case
WM_CREATE: //
принимает это сообщение после того, как
окно создано, но до того, как окно
становится видимым.

//регистрация
класса дочернего окна

WNDCLASS
wc0;

memset(&wc0,
0, sizeof(WNDCLASS));

wc0.lpfnWndProc
= DiagramProc0;

wc0.hInstance
= hInst_hWnd;

wc0.lpszClassName
= «ChildWClass0»;

wc0.hbrBackground
= CreateSolidBrush( RGB( 255,255,255 ) );

wc0.hCursor=LoadCursor(NULL,IDC_ARROW);

RegisterClass(&wc0);

WNDCLASS
Gwc;

memset(&Gwc,0,sizeof(WNDCLASS));

Gwc.lpfnWndProc
= DiagramProc;

Gwc.hInstance
= hInst_hWnd;

Gwc.lpszClassName
= «ChildWClass»;

Gwc.hbrBackground
= CreateSolidBrush( RGB( 255,255,255 ) );

Gwc.style
= CS_HREDRAW|CS_VREDRAW;

Gwc.hCursor=LoadCursor(NULL,IDC_ARROW);

Gwc.lpszMenuName=NULL;

Gwc.cbClsExtra=0;

Gwc.cbWndExtra=0;

RegisterClass(&Gwc);

//
построение
окна
графика

hGrWnd1
= CreateWindowEx(0,»ChildWClass0″,(LPCTSTR)
NULL,WS_CHILD | WS_VISIBLE ,180, 15, 440, 370, hWnd, NULL, hInst0,
NULL);

hGrWnd
= CreateWindowEx(0,»ChildWClass»,(LPCTSTR)
NULL,WS_CHILD | WS_VISIBLE,50, 55, graph_xsize, graph_ysize, hGrWnd1,
NULL, hInst_hWnd, NULL);

//
построение
дочерних
окон
выбора
масштаба

CreateWindow(«STATIC»,»Scale»,WS_CHILD|WS_VISIBLE,70,15,40,18,
hWnd, 0,0,0);

CreateWindow(«STATIC»,»tmax:»,WS_CHILD|WS_VISIBLE,15,38,75,18,
hWnd, 0,0,0);

Scale_t
= CreateWindow(«EDIT»,Tscalech,WS_CHILD|WS_VISIBLE,95,38,77,18,
hWnd,(HMENU)ID_tmax,0,0);

CreateWindow(«STATIC»,»Umax:»,WS_CHILD|WS_VISIBLE,15,61,75,18,
hWnd, 0,0,0);

Scale_U
= CreateWindow(«EDIT»,Uscalech,WS_CHILD|WS_VISIBLE,95,61,77,18,
hWnd,(HMENU)ID_Umax,0,0);

//
построение дочерних окон настройки
конфигурации выборки

CreateWindow(«STATIC»,»Configuration
samples»,WS_CHILD|WS_VISIBLE,19,104,148,18,
hWnd, 0,0,0);

CreateWindow(«STATIC»,»Frequency:»,WS_CHILD|WS_VISIBLE,15,127,75,18,
hWnd, 0,0,0);

textbFreq=CreateWindow(«EDIT»,freqch,WS_CHILD|WS_VISIBLE,95,127,77,18,
hWnd,0,0,0);

CreateWindow(«STATIC»,»Samples:»,WS_CHILD|WS_VISIBLE,15,150,75,18,
hWnd, 0,hInst_hWnd,0);

textbSample=CreateWindow(«EDIT»,sampch,WS_CHILD|WS_VISIBLE,95,150,77,18,
hWnd,(HMENU)ID_Samples,hInst_hWnd,0);

//
построение
кнопки
«Demo»

demobut=CreateWindow(«BUTTON»,»Demo»,WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,15,193,157,18,
hWnd,(HMENU)ID_DEMO,hInst_hWnd,0);

//
построение дочерних окон настройки МК
и СOM-порта

CreateWindow(«STATIC»,»Configuration
ADC»,WS_CHILD|WS_VISIBLE,33,237,120,18,
hWnd, 0,0,0);

CreateWindow(«STATIC»,»Port:»,WS_CHILD|WS_VISIBLE,15,262,75,21,
hWnd, 0,0,0);

hComboCOM
=
CreateWindow(WC_COMBOBOX,0,WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST,95,260,77,100,
hWnd,(HMENU)ID_Port,hInst_hWnd,0);

SendMessage(hComboCOM,CB_ADDSTRING,0,(LPARAM)»Com1″);

SendMessage(hComboCOM,CB_ADDSTRING,0,(LPARAM)»Com2″);

SendMessage(hComboCOM,CB_ADDSTRING,0,(LPARAM)»Com3″);

SendMessage(hComboCOM,CB_SETCURSEL,(WPARAM)2,0);

CreateWindow(«STATIC»,»Mode:»,WS_CHILD|WS_VISIBLE,15,290,75,21,
hWnd, 0,0,0);

hCombo
=
CreateWindow(WC_COMBOBOX,0,WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST,95,288,77,100,
hWnd,(HMENU)ID_Mode,hInst_hWnd,0);

SendMessage(hCombo,CB_ADDSTRING,0,(LPARAM)»ADC
0″);

SendMessage(hCombo,CB_ADDSTRING,0,(LPARAM)»ADC
1″);

SendMessage(hCombo,CB_ADDSTRING,0,(LPARAM)»ADC
0 Diff.»);

SendMessage(hCombo,CB_SETCURSEL,(WPARAM)0,0);

//
построение кнопки «Start» и «Exit»

startbut=CreateWindow(«BUTTON»,»Start»,WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,15,336,157,18,
hWnd,(HMENU)ID_START,hInst_hWnd,0);

exitbut=CreateWindow(«BUTTON»,»Exit»,WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,15,370,70,18,
hWnd,(HMENU)ID_KILL,hInst_hWnd,0);

InvalidateRect(hGrWnd,
NULL, true); //
перерисовка
окна
графика

SendMessage(hGrWnd,
WM_PAINT, NULL, NULL); //
отправка
сообщения
для
перерисовки

break;

default:

return
DefWindowProc(hWnd, msg, wParam, lParam);//
обеспечивает
обработку
по
умолчанию
любого
сообщения
окна,

//
которые приложение не обрабатывает

}

return
0l;

}

void
demo(){//функция
записи в буффер демонстрационного
варианта графика

graph=1;

for(i
= 0; i < samp; i++)

buffer[i]
= fabs(2200 * sin((double)i/30)); //запись
в буффер демонстрационного варианта
графика

InvalidateRect(hGrWnd,
NULL, true);

InvalidateRect(hGrWnd1,
NULL, true); //
перерисовка окна графика из любого
места программы

SendMessage(hGrWnd1,
WM_PAINT, NULL, NULL);

SendMessage(hGrWnd,
WM_PAINT, NULL, NULL); //
отправка
сообщения
для
перерисовки

}

LRESULT
CALLBACK DiagramProc(HWND hGrWndtmp, UINT msg, WPARAM wParam, LPARAM
lParam)

{//обработка
событий дочернего окна построения
графика

HDC
hDC;

PAINTSTRUCT
ps;

HPEN
hPen;

switch(msg){

case
WM_PAINT: //
используется, когда необходимо
перерисовать графические элементы в
окне

hDC=BeginPaint(hGrWndtmp,&ps);

//оси
координат

MoveToEx(hDC,1,
0,0);

LineTo(hDC,
1, dheight-2);

LineTo(hDC,
dwidth, dheight-2);

LineTo(hDC,
dwidth, dheight-1);

LineTo(hDC,
0, dheight-1);

LineTo(hDC,
0, 0);

//линии
сетки

hPen
= CreatePen(0,1,RGB(140,160,191));

SelectObject(hDC,hPen);

for(int
i = 1; i < 10; i++){

MoveToEx(hDC,1,graph_ysize*i/10,NULL);

LineTo(hDC,graph_xsize,graph_ysize*i/10);

MoveToEx(hDC,graph_xsize*i/10,0,NULL);

LineTo(hDC,graph_xsize*i/10,graph_ysize-1);

if
(i%2 == 0){

MoveToEx(hDC,1,graph_ysize*i/10+1,NULL);

LineTo(hDC,graph_xsize,graph_ysize*i/10+1);

MoveToEx(hDC,graph_xsize*i/10+1,0,NULL);

LineTo(hDC,graph_xsize*i/10+1,graph_ysize-1);

}

}

hPen
= CreatePen(0,1,RGB(0,0,0));

SelectObject(hDC,hPen);

//линии
отметки масштаба на осях графика

MoveToEx(hDC,0,0,NULL); LineTo(hDC,5,0);

MoveToEx(hDC,0,1,NULL); LineTo(hDC,5,1);

MoveToEx(hDC,graph_xsize-2,graph_ysize,NULL); LineTo(hDC,graph_xsize-2,graph_ysize-6);

MoveToEx(hDC,graph_xsize-1,graph_ysize,NULL); LineTo(hDC,graph_xsize-1,graph_ysize-6);

//рисование
графика

if(graph
== 1){

double
U, T = 0, tmp = 0;

graph=0;

for
(double
i = 0; i < samp; i++){

T
= i/freq;

if
(T*graph_xsize/Tscale > dwidth)

i
= samp;

else
if
(abs(T-tmp)>=1){

U
= buffer[(int)tmp];

MoveToEx(hDC,tmp*graph_xsize/Tscale,-U*graph_ysize/Uscale
+ dheight,0);

U
= buffer[(int)T];

LineTo(hDC,
T*graph_xsize/Tscale, -U*graph_ysize/Uscale + dheight);

tmp
= T;

}

}

}

EndPaint(hGrWndtmp,&ps);

break;

case
WM_SIZE: //
используется при изменении размеров
окна

dwidth
= LOWORD(lParam);

dheight
= HIWORD(lParam);

break;

case
WM_DESTROY: //
при
закрытии
окна

PostQuitMessage(0); //
отправка сообщения выхода

break;

default:

return
DefWindowProc(hGrWndtmp, msg, wParam, lParam);

}

return
0l;

}

LRESULT
CALLBACK DiagramProc0(HWND hGrWndtmp, UINT msg, WPARAM wParam, LPARAM
lParam)

{ //дочернее
окно подписи осей координат графика

HDC
hDc;

PAINTSTRUCT
ps;

switch(msg){

case
WM_PAINT: //
используется, когда необходимо
перерисовать графические элементы в
окне

hDc
= BeginPaint(hGrWndtmp,&ps);

//заголовок
графика

TextOut(hDc,graph_xsize/2-15,0,(LPCTSTR)»Current
configuration»,21);

TextOut(hDc,graph_xsize/2-50,18,(LPCTSTR)»Frequency:»,10);

TextOut(hDc,graph_xsize/2+25,18,(LPCTSTR)freqch,EditfreqLength);

TextOut(hDc,graph_xsize/2+85,18,(LPCTSTR)»Samples:»,8);

TextOut(hDc,graph_xsize/2+150,18,(LPCTSTR)sampch,EditsampLength);

//линии
отметки
масштаба
по
U

MoveToEx(hDc,51,55,NULL);
LineTo(hDc,51,50);

MoveToEx(hDc,50,55,NULL);
LineTo(hDc,50,50);

MoveToEx(hDc,47,55,NULL);
LineTo(hDc,57,55);

MoveToEx(hDc,47,56,NULL);
LineTo(hDc,57,56);

//подпись
выбранного масштаба U на графике

TextOut(hDc,30-7*(EditULength-1),47,(LPCTSTR)Uscalech,EditULength);

TextOut(hDc,45,35,(LPCTSTR)»U,
mV»,5);

//линии
отметки
масштаба
по
t

MoveToEx(hDc,50+graph_xsize,53+graph_ysize,NULL);
LineTo(hDc,55+graph_xsize,53+graph_ysize);

MoveToEx(hDc,50+graph_xsize,54+graph_ysize,NULL);
LineTo(hDc,55+graph_xsize,54+graph_ysize);

MoveToEx(hDc,48+graph_xsize,54+graph_ysize,NULL);
LineTo(hDc,48+graph_xsize,59+graph_ysize);

MoveToEx(hDc,49+graph_xsize,54+graph_ysize,NULL);
LineTo(hDc,49+graph_xsize,59+graph_ysize);

//подпись
выбранного масштаба t на графике

TextOut(hDc,graph_xsize+50-15,350,(LPCTSTR)Tscalech,EditTLength);

TextOut(hDc,400,336,(LPCTSTR)»t,
sec»,6);

//подпись
начала координат на графике

TextOut(hDc,35,350,(LPCTSTR)»0″,1);

EndPaint(hGrWndtmp,&ps);

break;

case
WM_DESTROY: //
при
закрытии
окна

PostQuitMessage(0); //
отправка сообщения выхода

break;

default:

return
DefWindowProc(hGrWndtmp, msg, wParam, lParam);

}

return
0l;

}

int
power(int
x,int
y) //x
в
степени
y

{

int
rez;

rez
= 1;

for(int
i=0;i<y;i++)

rez
= rez*x;

return
rez;

}

bool
scale(void){//функция
проверки выбранных настроек масштаба

int
a;

Uscale=0;

EditULength
=
(WORD)SendMessage(Scale_U,EM_LINELENGTH,(WPARAM)0,(LPARAM)0); //сохранение
выбранного
масштаба
по
оси
U

if
(EditULength==0){ //если
ничего не введено

MessageBox(hWnd,
TEXT(«Проверьте
настройки масштаба по U»),
TEXT(«Ошибка»),
MB_OK); //сообщение
об ошибке

return
0;

}

*((LPWORD)EditULine)
= EditULength;

SendMessage(Scale_U,EM_GETLINE,(WPARAM)0,(LPARAM)EditULine);

for(int
i=0;i<EditULength;i++){ //преобразование
строки в число

Uscalech[i]
= EditULine[i];

a
= EditULine[EditULength-1-i]; //set
current symbol

if
(a == ‘.’
|| a == ‘,’){//если
строка содержит какие-либо символы,
кроме цифр

MessageBox(hWnd,
TEXT(«Проверьте
настройки масштаба. U должно быть
целое.»),
TEXT(«Ошибка»),
MB_OK); //сообщение
об ошибке

return
0;

}

if
(a < ‘0’
|| a > ‘9’){//если
строка содержит какие-либо символы,
кроме цифр

MessageBox(hWnd,
TEXT(«Проверьте
настройки масштаба. U должно содержать
только цифры»),
TEXT(«Ошибка»),
MB_OK); //сообщение
об ошибке

return
0;

}

if
(a>=48) a=a-48; //get
integer by substracting ASCII code

Uscale
+= a*power(10,i); //Sum
with value in right order

}

if(Uscale==0)
{//если
выбран масштаб 0 по оси U

MessageBox(hWnd,
TEXT(«Проверьте
настройки масштаба. Напряжение U не
может быть равно 0.»),
TEXT(«Ошибка»),
MB_OK); //сообщение
об ошибке

return
0;

}

samp
= 0;

EditsampLength
= (WORD)SendMessage(textbSample,EM_LINELENGTH,(WPARAM)0,(LPARAM)0);

if
(EditsampLength==0){

MessageBox(hWnd,
TEXT(«Проверьте
настройки
количества
точек
выборки»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

*((LPWORD)EditsampLine)
= EditsampLength;

SendMessage(textbSample,EM_GETLINE,(WPARAM)0,(LPARAM)EditsampLine);

for(int
i=0;i<EditsampLength;i++){

sampch[i]
= EditsampLine[i];

a
= EditsampLine[EditsampLength-1-i]; //set
current symbol

if
(a == ‘.’
|| a == ‘,’){

MessageBox(hWnd,
TEXT(«Проверьте
настройки.
Samples должно
быть
целое.»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(a < ‘0’
|| a > ‘9’){

MessageBox(hWnd,
TEXT(«Проверьте
настройки.
Samples
должно содержать только цифры»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(a>=48) a=a-48; //get
integer by substracting ASCII code

samp
+= a*power(10,i); //Sum
with value in right order

}

if(samp>2048)
{

MessageBox(hWnd,
TEXT(«Проверьте
настройки количества точек выборки.
Должно быть менее 2048»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if(samp==0)
{

MessageBox(hWnd,
TEXT(«Проверьте
настройки. Samples не может быть равно 0.»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

freq
= 0;

EditfreqLength
= (WORD)SendMessage(textbFreq,EM_LINELENGTH,(WPARAM)0,(LPARAM)0);

if
(EditsampLength==0){

MessageBox(hWnd,
TEXT(«Проверьте
настройки
частоты
выборки»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

*((LPWORD)EditfreqLine)
= EditfreqLength;

SendMessage(textbFreq,EM_GETLINE,(WPARAM)0,(LPARAM)EditfreqLine);

for(int
i=0;i<EditfreqLength;i++){

freqch[i]
= EditfreqLine[i];

a
= EditfreqLine[EditfreqLength-1-i]; //set
current symbol

if
(a == ‘.’
|| a == ‘,’){

MessageBox(hWnd,
TEXT(«Проверьте
настройки
частоты
выборки.
Должно
быть целое.»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(a < ‘0’
|| a > ‘9’){

MessageBox(hWnd,
TEXT(«Проверьте
настройки частоты выборки. Должно
содержать только цифры»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(a>=48) a=a-48; //get
integer by substracting ASCII code

freq
+= a*power(10,i); //Sum
with value in right order

}

if(freq==0)
{

MessageBox(hWnd,
TEXT(«Проверьте
настройки.
freq не
может
быть
равно
0.»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

Tscale=0;

EditTLength
= (WORD)SendMessage(Scale_t,EM_LINELENGTH,(WPARAM)0,(LPARAM)0);

if
(EditTLength==0){

MessageBox(hWnd,
TEXT(«Проверьте
настройки
масштаба
по
t»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

*((LPWORD)EditTLine)
= EditTLength;

SendMessage(Scale_t,EM_GETLINE,(WPARAM)0,(LPARAM)EditTLine);

for(int
i=0;i<EditTLength;i++){

Tscalech[i]
= EditTLine[i];

a
= EditTLine[EditTLength-1-i]; //set
current symbol

if
(a == ‘.’
|| a == ‘,’){

MessageBox(hWnd,
TEXT(«Проверьте
настройки.
tmax должно
быть
целое.»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(a < ‘0’
|| a > ‘9’){

MessageBox(hWnd,
TEXT(«Проверьте
настройки.
tmax должно
содержать
только
цифры»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(a>=48) a=a-48; //get
integer by substracting ASCII code

Tscale
+= a*power(10,i); //Sum
with value in right order

}

if(Tscale==0)
{

MessageBox(hWnd,
TEXT(«Проверьте
настройки.
tmax не
может
быть
равно
0.»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

return
1;

}

int
start(){//считывает
введенные настройки, проверяет их,
создает подключение к COM-порту

//проверяет,
готов ли порт к получению данных, создает
поток для рисования графика.

int
a;

int
k;

IfData=FALSE; //reset
result receive flag

//считываем
выбранный
COM-порт

i=SendMessage(hComboCOM,CB_GETCURSEL,0,0);

if
(flagCOM == FALSE)

{

if
(i==0)
COM=CreateFile((LPCSTR)L»COM1″,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);

if
(i==1)
COM=CreateFile((LPCSTR)L»COM2″,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);

if
(i==2)
COM=CreateFile((LPCSTR)L»COM3″,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);

flagCOM=TRUE;

}

//выбранный
ADC

i=SendMessage(hCombo,CB_GETCURSEL,0,0); //send
message to COMBOADC

//set
ADC mode variable

if
(i==0) ModeADC=’s’;

//if
(i==1) ModeADC=’s’;

if
(i==2) ModeADC=’d’;

SendMessage(hWnd,WM_PAINT,0,0); //send
WM_PAINT message

if
(COM == INVALID_HANDLE_VALUE)//проверяем
COM-порт

{

MessageBox(hWnd,
TEXT(«Ошибка
при
открытии
СОМ
порта»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

for
(i=0;i<100;i++) buffer[i]=0; //clear
buff with drawn results

//конфигурируем
COM-порт

DCB
dcbComm; //declare
structure for configure COM port

BOOL
fSuccess; //variable
to check

fSuccess
= GetCommState(COM, &dcbComm);//get
current setting of COM port

PurgeComm(COM,
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

//abort
all COM port actions

dcbComm.fBinary=TRUE;

dcbComm.BaudRate
= CBR_115200; //seet
BaudRate

dcbComm.ByteSize
= 8; //set
size of bytes

dcbComm.fParity
= NOPARITY; //turn
off PARITY

dcbComm.StopBits
= ONESTOPBIT; //set
number of stopbits

COMMTIMEOUTS
TimeOuts; //structure
for setting timeouts of COM

TimeOuts.ReadTotalTimeoutMultiplier=1;

TimeOuts.ReadTotalTimeoutConstant=3000;

SetCommTimeouts(COM,&TimeOuts); //set
timeouts

fSuccess
= SetCommState(COM, &dcbComm);//set
configuration of COM port

//end
of Configure

if
(fSuccess==0)//проверяем,
прошла
ли
конфигурация

{

MessageBox(hWnd,
TEXT(«Ошибка
при
попытке
настроить
СОМ
порт…»),
TEXT(«Ошибка»),
MB_OK);

return
0;

}

if
(ConnectToCOM(COM)==0)//функция
установления связи

{

MessageBox(hWnd,TEXT(«Не
удалось установить соединение»),
TEXT(«Ошибка
COM порта»),
MB_OK);

return
0;

}

BYTE
z;

z=0;

i=0;

//проверка,
готов ли порт к получению данных с МК

while
(z!=’r’)

{

pRead(COM,&z); //получать
байт с МК

i++;

if
(i==10)

{

MessageBox(hWnd,TEXT(«СОМ
порт не готов для получения
данных»),TEXT(«Ошибка»),MB_OK);

return
0;

}

}

k
= Get_Res(COM,Result); //получать
данные
с
МК

if
(k==0)

{

MessageBox(hWnd,TEXT(«Ошибка
при
получении
данных»),TEXT(«Ошибка»),MB_OK);

return
0;

}

//создание
потока для рисования графика

DataProcess_Thread=CreateThread(NULL,0,ThreadProcDraw,NULL,CREATE_SUSPENDED,NULL);

ResumeThread(DataProcess_Thread); //execute
thread

return
1;

}

int
ConnectToCOM(HANDLE port) //
функция
соединения
с
COM портом

{

int
i; //secondary
variables

int
a;

unsigned
char
b;

BYTE
buf; //exchange
buffer

PurgeComm(port,
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

//сбрасывает
все символы из буфера вывода или ввода
данных COM-порта.

buf=’c’;

pWrite(port,buf); //отправка
символа ‘c’ на COM-порт

buf=0;

i=0;

int
flag=0;

//while
(buf!=’p’)

while(flag
== 0)

{

flag
= pRead(port,&buf);//получение
символа
‘p’ от
COM-порт

i++;

if
(i==10) {return
0;} //если
ответный символ не пришел, то выйти из
функции (подключение не состоялось)

}

if
(buf!=’p’)
return
0; //если
ответный символ не ‘p’, то выйти из функции
(подключение не состоялось)

buf=ModeADC;

pWrite(port,buf); //отправка
настройки режима ADC МК, используя COM-порт

a
= samp>>8; //
установить старший байт значения
количества выборок для дальнейшей
отправки

buf=(unsigned
char)a;

pWrite(port,buf); //
отправить старший байт значения
количества выборок

b
= (unsigned
char)samp; //
установить младший байт значения
количества выборок для дальнейшей
отправки

buf=b;

pWrite(port,buf); //
отправить младший байт значения
количества выборок

a
= freq>>8; //установить
старший байт значения частоты для
дальнейшей отправки

b
= (unsigned
char)freq; //установить
младший байт значения частоты для
дальнейшей отправки

buf=(unsigned
char)a;

pWrite(port,buf); //
отправить старший байт значения частоты

buf=b;

pWrite(port,buf); //
отправить младший байт значения частоты

return
1;

}

DWORD
WINAPI ThreadProcDraw(LPVOID param) //функция-поток
рисования графика

{

int
k; //secondary
variables

unsigned
int
i;

k=0; //zero
counter of DrawBuffer

RECT
rect; //struct
with main window sizes

for
(i=0;i<2*samp;i=i+2)

{

buffer[k]+=Result[i]; //запись
в буфер старшего байта

buffer[k]=buffer[k]<<8;
//сдвиг
этого байта на один байт влево

buffer[k]+=Result[i+1]; //запись
в буфер младшего байта

k++;

}

IfData=TRUE; //set
flag that data processed

GetWindowRect(hWnd,&rect); //отыскивает
размеры рамки ограничивающей прямоугольник
определяемого окна

//update
main window

SetWindowPos(hWnd,NULL,rect.left+1,rect.top,rect.right-rect.left+1,rect.bottom-rect.top,SWP_SHOWWINDOW);

SendMessage(hWnd,
WM_PAINT,NULL,NULL);//send
message to draw grafics

return
(DWORD)1;

}

int
Get_Res(HANDLE port,unsigned
char
* res)//функция
получения
данных
от
МК
(отправленных
на
COM-порт)

//и
сохранения их в буфер Result[4000]

//возвращает
1, если успешно, 0 — в противном случае

{

unsigned
int
i; //secondary
variables

int
k;

BYTE
D;

if
(!pWrite(port,’a’))
return
0; //отправка
байта
‘a’ МК
— начало
передачи

for
(i=0;i<2*samp;i++)

{

k=pRead(port,&D); //получать
байт данных от МК

Result[i]=D; //сохранение
его в буфер

if
(k==0) return
0; //если
не успешно, то вернуть 0

}

return
1;

}

int
pWrite(HANDLE port,unsigned
char
c) //функия
записывает байт c в буфер COM-порта в

//асинхронном
режиме, используя специальную структуру
переполнения

//возвращает
1, если успешно, 0 — в противном случае

{

DWORD
dwWritten;

int
success= 0;

OVERLAPPED
inf= {0}; //структура
с информацией для передачи данных

inf.hEvent
= CreateEvent( //inf.hEvent
— сигнал окончания передачи

NULL, //handle
не может быть унаследован

FALSE, //авто-перезагружаемые
события

FALSE, //начальное
состояние объекта события не сигнализируется

NULL); //событие
без имени

if
(!WriteFile(port,&c,1,&dwWritten, &inf)) //запись
байта в буфер COM-порта в асинхронном
режиме

{ //если
не успешно

if
(GetLastError()
== ERROR_IO_PENDING) //проверить
последнюю
ошибку

//ждем
сигнал обеъкта (COM port)

if
(WaitForSingleObject(inf.hEvent, INFINITE) == WAIT_OBJECT_0)

//затем
получаем результат переполнения операции
с COM портом

//если
все еще ждем, функция возвращает FALSE

if
(GetOverlappedResult(port, &inf, &dwWritten, FALSE))

success
= 1; //запись
завершена

}

else

success
= 1; //запись
завершена

if
(dwWritten != 1) success = 0; //проверка
если количество байт для передачи =
количеству переданных данных

CloseHandle(inf.hEvent); //удалить
событие

return
success;

return
1;

}

int
pRead(HANDLE port,unsigned
char
* c) //function
reads byte c from buffer of COM port in

//asynchronous
mode using spesial OVERLAPPED struct

//return
1 if succeed, 0 otherwise

{

int
success= 0; //check
variable

DWORD
dwRead; //bytes
been read

//dwRead=0;

UINT
timeout=0xFFFF;

OVERLAPPED
inf= {0}; //struct
with information for transmission data

inf.hEvent
= CreateEvent( //inf.hEvent
— signal of ending of transmission

NULL, //handle
cannot be inherited

FALSE, //auto-reset
event

FALSE, //initial
state of the event object is nonsignaled

NULL); //no
name of event

if
(ReadFile(port, c, 1, &dwRead, &inf))//write
byte to buffer of COM port in asynchronous mode

{

success
= 1; //if
succeed

}

CloseHandle(inf.hEvent); //delete
event

return
success; //successful
reading}

    1. Алгоритмы

      1. Обобщенный алгоритм программы для МК

      1. Дополнительные алгоритмы программы
        для МК

        1. Алгоритм процедуры отключения WDT

        1. Алгоритм функции SYSCLK_Init

нет

да

        1. Алгоритм функции Port_Init

        1. Алгоритм функции ADCInit

нет

да

        1. Алгоритм функции DMAInit

нет

да

        1. Алгоритм работы функции UART0_Init

        1. Алгоритм функции Timer3_Init

        1. Алгоритм функции main

        1. Алгоритм функции Config

        1. Алгоритм функции SendData

нет

да

        1. Алгоритм функции ReceiveData

нет

да

      1. Обобщенный алгоритм программы
        Windows-приложения

        1. Алгоритм работы функции рисования
          графика

        1. Алгоритм обработки сообщений

10

Иногда возникает ситуация, когда надо что‑то посчитать согласно сложному алгоритму прямо на LapTop/NetTop/DeskTop PC. При этом этот алгоритм написан на Си. Это может быть цифровой фильтр, дискретное преобразование Фурье, генератор QR кода, кусок линейной алгебры с векторами, какое‑то тригонометрическое вычисление, программный модулятор, статистическая обработка случайной величины. Да всё, что угодно! То есть Вы хотите использовать язык Си как гибкий и быстрый калькулятор в Windows. Тут надо написать программу на Си.

Компьютер — это универсальный вычислитель

Или, например, Вы программируете микроконтроллеры на Си и хотите сделать симулятор прошивки в виде консольного приложения. Надо вам, например, прогнать модульные тесты платформа‑независимого кода на «большом компьютере». Потом Вам 80% вероятность, что понадобится конфигуратор прошивки по UART. Потом Вам понадобится консольное приложение Loader для загрузки по UART самой прошивки через BootLoader.

Потом понадобится крохотная PC‑утилита синхронизации для часов реального времени с PC.

Почему именно Си?

  1. Эту LapTop утилиту стоит писать на том же языке, что и прошивку хотя бы по той причине, что можно пере использовать кодовую базу из микроконтроллеров для программирования на DeskTop(е).

  2. Дело в том что язык программирования Си — это самый простой язык программирования из тех, что всё еще более или менее используется в промышленной разработке. По факту в Си только функции и переменные. Тут нет никаких виртуальных функций, шаблонов, делегатов и прочих концепций. В Си всё предельно просто и конкретно.


Разработка под PC это не сross компиляция, как в случае со сборкой артефактов для микроконтроллера, и тут в PC всё в какой‑то степени проще. При сборке Си приложения не надо думать о файле конфигурации компоновщика, как мы это привыкли делать для cross компиляции артефактов для микроконтроллеров (файлы *.ld).

Сперва определимся для какого Target(а) надо собрать бинарь. Надо узнать какой у нас на материнской плате установлен микропроцессор. Такую информацию может показать утилита CPUZ.

В данном случае у меня на одном компьютере Intel Celeron J4125 2Ghz, 4x cores, L1 32kByte, L2 4MByte, 10W. На другом компьютере установлен 64 разрядный микропроцессор AMD Ryzen 5 PRO 3400GE 3.30 GHz.

Но это даже не так важно. Важно какой у нас Instruction Set. В данном случае — x86–64. Это значит, что у нас 64-битный процессор. Получается, что у нас есть выбор: либо ставить 64-битный компилятор Си, либо накатывать 32-битный компилятор Си.

Когда мы пишем программу в Windows мы пишем программу не для микропроцессора. Мы пишем программу для операционной системы. Это главное отличие от программирования для микроконтроллера. Там мы писали монолитную программу для конкретного микропроцессорного ядра (ARM-Cortex M33 или PowerPC). Тут же как правило, нам приходится работать на разных процессорах, однако мы в OS Windows этого даже не замечаем.

Units:

text

bit

N

N

kByte

kByte

MByte

Процессор

bitness

cores

Threads

L1

L2

L3

1

AMD Ryzen 5 PRO 3400GE

64

4

8

32

512

4

2

Intel Celeron J4125

?

4

4

32

?

?

3

Intel Core i7 8550U

64

4

8

32

256

8

Одновременно с этим наш имитатор прошивки должен собираться и запускаться на всех окружениях настольных компьютеров: на работе, дома, в гараже.

Как и в любом деле сначала надо определится с терминологией.

Терминология

Компилятор — программа, переводящая написанный на языке программирования читаемый понятный человеком текст, в набор машинных кодов (человеко‑нечитаемый бинарный код). Программисты это люди, которые как никто приближены к абстракциям. Любой язык программирования, в частности Си — это уровень на коротком можно не думать о наборе команд данного микропроцессора. У процессора нет никаких переменных и функций. Переменные существуют только в сознании программиста. Поэтому нам нужен компилятор Си. Компилятор для каждого Cи файлика производит *.o файлик с родным для данного процессора машинным кодом.

Компоновщик (Linker)— утилита, которая склеивает бинарные файлы *.o в один монолитный исполняемый бинарный файл программы. В нашем случае это *.exe.

Артефакт — результат работы ToolChain(а). В нашем случае это *.exe файл с бинарным файлом программы.

Что надо из софтвера?

Текстовый редактор (Text Editor)

Прежде всего нужен какой‑то текстовый редактор, чтобы написать этот самый исходный текст программы на Си. Тут вариантов масса. NotePad++, Eclipse, MS VS Code.

Сборщик (Build Tools)

В компьютерах ничего само собой не происходит. Компьютеры — это самые ленивые и безынициативные сущности. Им всё надо объяснять максимально подробно и понятно. Поэтому надо явно указать из каких *.с файлов мы хотим собирать программу. Эти файлы надо как-то перечислить проиндексировать. Для этого была создана специальная утилита называемая make. Идея проста. Создается текстовый файл (Makefile) и в нем прописывается правильная последовательность вызова консольных утилит, которая приведет к тому, что на жестком диске появится исполняемый файл с программой.

Препроцессор (Preprocessor)

Это такая консольная утилита (cpp.exe), которая вставляет и заменяет куски текста. Нужна чисто ради удобства написание текста. Препроцессор позволяет полностью исключить такое нехорошее явление как дублирование программного кода. При этом препроцессору абсолютно всё равно с каким языком программирования работать (Cи, C++, DeviceTree, Graphviz, скрипты компоновки и т. п.). Для препроцессора любой язык программирования — это просто текст.

Теперь рассмотрим практические аспекты.

Какой выбрать компилятор Си кода?

Тут есть несколько бесплатных вариантов на выбор.

Компилятор

разрядность генерируемого кода

1

СygWin

64

2

MinGW

32

3

Mingw-w64

64

4

clang

64

5

Tiny C Compiler

32/64

Для программистов микроконтроллеров я настоятельно рекомендую выбрать именно MinGW. Дело в том, что MinGW генерирует 32-битный код. Это как раз соответствует тому, что большинство микроконтроллеров (например ARM Cortex Mx) как раз 32-битные. И Вы так достигните большей совместимости между кодом прошивки микроконтроллера и консольным приложением в Windows.

Вторая причина по которой надо использовать компилятор C:\MinGW\bin\gcc.exe заключается в том, что в окружении MinGW есть заголовочный файл conio.h, который определяет функцию kbhit(). Это нам понадобится для имитации на PC текстового терминала UART-CLI консоли в stdout/stdin.

>C:\MinGW\bin\gcc.exe  --version
gcc.exe (MinGW.org GCC-6.3.0-1) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Набор утилит MinGW преспокойно скачивается и устанавливается как любая другая программа под OS Windows.

Компоновщик (Linker)

Компоновкой занимается утилита ld, которая вызывает collect2. Именно утилита collect2.exe будет выдавать ошибки, если Вы будите вызывать функции без определения их тела.

Что вообще надо из софтвера?

Вот минимальный джентельменский набор для того чтобы собрать программу на Си.

Назначение утилиты

Название утилиты

1

Текстовый редактор

NotePad++.exe

2

Препроцессор

cpp.exe

3

Компилятор

gcc.exe

4

Консольная утилита для удаления файлов или папок

rm

5

Компоновщик

ld.exe

6

Утилита управления ToolChain(ом). Она решает что и в какой последовательности собирать, чтобы получить артефакты

make.exe

7

Утилита анализа получившегося бинаря. Аналог readelf.exe из мира программирования микроконтроллеров

PE explore.exe

Традиционно программы на Си собирают из make скриптов. Вот минималистический makefile для сборки много файлового Си‑проекта на Windows

MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
INCDIR += -I$(MK_PATH)

WORKSPACE_LOC:= $(MK_PATH)../../
$(info  WORKSPACE_LOC= $(WORKSPACE_LOC))
INCDIR += -I$(WORKSPACE_LOC)

BUILDDIR := $(MK_PATH)/Build

SRC_PATH :=  $(dir $(abspath $(dir $$PWD) ))
#@echo $(error SRC_PATH=$(SRC_PATH))

OBJDIR := $(SRC_PATH)obj
# the compiler to use

OPT += -DHAS_GCC
CC = C:\MinGW\bin\gcc.exe

# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS += -g

#Generate code for 32-bit ABI
CFLAGS += -m32

CFLAGS += -std=c11 -fshort-enums
#CFLAGS += -Og
CFLAGS  += -O0
#CFLAGS  += -Wall
#CFLAGS  +=-pedantic
#CFLAGS += -ftime-report

#files to link:
LFLAGS += -static
#LFLAGS += -lm

EXECUTABLE=firmware_simulator_x86_m

include $(MK_PATH)config.mk

ifeq ($(CLI),Y)
    include $(MK_PATH)cli_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)test_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)diag_config.mk
endif

include $(WORKSPACE_LOC)code_base.mk


#@echo $(error SOURCES_C= $(SOURCES_C))
INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR))
#@echo $(error INCDIR= $(INCDIR))
OBJ := $(patsubst %.c, %.o, $(SOURCES_C))
OBJ := $(subst /cygdrive/c/,C:/, $(OBJ))
#@echo $(error OBJ= $(OBJ))

.PHONY:all

all:$(OBJ) $(EXECUTABLE)

$(EXECUTABLE): $(OBJ)
	$(CC) $(CFLAGS)  $(OBJ) $(LFLAGS) -o $(EXECUTABLE).exe 

%.o: %.c
	$(CC) $(CFLAGS) $(INCDIR) $(OPT) -c $< -o $@ 

clean:
	rm -r $(EXECUTABLE) $(OBJ) 

Тут файлы config.mk, cli_config.mk, test_config.mk и diag_config.mk это просто файлы с перечислением набора переменных окружения для выборочной сборки конкретных исходников из общей кодовой базы. Вот корневой makefile для подключения разных программных компонентов code_base.mk

ifneq ($(CODE_BASE_MK),Y)
    CODE_BASE_MK=Y
    $(info CodeBase Config)

    #@echo $(error WORKSPACE_LOC=$(WORKSPACE_LOC))
    INCDIR += -I$(WORKSPACE_LOC)

    ifeq ($(THIRD_PARTY),Y)
        include $(WORKSPACE_LOC)/third_party/third_party.mk
    endif

    ifeq ($(APPLICATIONS),Y)
        include $(WORKSPACE_LOC)/applications/applications.mk
    endif

    ifeq ($(CONNECTIVITY),Y)
        include $(WORKSPACE_LOC)/connectivity/connectivity.mk
    endif

    ifeq ($(CONTROL),Y)
        include $(WORKSPACE_LOC)/control/control.mk
    endif
    
    ifeq ($(COMPUTING),Y)
        #@echo $(error COMPUTING=$(COMPUTING))
        include $(WORKSPACE_LOC)/computing/computing.mk
    endif

    ifeq ($(SENSITIVITY),Y)
        #@echo $(error SENSITIVITY=$(SENSITIVITY))
        include $(WORKSPACE_LOC)/sensitivity/sensitivity.mk
    endif

    ifeq ($(STORAGE),Y)
        #@echo $(error STORAGE=$(STORAGE))
        include $(WORKSPACE_LOC)/storage/storage.mk
    endif

    ifeq ($(UNIT_TEST),Y)  
        include $(WORKSPACE_LOC)/unit_tests/unit_test.mk
    endif
endif

Конечный make файл может выглядеть например так. Вот тут и индексируют конечные *.c файлы и определяют ключевые слова для препроцессора (начинаются на HAS_XXXXX).


$(info SCHMITT_TRIGGER_MK_INC=$(SCHMITT_TRIGGER_MK_INC))
ifneq ($(SCHMITT_TRIGGER_MK_INC),Y)
    SCHMITT_TRIGGER_MK_INC=Y

    mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
    $(info Build  $(mkfile_path) )

    SCHMITT_TRIGGER_DIR = $(COMPUTING_DIR)/schmitt_trigger

    INCDIR += -I$(SCHMITT_TRIGGER_DIR)
    SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger.c

    SCHMITT_TRIGGER=Y
    OPT += -DHAS_SCHMITT_TRIGGER

    ifeq ($(DIAG),Y)
        OPT += -DHAS_SCHMITT_TRIGGER_DIAG
        SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_diag.c
    endif

    ifeq ($(CLI),Y)
        ifeq ($(SCHMITT_TRIGGER_COMMANDS),Y)
            OPT += -DHAS_SCHMITT_TRIGGER_COMMANDS
            SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_commands.c
        endif
    endif
endif

Если говорить метафорично, то Make — это как механическая коробка передач, только для сборки программ. C Make Вы можете буквально контролировать каждую опцию компилятора. Плюс Make в том, что за 60 лет своего существования это теперь самая разобранная и надежная технология из всего Computer Science.

При первой сборке проекта скорее всего выскочит вот эта ошибка. Это значит, что надо переустановить MinGW.


C:\Users\username\AppData\Local\Temp\ccT9XWou.s:54: Error: invalid instruction suffix for `push'
C:\Users\username\AppData\Local\Temp\ccfvWBon.s:19: Error: invalid instruction suffix for `pop'

Надо чтобы команда gcc.exe -dumpmachine после установки показывала mingw32

>C:\MinGW\bin\gcc.exe -dumpmachine
mingw32

Содержимое того Makefile можно представить в виде вот этой простенькой блок схемы ToolChain(а). Тут можно визуально проследить какой путь проходит *.с файл с момента написания до исполнения в Windows.

Схема ToolChain(а) для сборки Си кода

Схема ToolChain(а) для сборки Си кода

В остальном сборка на PC не отличается от сборки для микроконтроллера. В этом и достоинство сборки из Make. При работе с make cборка под любые процессоры выглядит плюс/минус одинаково. Просто по другому определенные переменные окружения: CC LD и т. п. Все те же *.mk файлы подтянутся что и в кодовой базе для прошивок.

Про то как происходит сборка прошивок из-под скриптов можно почитать в этом тексте:

Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB

.

Когда собралась вся кодовая база бинарь получился всего 1,9MByte

Отладка имитатора прошивки

Артефактом сборки является файл *.exe. Его можно запустить вызвав его из командной строки cmd.

Тут происходит имитация UART-CLI терминала только вместо UART выступают файлы stdin (аналог UART-RX)/stdout (аналог UART-TX).

Можно заметить, что скорость исполнения приложения просто бешеная. За одну секунду супер-цикл успевает прокрутиться аж 11 240 590 раз! В микроконтроллерах это значение обычно было порядка 7588 раз.

диагностика супер цикла в микроконтроллере ARM Cortex-M33

диагностика супер цикла в микроконтроллере ARM Cortex-M33

Получается, что на PC приложение исполняется быстрее в 1481 раз. На три порядка быстрее!

диагностика супер цикла в консольном приложении Win32

диагностика супер цикла в консольном приложении Win32

Вывод

Сборка кода на Cи для DeskTop это весьма полезный навык при отладке микроконтроллерных прошивок. Можно отладить огромные куски платформа независимого кода: CRC, обработку строчек, триггер Шмитта, бинарные протоколы, и т.п.

Также можно собрать проект разными компиляторами: GCC, Clang. И тем самым найти и устранить больше ошибок в кодовой базе.

Как видите, в сборке Си программ на PC, ровным счетом, нет ничего сложного. Надеюсь этот текст поможет большему количеству программистов-микроконтроллеров отлаживать свои приложения на DeskTop PC и создавать, тем самым, отличные программные продукты.

Links

Дайте мне 15 минут, и я изменю ваш взгляд на GDB

Компилятор GCC. Первая программа на Windows

EclipseIDE.Установка, настройка, программирование на С/Installation, configuration, programming in C

Eclipse и MinGW: как указать IDE путь к makefile и файлам проекта; разделение mingw32-make и ключей

Сборка firmware для CC2652 из Makefile

Почему Важно Собирать Код из Скриптов

Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB

Tiny C Compiler — Summary https://savannah.nongnu.org/projects/tinycc

Вопросы

  1. Какой путь проходит *.с файл с момента написания до момента исполнения?

  2. Что происходит между нажатием на Enter при запуске консольной утилиты в cmd и запуском функции main()?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Вы собирали Си программы для x86-64 / x86?

Проголосовали 72 пользователя. Воздержались 9 пользователей.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Вы делаете имитаторы прошивок на PC?

Проголосовали 65 пользователей. Воздержались 10 пользователей.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Вы писали консольные приложения на Си?

Проголосовал 71 пользователь. Воздержались 10 пользователей.

  • Стартовая функция WinMain
  • Программа на Си для Windows, как и для любой другой платформы, должна
    обязательно содержать некоторую «стартовую» функцию, которой передается
    управление при запуске программы. Вообще говоря, имя такой «стартовой»
    функции может различаться в различных компиляторах, но исторически
    сложилось так (а, кроме того, имеются еще и стандарты ANSI и ISO,
    к которым, правда, производители коммерческих компиляторов типа Microsoft
    и Borland/Inprise относятся без особого трепета), что такой функцией является:

    int main()

    У этой функции может быть до трех параметров:

    int main(int argc, char *argv[], char *env[])
    • argc — количество параметров в командной строке (включая имя программы),
    • argv — массив строк-параметров (argv[0] — имя программы),
    • env — массив строк-переменных окружения.

    Многие компиляторы для Windows «понимают» такую стартовую функцию.
    Однако при этом они создают хотя и 32-битное, но консольное приложение.
    Пример 1 (example1.cpp):

    #include <stdio.h>
    int main() {
     printf("Hello, world!");
     getc(stdin);
     return 0;
    }

    Компилируем:

    bcc32 example1.cpp

    Запускаем:

    При использовании стандартных библиотек (stdio, stdlib и т. п.)
    вам не потребуется никаких лишних телодвижений по сравнению с обычными
    методами написания программ на Си. Если же ваша цель — 32-битное приложение
    с графическим интерфейсом, то черное консольное окошко будет вас раздражать.
    Пример (example2.cpp):

    #include <windows.h>
    int main() {
     MessageBox(NULL,"Hello, World!","Test",MB_OK);
     return 0;
    }

    В общем, чтобы получить нормальное приложение без каких-либо «довесков»
    типа консольного окошка, используется другая стартовая функция:

    int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hpi, LPSTR cmdline, int ss)
    • hInst — дескриптор для данного экземпляра программы,
    • hpi — в Win32 не используется (всегда NULL),
    • cmdline — командная строка,
    • ss — код состояния главного окна.

    Пример (example3.cpp):

    #include <windows.h>
    int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) {
     MessageBox(NULL,"Hello, World!","Test",MB_OK);
     return 0;
    }

    Кроме того, компилятору и компоновщику нужно сообщить о том, что
    вы делаете графическое приложение. Для bcc32 это опция -tW:

    bcc32 -tW example3.cpp

    Если же вы соскучитесь по черному консольному окошку, его можно
    в любой момент создать при помощи вызова Win32 API

    BOOL AllocConsole(void)

  • О типах, о функциях
  • Как известно, в Си есть лишь три базовых типа
    (char, int, float/double)
    и еще несколько их вариаций с модификаторами signed/unsigned, short/long.
    Однако фирме Microsoft зачем-то понадобилось описывать функции Win32 API
    с помощью переопределенных типов:

    typedef unsigned char BYTE;
    typedef unsigned short WORD;
    typedef unsigned int UINT;
    typedef int INT;
    typedef long BOOL;
    #define FALSE 0
    #define TRUE 1
    typedef long LONG;
    typedef unsigned long DWORD;
    typedef void *LPVOID;
    typedef char CHAR;
    typedef CHAR *LPSTR;
    typedef const CHAR *LPCSTR;

    Кроме перечисленных простых типов, практически ни один вызов Win32 API
    не обходится без «штучек» с «ручками» — переменных типа handle («ручка»),
    которые идентифицируют некоторый объект («штучку»). Такие «ручки» принято
    называть дескрипторами. Реально такая переменная представляет собой всего
    лишь указатель на некоторую системную структуру или
    индекс в некоторой системной таблице.

    typedef void *HANDLE;    /* абстрактный дескриптор (например, файла) */
    typedef void *HMODULE;   /* дескриптор модуля */
    typedef void *HINSTANCE; /* дескриптор экземпляра программы */
    typedef void *HKEY;      /* дескриптор ключа в реестре */
    typedef void *HGDIOBJ;   /* дескриптор графического примитива (перо, шрифт, кисть, палитра,...) */
    typedef void *HWND;      /* дескриптор окна */
    typedef void *HMENU;     /* дескриптор меню */
    typedef void *HICON;     /* дескриптор иконки */
    typedef void *HBITMAP;   /* дескриптор картинки */
    typedef void *HFONT;     /* дескриптор шрифта */

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

    В стандартных версиях Си для функций используются два варианта соглашения
    о передаче параметров: соглашение языка Си (параметры функции помещаются в стек в порядке
    обратном их описанию, очистку стека производит вызывающая процедура) и
    соглашение языка Паскаль (параметры функции помещаются в стек в (прямом) порядке их
    описания, очистку стека производит вызываемая процедура). Для этих соглашений
    использовались, соответственно, модификаторы cdecl и pascal.
    При описании функций Win32 API используется модификатор WINAPI, а для
    описания пользовательских функций обратного вызова — модификатор CALLBACK.
    Оба этих модификатора являются переопределением специального модификатора _stdcall,
    соответствующего соглашению о передаче параметров, использующегося исключительно
    в Win32 API, — Standard Calling Convention (параметры функции помещаются в стек
    в порядке обратном их описанию, очистку стека производит вызываемая процедура).

  • Окна
  • Окно — это прямоугольная область экрана, в котором приложение отображает информацию и
    получает реакцию от пользователя. Одновременно на экране может отображаться несколько
    окон, в том числе, окон других приложений, однако лишь одно из них может получать
    реакцию от пользователя — активное окно. Пользователь использует клавиатуру, мышь
    и прочие устройства ввода для взаимодействия с приложением, которому принадлежит активное окно.
    Каждое 32-битное приложение создает, по крайней мере, одно окно, называемое главным окном,
    которое обеспечивает пользователя основным интерфейсом взаимодействия с приложением.

    Окно приложения может содержать строку заголовка title bar (1),
    строку меню menu bar (2), системное меню system menu (3),
    кнопку сворачивания окна minimize box (4), кнопку разворачивания окна maximize box (5),
    рамку изменения размеров sizing border (6), клиентскую область client area (7),
    горизонтальную и вертикальную полосы прокрутки scroll bars (8):

    Меню, строка заголовка с системными кнопками, системное меню, рамка изменения размеров
    и полосы прокрутки относятся к области окна, называемой неклиентской областью (non-client area).
    С неклиентской областью Windows справляется сама, а вот за содержимое и обслуживание
    клиентской области отвечает приложение.

    Кроме главного окна, приложение может использовать еще и другие типы окон:
    управляющие элементы (controls), диалоговые окна (dialog boxes),
    окна-сообщения (message boxes). Управляющий элемент — окно, непосредственно
    обеспечивающее тот или иной способ ввода информации пользователем. К управляющим
    элементам относятся: кнопки, поля ввода, списки, полосы прокрутки и т.п.
    Управляющие элементы обычно не болтаются сами по себе, а проживают в каком-либо
    диалоговом окне.
    Диалоговое окно — это временное окно, напичканное управляющими элементами,
    обычно использующееся для получения дополнительной информации от пользователя.
    Диалоговые окна бывают модальные (modal) и немодальные (modeless).
    Модальное диалоговое окно требует, чтобы пользователь обязательно ввел обозначенную
    в окне информацию и закрыл окно прежде, чем приложение продолжит работу.
    Немодальное диалоговое окно позволяет пользователю, не закрывая диалогового окна,
    переключаться на другие окна этого приложения.
    Окно-сообщение — это диалоговое окно предопределенного системой формата,
    предназначенное для вывода небольшого текстового сообщения с одной или несколькими
    кнопками. Пример такого окна показан в Примере 3.

    В отличие от традиционного программирования на основе линейных алгоритмов,
    программы для Windows строятся по принципам событийно-управляемого программирования
    (event-driven programming) — стиля программирования, при котором поведение
    компонента системы определяется набором возможных внешних событий и ответных реакций
    компонента на них. Такими компонентами в Windows являются окна. С каждым окном
    в Windows связана определенная функция обработки событий. События для окон
    называются сообщениями. Сообщение относится к тому или иному типу,
    идентифицируемому определенным кодом (32-битным целым числом), и сопровождается
    парой 32-битных параметров (WPARAM и LPARAM),
    интерпретация которых зависит от типа сообщения. В заголовочном файле windows.h
    для кодов сообщений определены константы с интуитивно понятными именами:

    #define WM_CREATE   0x0001  /* сообщение о создании окна */
    #define WM_DESTROY  0x0002  /* сообщение об уничтожении окна */
    #define WM_SIZE     0x0005  /* сообщение об изменении размеров окна */
    #define WM_COMMAND  0x0111  /* сообщение от команды меню или управляющего элемента */

    Таким образом, задача любого приложения — создать главное окно и сообщить
    Windows функцию обработки событий для этого окна. Все самое интересное
    для приложения будет происходить именно в функции обработки событий главного окна.

    Для стандартных управляющих элементов (библиотека Common Controls Library — COMCTL32.DLL)
    в Windows имеются предопределенные обработчики событий, которые при наступлении
    интересных событий сообщают всяческую полезную информацию окну, содержащему этот
    управляющий элемент. Стандартная библиотека Common Dialog Box Library
    (COMDLG32.DLL) содержит несколько готовых весьма полезных диалоговых окон с
    обработчиками: диалоги выбора файла, настроек печати, выбора шрифта, выбора цвета и др.
    Кроме того, любая среда разработки (VisualBasic, Delphi, VisualC++ и т.п.) навязывает
    разработчику дополнительный набор готовых управляющих элементов и диалогов —
    иногда достаточно удобных, иногда не очень.

  • Структура программы
  • Программа для Win32 обычно состоит из следующих блоков:

    #include <windows.h>
    
    int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR cmdline,int ss) {
    

    /* Блок инициализации: создание класса главного окна, создание главного окна, загрузка ресурсов и т.п. */

    /* Цикл обработки событий: */ MSG msg; while (GetMessage(&msg,(HWND)NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }

    return msg.wParam; } LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {

    /* Обработка сообщений главного окна */ switch (msg) { case WM_CREATE: /* ... */ return 0; case WM_COMMAND: /* ... */ return 0; case WM_DESTROY: /* ... */ PostQuitMessage(0); return 0; /* ... */ }

    return DefWindowProc(hw,msg,wp,lp); }

    Каждое окно принадлежит определенному классу окон. Окна одного класса
    имеют схожий вид, обслуживаются общей процедурой обработки событий,
    имеют одинаковые иконки и меню. Обычно каждое приложение создает
    для главного окна программы свой класс. Если приложению требуются
    дополнительные нестандартные окна, оно регистрирует другие классы.
    Стандартные диалоги и управляющие элементы принадлежат к предопределенным классам окон,
    для них не надо регистрировать новые классы. Чтобы определить новый класс окон,
    надо заполнить структуру WNDCLASS, содержащую следующие поля:

    • UINT style — стиль (поведение) класса окон,
    • WNDPROC lpfnWndProc — процедура обработки событий окна,
    • int cbClsExtra — размер дополнительной памяти в системной структуре класса для данных пользователя,
    • int cbWndExtra — размер дополнительной памяти в системной структуре окна для данных пользователя,
    • HINSTANCE hInstance — дескриптор модуля (экземпляра программы), в котором реализована процедура обработки,
    • HICON hIcon — дескриптор иконки окна,
    • HCURSOR hCursor — дескриптор курсора мыши для окна,
    • HBRUSH hbrBackground — дескриптор «кисточки» для закрашивания фона окна,
    • LPCSTR lpszMenuName — имя ресурса, содержащего меню окна,
    • LPCSTR lpszClassName — имя класса.

    Класс регистрируется при помощи функции:

    WORD WINAPI RegisterClass(const WNDCLASS *lpwc)

    При успешном завершении функция возвращает целочисленный код,
    соответствующий строке-имени класса в общесистемной таблице строк
    (такой код называется атомом). При ошибке возвращается 0.

    Для создания окна вызывается функция:

    HWND WINAPI CreateWindow(
      LPCSTR lpClassName,  /* имя класса */
      LPCSTR lpWindowName, /* имя окна (заголовок) */
      DWORD dwStyle,       /* стиль (поведение) окна */
      int x,               /* горизонтальная позиция окна на экране */
      int y,               /* вертикальная позиция окна на экране */
      int nWidth,          /* ширина окна */
      int nHeight,         /* высота окна */
      HWND hWndParent,     /* дескриптор родительского окна */
      HMENU hMenu,         /* дескриптор меню */
      HANDLE hInstance,    /* дескриптор экземпляра программы */
      LPVOID lpParam       /* указатель на какую-нибудь ерунду */
    )

    Вместо параметров x, y, nWindth, nHeight допустимо передавать
    константу CW_USEDEFAULT, позволяющую операционной системе задать эти числа
    по ее усмотрению.

    Интерпретация кода стиля определяется классом окна.
    Стиль определяет не только оформление окна, но и его поведение.
    Общие для всех классов константы стилей
    (при необходимости объединяются операцией побитовое ИЛИ):

    • WS_DISABLED — при создании окно заблокировано (не может получать реакцию от пользователя);
    • WS_VISIBLE — при создании окно сразу же отображается (не надо вызывать ShowWindow);
    • WS_CAPTION — у окна есть строка заголовка;
    • WS_SYSMENU — у окна есть системное меню;
    • WS_MAXIMIZEBOX — у окна есть кнопка разворачивания;
    • WS_MINIMIZEBOX — у окна есть кнопка сворачивания;
    • WS_SIZEBOX или WS_THICKFRAME — у окна есть рамка изменения размеров;
    • WS_BORDER — у окна есть рамка (не подразумевает изменение размеров);
    • WS_HSCROLL или WS_VSCROLL — у окна есть горизонтальная или вертикальная прокрутка;
    • WS_OVERLAPPED или WS_TILED — «перекрываемое» окно — обычное окно с рамкой и строкой заголовка;
    • WS_POPUP — «всплывающее» окно;
    • WS_OVERLAPPEDWINDOW — «перекрываемое» окно с системным меню, кнопками сворачивания/разворачивания,
      рамкой изменения размеров, короче, типичный стиль для главного окна приложения.

    Во время выполнения функции CreateWindow процедуре обработки событий
    окна посылается сообщение WM_CREATE. При успешном выполнении функции
    возвращается дескриптор созданного окна, при неудаче — NULL.

    После создания окна неплохо бы сделать его видимым (отобразить), если только
    оно не создано со стилем WS_VISIBLE:

    BOOL WINAPI ShowWindow(HWND hw, int ss)

    Второй параметр этой функции — код состояния отображения окна. В качестве этого кода
    можно взять значение четвертого параметра, с которым была запущена функция WinMain.
    Другие возможные значения этого параметра:

    • SW_SHOW — отобразить и активировать окно;
    • SW_HIDE — скрыть окно;
    • SW_MAXIMIZE — развернуть окно на весь экран;
    • SW_RESTORE — активировать окно и отобразить его в размерах по умолчанию;
    • SW_MINIMIZE — свернуть окно.

    Если перед вызовом этой функции окно было видимым, функция возвращает TRUE,
    если же окно было скрыто — FALSE.

    Если клиентская область главного окна приложения содержит объекты, прорисовываемые
    по сообщению WM_PAINT, имеет смысл прорисовать эти объекты сразу после
    отображения главного окна на экране. Функция UpdateWindow непосредственно
    вызывает процедуру обработки событий указанного окна с сообщением WM_PAINT
    (минуя очередь сообщений приложения):

    BOOL WINAPI UpdateWindow(HWND hw)

    Windows использует два способа доставки сообщений процедуре обработки событий окна:

    • непосредственный вызов процедуры обработки событий (внеочередные или
      неоткладываемые сообщенияnonqueued messages);
    • помещение сообщения в связанный с данным приложением буфер типа FIFO,
      называемый очередью сообщенийmessage queue
      (откладываемые сообщенияqueued messages).

    К внеочередным сообщениям относятся те сообщения, которые непосредственно
    влияют на окно, например, сообщение активации окна WM_ACTIVATE и т.п.
    Кроме того, вне очереди сообщений обрабатываются сообщения, сгенерированные
    различными вызовами Win32 API, такими как SetWindowPos,
    UpdateWindow, SendMessage,
    SendDlgItemMessage

    К откладываемым сообщениям относятся сообщения, связанные с реакцией пользователя:
    нажатие клавиш на клавиатуре, движение мышки и клики. Чтобы извлечь сообщение из
    очереди, программа вызывает функцию

    BOOL WINAPI GetMessage(
      MSG *lpmsg,          /* сюда попадает сообщение со всякими параметрами */
      HWND hw,             /* извлекать только сообщения для указанного окна (NULL - все) */
      UINT wMsgFilterMin,  /* фильтр сообщений (нам не надо - ставим 0) */
      UINT wMsgFilterMax   /* фильтр сообщений (нам не надо - ставим 0) */
    )

    Эта функция возвращает FALSE, если получено сообщение WM_QUIT,
    и TRUE в противном случае. Очевидно, что условием продолжения
    цикла обработки событий является результат этой функции. Если приложение хочет
    завершить свою работу, оно посылает само себе сообщение WM_QUIT
    при помощи функции

    void WINAPI PostQuitMessage(int nExitCode)

    Ее параметр — статус выхода приложения. Обычно эта функция вызывается в ответ на
    сообщение об уничтожении окна WM_DESTROY.

    После извлечения сообщения из очереди следует вызвать функцию
    TranslateMessage, переводящую сообщения от нажатых клавиш
    в удобоваримый вид, а затем DispatchMessage,
    которая определяет предназначенное этому сообщению окно и вызывает
    соответствующую процедуру обработки событий.

    BOOL WINAPI TranslateMessage(const MSG *lpmsg)
    LONG WINAPI DispatchMessage(const MSG *lpmsg)

    Результат возврата соответствует значению, которое вернула процедура обработки событий
    (обычно никому не нужен).

    Процедура обработки сообщений окна должна быть объявлена по следующему прототипу:

    LRESULT CALLBACK WindowProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp)

    Значения параметров: hw — дескриптор окна, которому предназначено сообщение,
    msg — код сообщения, wp и lp — 32-битные параметры
    сообщения, интерпретация которых зависит от кода сообщения. Зачастую старший/младший
    байт или старшее/младшее слово параметров сообщения несут независимый смысл, тогда
    удобно использовать определенные в windows.h макросы:

    #define LOBYTE(w)   ((BYTE) (w))
    #define HIBYTE(w)   ((BYTE) (((WORD) (w) >> 8) & 0xFF))
    #define LOWORD(l)   ((WORD) (l))
    #define HIWORD(l)   ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))

    Например, сообщение WM_COMMAND посылается окну в трех случаях:

    1. пользователь выбрал какую-либо команду меню;
    2. пользователь нажал «горячую» клавишу (accelerator);
    3. в дочернем окне произошло определенное событие.

    При этом параметры сообщения интерпретируются следующим образом.
    Старшее слово параметра WPARAM содержит: 0 в первом случае, 1 во втором случае
    и код события в третьем случае. Младшее слово WPARAM содержит
    целочисленный идентификатор пункта меню, «горячей» клавиши или дочернего управляющего
    элемента. Параметр LPARAM в первых двух случаях содержит NULL,
    а в третьем случае — дескриптор окна управляющего элемента.

    Процедура обработки событий должна вернуть определенное 32-битное значение,
    интерпретация которого также зависит от типа сообщения. В большинстве случаев,
    если сообщение успешно обработано, процедура возвращает значение 0.

    Процедура обработки событий не должна игнорировать сообщения. Если
    процедура не обрабатывает какое-то сообщение, она должна вернуть его системе
    для обработки по умолчанию. Для этого вызывается функция:

    LRESULT WINAPI DefWindowProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp)

    Все описанное в данном параграфе суммируется в примере 4 (example4.cpp):

    #include <windows.h>
    
    LRESULT CALLBACK MainWinProc(HWND,UINT,WPARAM,LPARAM);
    #define ID_MYBUTTON 1    /* идентификатор для кнопочки внутри главного окна */
    
    int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int ss) {
     /* создаем и регистрируем класс главного окна */
     WNDCLASS wc;
     wc.style=0;
     wc.lpfnWndProc=MainWinProc;
     wc.cbClsExtra=wc.cbWndExtra=0;
     wc.hInstance=hInst;
     wc.hIcon=NULL;
     wc.hCursor=NULL;
     wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
     wc.lpszMenuName=NULL;
     wc.lpszClassName="Example 4 MainWnd Class";
     if (!RegisterClass(&wc)) return FALSE;
    
     /* создаем главное окно и отображаем его */
     HWND hMainWnd=CreateWindow("Example 4 MainWnd Class","EXAMPLE 4",WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL);
     if (!hMainWnd) return FALSE;
     ShowWindow(hMainWnd,ss);
     UpdateWindow(hMainWnd);
    
     MSG msg; /* цикл обработки событий */
     while (GetMessage(&msg,NULL,0,0)) {
      TranslateMessage(&msg); 
      DispatchMessage(&msg); 
     } 
     return msg.wParam; 
    }
    
    /* процедура обработки сообщений для главного окна */
    LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
     switch (msg) {
      case WM_CREATE:
       /* при создании окна внедряем в него кнопочку */
       CreateWindow("button","My button",WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE,
        5,5,100,20,hw,(HMENU)ID_MYBUTTON,NULL,NULL);
       /* стиль WS_CHILD означает, что это дочернее окно и для него
        вместо дескриптора меню будет передан целочисленный идентификатор,
        который будет использоваться дочерним окном для оповещения 
        родительского окна через WM_COMMAND */
       return 0;
      case WM_COMMAND:
       /* нажата наша кнопочка? */
       if ((HIWORD(wp)==0) && (LOWORD(wp)==ID_MYBUTTON)) 
        MessageBox(hw,"You pressed my button","MessageBox",MB_OK|MB_ICONWARNING);
       return 0;
      case WM_DESTROY:
       /* пользователь закрыл окно, программа может завершаться */
       PostQuitMessage(0);
       return 0;
     }
     return DefWindowProc(hw,msg,wp,lp);
    }

    Приведенный пример создает окно с кнопкой «My button», при нажатии
    на которую вылезает окно-сообщение:

  • Ресурсы
  • Ресурсы — это бинарные данные, добавляемые в исполняемый файл
    при компоновке программы. К стандартным ресурсам относятся: иконки,
    курсоры, меню, диалоги, растровые изображения (BMP), векторные изображения (EMF),
    шрифты, таблицы горячих клавиш, таблицы строк, информация о версии программы или модуля.
    В процессе разработки программы ресурсы описывают в отдельном текстовом файле —
    файле описания ресурсов (расширение .rc), — а затем при помощи компилятора
    ресурсов переводят в бинарный вид и добавляют в исполняемый файл на этапе компоновки
    исполняемого файла. Использование ресурсов значительно облегчает работу программиста
    по визуализации графических примитивов интерфейса программы.

    Файл описания ресурсов состоит из операторов, объединяемых в блоки.
    Один оператор занимает одну строку файла. Допускается использовать
    комментарии, определяемые так же, как в программе на языке Си.
    Файл описания ресурсов перед компиляцией так же обрабатывается препроцессором,
    поэтому в нем можно использовать директивы препроцессора (#include,
    #define, …) и макроопределения. В сложных «блочных»
    описаниях ресурсов вместо ключевых слов BEGIN и END
    можно использовать { и }, соответственно.

    Иконки, картинки и курсоры мыши можно описать двумя способами
    (квадратные скобки не являются частью синтаксиса оператора и означают
    необязательный элемент):

    nameID RESOURCETYPE [load-option] [mem-option] filename

    Здесь nameID — численный или строковой идентификатор;
    RESOURCETYPE — ключевое слово, обозначающее тип ресурса:
    ICON, BITMAP или
    CURSOR;
    load-option и mem-option — всякие неинтересные
    в данный момент опции, которые можно спокойно пропустить;
    filename — имя файла, содержащее соответствующий ресурс.

    Примеры:

    disk1   BITMAP "disk.bmp"
    12      ICON   "myicon.ico"

    Эти ресурсы можно внедрить в виде шестнадцатеричных кодов
    непосредственно в файл ресурсов:

    nameID RESOURCETYPE
    BEGIN
     hex data
    END

    Пример:

    FltBmp BITMAP
    {
     '42 4D A2 00 00 00 00 00 00 00 3E 00 00 00 28 00'
     '00 00 19 00 00 00 19 00 00 00 01 00 01 00 00 00'
     '00 00 64 00 00 00 00 00 00 00 00 00 00 00 00 00'
     '00 00 00 00 00 00 00 00 00 00 FF FF FF 00 FF FF'
     'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF'
     'FF 80 FF FF FF 80 FF FF FF 80 C0 FF 81 80 FE FF'
     'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF'
     'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF'
     'BF 80 FE FF BF 80 FE 00 3F 80 FF FF FF 80 FF FF'
     'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF'
     'FF 80'
    }

    Следует отметить, что первая иконка в ресурсах
    будет использоваться «Проводником» как иконка
    исполняемого файла.

    Меню описывается следующим образом:

    nameID MENU [load-option] [mem-option]
    BEGIN 
        item-definitions
        ...
    END

    Здесь item-definitions — один из трех операторов:

    MENUITEM text, result [, optionlist]
            /* обычный пункт меню */
    MENUITEM SEPARATOR
            /* строка-сепаратор */
    POPUP text [, optionlist]
    BEGIN   /* подменю */
        item-definitions
        ...
    END

    Параметры операторов имеют следующий смысл:
    text — текст пункта меню или подменю
    (может содержать комбинации \t — табуляция,
    \a — выравнивание по правому краю, &
    следующий символ подчеркивается, обозначает «горячую» клавишу для
    указанного пункта меню);
    result — целочисленный идентификатор пункта меню,
    посылаемый окну-владельцу через сообщение WM_COMMAND
    при выборе этого пункта меню;
    optionlist — необязательный список опций, разделенных
    запятой или пробелом:

    • CHECKED — рядом с пунктом меню отображается галочка,
    • GRAYED — пункт меню неактивен (не может быть выбран)
      и отображается серым цветом и др.

    Доступ к ресурсам, скомпонованным с исполняемым файлом, можно получить
    при помощи следующих функций:

    HICON WINAPI LoadIcon(HINSTANCE hInst, LPCSTR lpIconName)
    HBITMAP WINAPI LoadBitmap(HINSTANCE hInst, LPCSTR lpBitmapName)
    HCURSOR WINAPI LoadCursor(HINSTANCE hInst, LPCSTR lpCursorName)
    HMENU WINAPI LoadMenu(HINSTANCE hInst, LPCSTR lpMenuName)

    Первый параметр этих функций — дескриптор экземпляра программы,
    второй — идентификатор соответствующего ресурса. Если ресурс идентифицируется
    не именем, а числом, то следует использовать макрос, объявленный в windows.h:

    #define MAKEINTRESOURCE(i)  (LPSTR) ((DWORD) ((WORD) (i)))

    Например:

    HMENU hMainMenu=LoadMenu(hInst,MAKEINTRESOURCE(10));

    Для закрепления полученных сведений, давайте добавим к примеру 4
    какую-нибудь иконку и такое меню:

    Для этого создаем файл ресурсов (example4a.rc):

    Ex4_Icon ICON "myicon.ico"
    
    Ex4_Menu MENU
    {
     POPUP "&File"
     {
      MENUITEM "&Open...\tCtrl-O", 2
      MENUITEM "&Save", 3
      MENUITEM "Save &As...", 4
      MENUITEM SEPARATOR
      MENUITEM "&Hex view", 5, CHECKED GRAYED
      MENUITEM "&Exit\tAlt-F4", 6
     }
     POPUP "&Edit"
     {
      MENUITEM "&Copy", 7
      MENUITEM "&Paste", 8
      POPUP "Popup"
      {
       MENUITEM "1", 9
       MENUITEM "2", 10
       MENUITEM "3", 11
      }
      MENUITEM SEPARATOR
      MENUITEM "Search", 12
     }
     POPUP "&Help"
     {
      MENUITEM "&About...\tF1", 13
     }
    }

    Для перевода файла описания ресурсов в бинарный вид используется
    компилятор ресурсов Borland Resource Compiler:

    brcc32 example4a.rc

    В результате получается файл example4a.res, который потребуется в процессе
    компоновки.

    В примере 4 надо изменить строки

     wc.hIcon=NULL;
     wc.lpszMenuName=NULL;

    на

     wc.hIcon=LoadIcon(hInst,"Ex4_Icon");
     wc.lpszMenuName="Ex4_Menu";

    Чтобы программа не была такой скучной, изменим обработчик сообщения WM_COMMAND:

      case WM_COMMAND:
       if (HIWORD(wp)==0) {
        char buf[256];
        switch (LOWORD(wp)) {
         case 6:  /* команда меню Exit */
          PostQuitMessage(0);
         default: /* все остальные команды */
          wsprintf(buf,"Command code: %d",LOWORD(wp));
          MessageBox(hw,buf,"MessageBox",MB_OK|MB_ICONINFORMATION);
        }
       }
       return 0;

    В результате при выборе того или иного пункта меню выводится окно-сообщение с кодом команды.

    Обратите внимание: среди команд меню не используется код 1,
    который отведен кнопке «My button». Это типичная практика назначать
    всем дочерним элементам окна и командам меню разные численные идентификаторы,
    что облегчает обработку сообщения WM_COMMAND.

  • Компиляция и компоновка сложных проектов
  • Теперь компоновка программы будет более сложной, поэтому bcc32
    с этой задачей не справится. В этом примере компилятор будет
    использоваться только для компилирования (запускаем с ключом ):

    bcc32 -c -tW example4a.cpp

    В результате получаем объектный файл example4a.obj.

    Чтобы собрать все части программы вместе, придется запускать
    компоновщик вручную (ilink32 или tlink32).
    В командной строке компоновщика указываются следующие параметры:

    ilink32 [options] objfiles,exefile,mapfile,libfiles,deffile,resfiles
    • options — о допустимых опциях можно узнать, запустив компоновщик без параметров.
      Нам потребуются:

      • -aa — тип приложения «графическое для Win32»
        (другие варианты: -ap — консольное приложение, -ad — драйвер);
      • -Tpe — формат выходного файла «.EXE» (другой вариант: -Tpd — «.DLL»);
      • -L путь — путь для поиска библиотек и объектных файлов
        (обычно: -Lc:\bcc55\lib).
    • objfiles — список объектных файлов, из которых составляется программа,
      разделенных пробелом или знаком «+». Этот список должен начинаться с борландовского
      инициализационного объектного файла: c0w32.obj — для графического приложения под
      Win32 или c0x32.obj — для консольного приложения.
    • exefile — имя исполняемого файла, который получится в результате компоновки.
    • mapfile — имя файла, который после компиляции будет содержать
      карту сегментов вашей программы (оно вам надо? если нет, здесь делаем «пусто»,
      а в опциях указываем -x, чтобы компоновщик не замусоривал рабочий каталог этой фигней).
    • libfiles — список библиотек, в которых надо искать не
      определенные в программе функции, разделенных пробелом или знаком «+».
      Как минимум, надо указать import32.lib, которая содержит код подключения
      к стандартным библиотекам Win32 API: kernel32.dll, user32.dll, gdi32.dll,
      advapi32.dll и др. Если вы используете какие-либо функции стандартных
      библиотек языка Си (stdlib, stdio, …), надо указать еще cw32.lib.
    • deffile — файл параметров модуля (module definition file).
      Это текстовый файл, в котором определяются различные настройки компилируемой
      программы типа: размеры сегментов стека, кода, данных, «заглушка»
      (что будет происходить при попытке запуска программы в DOS) и проч.
      Если не указывать этот файл, компоновщик выберет вполне приличные
      для большинства случаев параметры по умолчанию.
    • resfiles — файлы ресурсов (разделяются пробелом или знаком «+»).

    Компоновщик ilink32 умеет работать в пошаговом (инкрементирующем)
    режиме incremental linking, при этом он создает несколько файлов состояний
    (*.IL?). При последующих попытках компиляции он использует их, так что
    процесс компиляции занимает меньше времени. Чтобы отключить пошаговую
    компиляцию и не замусоривать рабочий каталог этими файлами, следует
    указать опцию -Gn. Например, если при отключенной пошаговой компиляции
    программа компилируется 8 секунд, то первая компиляция в пошаговом режиме
    займет 25 секунд, а все последующие — не более 2 секунд.

    Итак, компонуем модифицированный пример 4:

    ilink32 -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res

    Если все сделано правильно, первое, что становится сразу заметным —
    у исполняемого файла появилась иконка:

    Эта же иконка отображается в строке заголовка главного окна программы. Под
    строкой заголовка отображается созданное нами меню. При выборе любой команды
    меню появляется окно-сообщение с кодом команды. При выборе команды «Exit»
    программа завершается.

    Если ваш проект состоит из множества файлов, то компилировать и
    компоновать их вручную становится затруднительно. В таких случаях
    используются сценарии компиляции, которые обрабатываются программой make.
    Сценарий компиляции — текстовый файл с именем Makefile, описывающий
    зависимости между различными файлами проекта, следующего формата:

    правило: зависимости
    	команды для выполнения

    Сценарий компиляции должен содержать как минимум одно правило.
    Строка с командами обязательно должна начинаться с отступа табуляцией.
    В качестве имени правила обычно выступает имя файла, который получится
    в результате выполнения команд в теле правила.
    Зависимости — необязательный список имен файлов, разделенных пробелами,
    от которых зависит данное правило. Если при вызове make окажется,
    что хотя бы один файл из этого списка новее, чем файл-результат правила,
    то выполняются все команды из этого правила. В качестве зависимостей
    могут указываться имена файлов-названия других правил. Тогда make
    будет выполнять рекурсивную проверку зависимостей. Make не выполняет
    команды из правила, если все файлы-зависимости старее файла-результата.

    Пример:

    example4a.exe: example4a.rc example4a.cpp myicon.ico
    	brcc32 example4a.rc
    	bcc32 -c -tW example4a.cpp
    	ilink32 -Gn -x -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res

    Если не указывать в качестве файлов-зависимостей example4a.rc и example4a.cpp,
    то make не станет ничего делать, когда файл example4a.exe уже существует.
    Тем не менее, приведенный пример — не совсем удачный сценарий компиляции.
    Если мы изменим только файл ресурсов, make все равно будет перекомпилировать
    исходный текст. Если мы изменим только исходный текст, make будет
    перекомпилировать еще и ресурсы. С учетом этого замечания, более удачным
    будет следующий сценарий:

    example4a.exe: example4a.obj example4a.res
    	ilink32 -Gn -x -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res
    
    example4a.obj: example4a.cpp
    	bcc32 -c -tW example4a.cpp
    
    example4a.res: example4a.rc myicon.ico
    	brcc32 example4a.rc

    Если в командной строке make не указано иное, то make пытается выполнить
    первое правило из сценария. Именно поэтому первым правилом стоит example4a.exe
    — результат, который мы хотим получить после компиляции всего проекта.

    Если написать подходящий сценарий компиляции, то для компиляции
    вашего проекта придется набирать лишь команду:

    make

  • Диалоговые окна
  • Большинство приложений использует диалоговые окна для запроса у пользователя
    дополнительной информации для выполнения каких-либо команд. Например,
    команда открытия файла требует указания имени файла, так что приложение
    создает диалоговое окно, чтобы запросить у пользователя имя файла.
    Пока пользователь не укажет имя файла, команда не будет выполнена.
    После этого программа уничтожает это диалоговое окно. В этом случае
    используется модальное диалоговое окно. Другой пример: текстовый
    редактор может использовать немодальное диалоговое окно, для
    команды поиска. Пока редактор ищет введенную фразу, диалоговое окно
    остается на экране. Более того, пользователь может вернуться к редактированию
    текста, не закрывая диалог поиска. Либо пользователь может ввести
    другую фразу для поиска. Такое диалоговое окно остается открытым,
    пока приложение не завершится или пользователь непосредственно
    не выберет команду закрытия этого диалога.

    Чтобы создать диалоговое окно, приложение должно предоставить
    системе шаблон диалога, описывающий содержание и стиль диалога,
    и диалоговую процедуру. Диалоговая процедура выполняет примерно
    такие же задачи, что и процедура обработки событий окна.
    Диалоговые окна принадлежат к предопределенному классу окон.
    Windows использует этот класс и соответствующую процедуру
    обработки событий для модальных и немодальных диалогов.
    Эта процедура обрабатывает одни сообщения самостоятельно,
    а другие передает на обработку диалоговой процедуре приложения.
    У приложения нет непосредственного доступа к этому предопределенному
    классу и соответствующей ему процедуре обработки событий.
    Для изменения стиля и поведения диалога программа должна использовать
    шаблон диалогового окна и диалоговую процедуру.

    Для создания модального диалога используется функция DialogBox,
    а для создания немодального диалога — CreateDialog:

    int WINAPI DialogBox(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc)
    HWND WINAPI CreateDialog(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc)

    Параметры: hInst — дескриптор экземпляра программы
    (модуля, в котором находится шаблон); template — имя ресурса,
    описывающего диалог; parent — дескриптор родительского окна;
    DlgFunc — диалоговая функция следующего формата:

    BOOL CALLBACK DlgFunc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) 

    Параметры диалоговой функции такие же, как у обычной функции обработки
    событий. Отличие этой функции — она вызывается из предопределенной
    функции обработки событий для диалоговых окон. Она должна вернуть
    значение TRUE, если обработала переданное ей сообщение,
    или FALSE в противном случае. Она ни в коем случае
    не должна сама вызывать DefWindowProc.

    При создании диалогового окна диалоговая процедура получает
    сообщение WM_INITDIALOG. Если в ответ на это сообщение
    процедура возвращает FALSE, диалог не будет создан:
    функция DialogBox вернет значение -1,
    а CreateDialogNULL.

    Модальное диалоговое окно блокирует указанное в качестве родительского
    окно и появляется поверх него (вне зависимости от стиля WS_VISIBLE).
    Приложение закрывает модальное диалоговое окно при помощи функции

    BOOL WINAPI EndDialog(HWND hw, int result)

    Приложение должно вызвать эту функцию из диалоговой процедуры в ответ
    на сообщение от кнопок «OK», «Cancel» или команды «Close» из системного
    меню диалога. Параметр result передается программе как
    результат возврата из функции DialogBox.

    Немодальное диалоговое окно появляется поверх указанного в качестве
    родительского окна, но не блокирует его. Диалоговое окно остается
    поверх родительского окна, даже если оно неактивно. Программа сама
    отвечает за отображение/сокрытие окна (с помощью стиля WS_VISIBLE
    и функции ShowWindow). Сообщения для немодального
    диалогового окна оказываются в основной очереди сообщений программы.
    Чтобы эти сообщения были корректно обработаны, следует включить в цикл
    обработки сообщений вызов функции:

    BOOL WINAPI IsDialogMessage(HWND hwDlg, MSG *lpMsg)

    Если эта функция вернула TRUE, то сообщение обработано
    и его не следует передавать функциям TranslateMessage
    и DispatchMessage.

    Немодальное диалоговое окно уничтожается, если уничтожается его
    родительское окно. Во всех остальных случаях программа должна сама
    заботиться об уничтожении немодального диалогового окна, используя вызов:

    BOOL WINAPI DestroyWindow(HWND hw)

    Шаблон диалогового окна в ресурсах задается следующим образом:

    nameID DIALOG [load-option] [mem-option] x, y, width, height
        [property-statements]
    BEGIN 
        control-statements
        ...
    END

    В начале блока описания диалога задается: nameID
    целочисленный или строковой идентификатор ресурса, x, y
    координаты диалога на экране (или относительно родительского окна),
    width, height — размер диалога.
    Координаты и размеры диалога и всех элементов внутри
    него задаются в диалоговых единицах (dialog units).
    Одна горизонтальная диалоговая единица соответствует 1/4 средней ширины
    символа в системном шрифте. Одна вертикальная диалоговая единица
    соответствует 1/8 средней высоты символа в системном шрифте.

    После заголовка блока идет ряд необязательных операторов-параметров диалога
    (property-statements) в любом порядке:

    STYLE style               /* стиль диалога */
    CAPTION captiontext       /* заголовок диалога */
    FONT pointsize, typeface  /* шрифт диалога (размер, название) */
    MENU menuname             /* меню диалога */

    В качестве стиля диалога можно применять все перечисленные для обычных окон
    стили. Обычно выбирают: WS_POPUP, WS_SYSMENU, WS_CAPTION,
    а также WS_BORDER для немодального диалога и DS_MODALFRAME
    для модального. Кроме того, можно использовать DS_SETFOREGROUND,
    чтобы при отображении диалога перевести его на передний план, даже если
    его родительское окно неактивно.

    В теле шаблона (control-statements) перечисляются
    составляющие его управляющие элементы. Один из возможных вариантов оператора:

    CONTROL text, id, class, style, x, y, width, height

    Здесь text — текст управляющего элемента (заголовок),
    id — целочисленный идентификатор элемента 0…65535
    (внутри одного диалога идентификаторы всех элементов должны различаться),
    class — имя класса, к которому принадлежит управляющий элемент,
    style — стиль управляющего элемента,
    x, y, width, height
    положение и размер диалогового элемента относительно клиентской
    области диалога в диалоговых единицах.

    Вот пример диалога, содержащего простое статическое текстовое поле и кнопку «OK»:

    Ex4_Dlg DIALOG 50,50,90,40
     STYLE WS_POPUP|WS_CAPTION|DS_MODALFRAME
     CAPTION "MyDlg"
     FONT 10, "Arial"
    {
     CONTROL "", 1, "STATIC", SS_LEFT, 5, 5, 80, 10
     CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 80, 12
    }

    Добавим этот диалог к ресурсам примера 4.
    В текст программы добавим две глобальных переменных:

    char buf[256]=""; /* строка для текстового поля в диалоге */
    HINSTANCE h;      /* дескриптор экземпляра программы */

    Присвоим переменной h значение дескриптора экземпляра программы в самом начале
    функции WinMain. Это значение нам потребуется для вызова функции
    DialogBox.

    Изменим обработчик сообщения WM_COMMAND следующим образом:

      case WM_COMMAND:
        switch (LOWORD(wp)) {
         case 6:  /* команда меню Exit */
          PostQuitMessage(0);
         default: /* все остальные команды */
          wsprintf(buf,"Command code: %d",LOWORD(wp));
          DialogBox(h,"Ex4_Dlg",hw,DlgProc);
        }
        return 0;

    Теперь в текст программы необходимо добавить диалоговую процедуру:

    BOOL CALLBACK DlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
     switch (msg) {
      case WM_INITDIALOG: /* сообщение о создании диалога */
       SetDlgItemText(hw,1,buf);
       return TRUE;
      case WM_COMMAND:    /* сообщение от управляющих элементов */
       if (LOWORD(wp)==2) EndDialog(hw,0);
     }
     return FALSE;
    }

    При создании диалога вызывается процедура SetDlgItemText,
    меняющая содержание текстового поля в диалоге (элемент с id=1).
    Для уничтожения диалога используется кнопка «OK», генерирующая
    сообщение WM_COMMAND с id=2.

    Функция DlgProc должна быть определена или описана
    до ссылки на нее в вызове DialogBox.

  • Управляющие элементы
  • Управляющие элементы, как и другие окна, принадлежат тому или
    иному классу окон. Windows предоставляет несколько предопределенных
    классов управляющих элементов. Программа может создавать управляющие
    элементы поштучно при помощи функции CreateWindow
    или оптом, загружая их вместе с шаблоном диалога из своих ресурсов.
    Управляющие элементы — это всегда дочерние окна. Управляющие элементы
    при возникновении некоторых событий, связанных с реакцией пользователя,
    посылают своему родительскому окну сообщения-оповещения
    (notification messages) WM_COMMAND или WM_NOTIFY.

    Как и любое другое окно, управляющий элемент может быть скрыт или
    отображен при помощи функции ShowWindow. Аналогично,
    управляющий элемент может быть блокирован или разблокирован при помощи
    функции:

    BOOL WINAPI EnableWindow(HWND hw,BOOL bEnable)

    В качестве второго параметра передается флаг TRUE (разблокировать)
    или FALSE (блокировать). Функция возвращает значение TRUE,
    если перед ее вызовом окно было заблокировано. Узнать текущий статус
    блокирования окна можно при помощи функции:

    BOOL WINAPI IsWindowEnabled(HWND hw),

    которая возвращает значение TRUE, если окно разблокировано.

    Для многих управляющих элементов определены специальные сообщения,
    которые управляют видом или поведением таких элементов или позволяют
    получить параметры их состояния. Как и для любого другого окна
    эти сообщения можно отправить с помощью функции:

    LRESULT WINAPI SendMessage(HWND hw, UINT msg, WPARAM wp, LPARAM lp)

    Все упомянутые функции работают с дескриптором окна, который для
    управляющих элементов в случае создания диалога по шаблону из ресурсов
    непосредственно неизвестен, но может быть получен по дескриптору диалога
    и идентификатору управляющего элемента вызовом:

    HWND WINAPI GetDlgItem(HWND hDlg, int idDlgItem)

    Для функции отсылки сообщений есть специальный вариант, предназначенный
    для более удобной работы с управляющими элементами:

    LRESULT WINAPI SendDlgItemMessage(
      HWND hwndDlg,	 /* дескриптор родительского диалога */
      int idControl, /* идентификатор управляющего элемента */
      UINT msg,      /* код сообщения */
      WPARAM wp,     /* параметр сообщения */
      LPARAM lp      /* параметр сообщения */
    )

    Для управляющих элементов внутри диалогов специальный смысл имеют
    стили WS_TABSTOP и WS_GROUP. Если в диалоге
    имеются управляющие элементы со стилем WS_TABSTOP, то при нажатии
    пользователем на клавишу [Tab] (или [Shift]+[Tab]), текущий активный элемент
    диалога будет терять фокус и передавать его следующему за ним (или предыдущему)
    ближайшему элементу со стилем WS_TABSTOP. С помощью стиля
    WS_GROUP элементы диалога можно объединять в группы. Группа
    элементов начинается с элемента со стилем WS_GROUP и заканчивается
    элементом, после которого идет элемент со стилем WS_GROUP, или
    последним элементом в диалоге. Внутри группы только первый элемент
    должен иметь стиль WS_GROUP. Windows допускает перемещение
    внутри группы при помощи клавиш-стрелок.

    Классы предопределенных управляющих элементов:

    «STATIC»
    Статический управляющий элемент представляет собой
    текстовую метку или прямоугольник. Не предоставляет пользователю
    ни средств ввода, ни вывода. Примеры:

    Соответствующее описание ресурсов:

     CONTROL "",-1, "STATIC", SS_BLACKFRAME,  5, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_GRAYFRAME,  30, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_WHITEFRAME, 55, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_BLACKRECT,  80, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_GRAYRECT,  105, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_WHITERECT, 130, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_ETCHEDFRAME,155, 40, 20, 10
    
     /* Для статиков-иконок или картинок текстовое поле определяет имя ресурса */
     CONTROL "Ex4_Bmp",-1, "STATIC", SS_BITMAP, 5, 55, -1, -1
     CONTROL "Ex4_Icon",-1, "STATIC", SS_ICON, 65, 55, -1, -1
    
     CONTROL "text",-1, "STATIC", SS_LEFT,    105, 55, 20, 10
     CONTROL "text",-1, "STATIC", SS_CENTER,  130, 55, 20, 10
     CONTROL "text",-1, "STATIC", SS_RIGHT,   155, 55, 20, 10
    
     /* По умолчанию SS_LEFT, SS_RIGHT, SS_CENTER делают перенос по словам */
     CONTROL "This is long text example",-1, "STATIC", SS_SIMPLE|SS_SUNKEN, 65, 70, 55, 15
     CONTROL "This is long text example",-1, "STATIC", SS_LEFT|SS_SUNKEN,  125, 70, 50, 15

    Для текстовых статиков со стилями SS_LEFT, SS_RIGHT
    или SS_CENTER существуют более простые операторы объявления ресурсов:

     LTEXT "text",-1, 105, 55, 20, 10
     CTEXT "text",-1, 130, 55, 20, 10
     RTEXT "text",-1, 155, 55, 20, 10
    
     LTEXT "This is long text example",-1, 65, 70, 55, 15, SS_LEFTNOWORDWRAP|SS_SUNKEN
     LTEXT "This is long text example",-1,125, 70, 50, 15, SS_LEFT|SS_SUNKEN

    Чтобы поменять текст статика ему можно послать сообщение WM_SETTEXT
    (wp=0; lp=(LPARAM)(LPCSTR)lpsz — адрес строки) или
    использовать функции:

    BOOL WINAPI SetWindowText(HWND hw, LPCSTR lpsz)
    BOOL WINAPI SetDlgItemText(HWND hDlg, int idControl, LPCTSTR lpsz)

    Чтобы сменить иконку или картинку нетекстового статика, надо послать
    ему сообщение STM_SETIMAGE (wp=(WPARAM)fImageType
    тип изображения: IMAGE_BITMAP или IMAGE_ICON;
    lp=(LPARAM)(HANDLE)hImage — дескриптор иконки или картинки).

    «BUTTON»
    Кнопка — это небольшое прямоугольное дочернее окно, обычно
    имеющее два состояния: нажато/отпущено или включено/выключено.
    Пользователь меняет состояние этого элемента щелчком мыши.
    К этому классу относятся: кнопки-«давилки» (push buttons),
    кнопки-«галочки» (check boxes), «радио»-кнопки (radio buttons)
    и специальный тип групповых рамочек (group boxes). Примеры:

    Соответствующее описание ресурсов:

     /* DEFPUSHBUTTON - кнопка по умолчанию (нажимается по [Enter]) */
     CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 50, 12
     CONTROL "text", 3, "BUTTON", BS_PUSHBUTTON, 60, 20, 50, 12
    
     CONTROL "GroupBox1", -1, "BUTTON", BS_GROUPBOX, 5, 35, 50, 50
    
     CONTROL "text", 4, "BUTTON", BS_CHECKBOX,     10, 45, 30, 10
     CONTROL "text", 5, "BUTTON", BS_AUTOCHECKBOX, 10, 57, 30, 10
     CONTROL "text", 6, "BUTTON", BS_AUTO3STATE,   10, 69, 30, 10
    
     CONTROL "GroupBox2", -1, "BUTTON", BS_GROUPBOX, 60, 35, 50, 50
    
     CONTROL "text", 7, "BUTTON", BS_AUTORADIOBUTTON|WS_GROUP, 65, 45, 30, 10
     CONTROL "text", 8, "BUTTON", BS_AUTORADIOBUTTON, 65, 57, 30, 10
     CONTROL "text", 9, "BUTTON", BS_AUTORADIOBUTTON, 65, 69, 30, 10

    Для кнопок существуют более простые операторы объявления ресурсов:

     DEFPUSHBUTTON text, id, x, y, width, height [, style]
     PUSHBUTTON text, id, x, y, width, height [, style]
     GROUPBOX text, id, x, y, width, height [, style]
     CHECKBOX text, id, x, y, width, height [, style]
     RADIOBUTTON text, id, x, y, width, height [, style]

    Разница между стилями BS_xxx и BS_AUTOxxx
    заключается в том, что при щелчке по AUTO-кнопкам Windows
    сама автоматически переключает их состояние. Для не AUTO-кнопок
    это надо делать вручную в диалоговой процедуре, послав сообщение
    BM_SETCHECK (wp=(WPARAM)fCheck — флаг:
    BST_UNCHECKED, BST_CHECKED или BST_INDETERMINATE
    (для BS_3STATE-кнопок); lp=0) или при помощи функций:

    BOOL WINAPI CheckDlgButton(HWND hDlg, int idButton, UINT fCheck)
    BOOL WINAPI CheckRadioButton(
        HWND hDlg,         /* дескриптор родительского диалога */
        int idFirstButton, /* id первой радио-кнопки в группе */
        int idLastButton,  /* id последней радио-кнопки в группе */
        int idCheckButton  /* id отмечаемой радио-кнопки */
    )

    Автоматические радио-кнопки должны быть объединены в группу
    при помощи стиля WS_GROUP, чтобы Windows корректно их
    обрабатывала.
    Проверить состояние кнопки можно, послав ей сообщение BM_GETCHECK
    (wp=0; lp=0) или вызовом функции:

    UINT WINAPI IsDlgButtonChecked(HWND hDlg, int idButton)

    При щелчке мыши по кнопке она присылает родительскому диалогу сообщение-оповещение
    WM_COMMAND (HIWORD(wp)=BN_CLICKED; LOWORD(wp)=(int)idButton;
    lp=(HWND)hwndButton
    ).

    «EDIT»
    Поле редактирования предназначено для ввода пользователем текста
    с клавиатуры. Щелчком мыши внутри элемента пользователь передает
    этому элементу фокус ввода (input focus).
    При этом внутри элемента появляется текстовый курсор — мигающая
    каретка. Пользователь может использовать мышь для перемещения
    каретки по полю редактирования и выделению текста в этом поле. Примеры:

    Соответствующее описание ресурсов:

     /* По умолчанию эти элементы создаются вообще без рамки, поэтому добавлено WS_BORDER */
     CONTROL "" 4, "EDIT", ES_MULTILINE|ES_WANTRETURN|WS_BORDER 5, 45, 60, 35
    
     CONTROL "text", 5, "EDIT", ES_LEFT|WS_BORDER, 70, 45, 30, 10
     CONTROL "text", 6, "EDIT", ES_CENTER|ES_PASSWORD|WS_BORDER, 70, 57, 30, 10
     CONTROL "text", 7, "EDIT", ES_RIGHT|ES_READONLY|WS_BORDER, 70, 69, 30, 10

    Стиль ES_WANTRETURN означает, что кнопка [Enter] будет
    обрабатываться самим элементом, а не передаваться диалогу. Благодаря
    этому стилю оказался возможен переход на новую строчку для предложения
    «Она съела кусок…» (на картинке).
    По умолчанию текстовые поля позволяют вводить столько текста,
    сколько может отобразиться в рамках поля. Чтобы предоставить
    пользователю возможность ввести больше текста, надо использовать
    стиль ES_AUTOHSCROLLES_AUTOVSCROLL
    для многострочных полей).
    Для текстовых полей существует более простой оператор объявления ресурсов:

     EDITTEXT id, x, y, width, height [, style]

    Чтобы поменять содержимое текстового поля, программа вызывает
    функцию SetDlgItemText. Чтобы получить текущее
    содержимое текстового поля, используется функция:

    UINT WINAPI GetDlgItemText(
        HWND hDlg,       /* дескриптор родительского диалога */
        int idControl,   /* идентификатор поля */
        LPSTR lpString,  /* буфер под текст */
        int nMaxCount    /* размер буфера */
    )

    Чтобы узнать размер строки в текстовом поле, надо послать элементу
    сообщение WM_GETTEXTLENGTH (wp=0; lp=0).
    Текстовое поле посылает родительскому диалогу следующие сообщения-оповещения
    WM_COMMAND (LOWORD(wp)=(int)idContol; lp=(HWND)hwndEditCtrl):

    • HIWORD(wp)=EN_KILLFOCUS — текстовое поле потеряло фокус
      (фокус передан другому элементу диалога);
    • HIWORD(wp)=EN_SETFOCUS — текстовое поле получило фокус;
    • HIWORD(wp)=EN_CHANGE — пользователь изменил текст в поле;
    • HIWORD(wp)=EN_ERRSPACE — закончилось место, отведенное под
      текстовый буфер управляющего элемента.
    «LISTBOX»
    Окно-список используется для отображения списка имен
    (например, имен файлов). Пользователь может, просматривая список,
    выделить один или несколько элементов щелчком мыши. При выделении
    того или иного элемента списка, он подсвечивается, а родительскому
    окну посылается сообщение-оповещение. Для очень больших списков
    могут использоваться полосы прокрутки. Примеры:

    Соответствующее описание ресурсов:

     CONTROL "" 3, "LISTBOX", LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL, 5, 45, 60, 35
     CONTROL "" 4, "LISTBOX", LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL, 70, 45, 60, 35
     CONTROL "" 5, "LISTBOX", LBS_SORT|LBS_NOSEL|WS_BORDER, 135, 45, 60, 35

    или в короткой форме:

     LISTBOX 3, 5, 45, 60, 35, LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL
     LISTBOX 4, 70, 45, 60, 35, LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL
     LISTBOX 5, 135, 45, 60, 35, LBS_SORT|LBS_NOSEL|WS_BORDER

    Для добавления элемента к списку следует послать ему сообщение
    LB_ADDSTRING (wp=0; lp=(LPARAM)(LPCSTR)lpsz
    строка для добавления). Для того, чтобы заполнить один из списков,
    показанных на рисунке, в обработчик сообщения WM_INITDIALOG
    в диалоговую процедуру был вставлен такой фрагмент:

       char *a[12]={
        "jan","feb","mar","apr","may","jun",
        "jul","aug","sep","oct","nov","dec"};
       for (int i=0; i<12; i++) {
        SendDlgItemMessage(hw,3,LB_ADDSTRING,0,(LPARAM)a[i]);
       }

    Кроме этого, список «понимает» следующие сообщения:

    • LB_DELETESTRING (wp=(WPARAM)index; lp=0) —
      удалить элемент с указанным номером;
    • LB_INSERTSTRING (wp=(WPARAM)index; lp=(LPARAM)(LPCSTR)lpsz) —
      вставить указанную строку в список как элемент с индексом index;
    • LB_FINDSTRING (wp=(WPARAM)indexStart; lp=(LPARAM)(LPTSTR)lpszFind) —
      найти элемент, содержащий указанную строку (поиск ведется, начиная
      с элемента indexStart), результат сообщения — номер элемента,
      удовлетворяющего критерию, или LB_ERR;
    • LB_GETCOUNT (wp=0; lp=0) — количество элементов в списке;
    • LB_GETCURSEL (wp=0; lp=0) — выделенный элемент в списке;
    • LB_RESETCONTENT (wp=0; lp=0) — удалить все элементы из списка.

    Окно-список посылает родительскому диалогу следующие сообщения-оповещения
    WM_COMMAND (LOWORD(wp)=(int)idContol; lp=(HWND)hwndListBox):

    • HIWORD(wp)=LBN_DBLCLK — пользователь дважды щелкнул мышью по списку;
    • HIWORD(wp)=LBN_SELCHANGE — пользователь выделил другой элемент
      в списке (или отменил выделение).
    «COMBOBOX»
    Комбобокс — это помесь поля редактирования с окном-списком.
    Этот элемент содержит поле редактирование и список, который может
    отображаться все время либо «выпадать» при нажатии на кнопку рядом
    с полем редактирования. Есть три основных типа комбобоксов:

    • «выпадающий» комбобокс (CBS_DROPDOWN) содержит поле
      редактирования и «выпадающий» список;
    • «выпадающий» список (CBS_DROPDOWNLIST) не содержит
      поля для изменения текста;
    • простой комбобокс (CBS_SIMPLE) содержит поле
      редактирования и обычный список.

    Обратите внимание, что в ресурсах значение высоты элемента определяет
    размер поля редактирования вместе с «выпавшим» списком по вертикали.

     CONTROL "" 3, "COMBOBOX", CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL, 5, 45, 60, 70
     CONTROL "" 4, "COMBOBOX", CBS_DROPDOWNLIST|WS_VSCROLL, 70, 45, 60, 70
     CONTROL "" 5, "COMBOBOX", CBS_SIMPLE|CBS_SORT, 135, 45, 60, 70

    Короткий вариант этого же объявления ресурсов:

     COMBOBOX 3, 5, 45, 60, 70, CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL
     COMBOBOX 4, 70, 45, 60, 70, CBS_DROPDOWNLIST|WS_VSCROLL
     COMBOBOX 5, 135, 45, 60, 70, CBS_SIMPLE|CBS_SORT

    Для работы с комбобоксами существуют сообщения, аналогичные списковым:
    CB_ADDSTRING, CB_DELETESTRING, CB_INSERTSTRING,
    CB_FINDSTRING, CB_GETCOUNT, CB_GETCURSEL,
    CB_RESETCONTENT.
    Комбобокс посылает родительскому диалогу сообщение оповещение WM_COMMAND
    со следующими кодами оповещения:

    • CBN_SELCHANGE, когда пользователь выделяет другую строку
      в комбобоксе (бывает полезным для простых комбобоксов);
    • CBN_SELENDOK, когда пользователь выбрал элемент в выпадающем
      списке и щелкнул мышкой по выделению (подтвердил выделение), для
      простых комбобоксов посылается перед каждым CBN_SELCHANGE;
    • CBN_SELENDCANCEL, когда пользователь закрыл выпадающий список,
      так и не выбрав никакой элемент;
    • CBN_DROPDOWN, когда открывается выпадающий список;
    • CBN_CLOSEUP, когда выпадающий список был закрыт по той или
      иной причине.

    Кроме предопределенных управляющих элементов, Windows предоставляет еще
    набор стандартных управляющих элементов посредством библиотеки
    Common Controls Library (COMCTL32.DLL). Чтобы воспользоваться ей,
    в тест программы надо включить заголовочный файл commctrl.h
    и добавить в блок инициализации программы вызов функции:

    void WINAPI InitCommonControls(void)

    Управляющие элементы из этой библиотеки, как правило, посылают
    сообщения-оповещения родительскому диалогу через сообщение WM_NOTIFY
    (wp=(int)idControl; lp=(LPARAM)(NMHDR*)pmnh — указатель на структуру
    со специльными параметрами сообщения-оповещения).

    Классы управляющих элементов из Common Controls Library:

    List View Controls (WC_LISTVIEW)
    Элемент просмотра списков — это окно отображающее совокупность
    элементов. Каждый элемент может быть представлен текстовой меткой и
    (необязательно) иконкой. Типичный пример использования этого элемента —
    программа «Проводник». Содержимое того или иного каталога представляется
    в виде элемента просмотра списков. Есть четыре основных стиля для этого
    элемента:

    • крупные иконки — стиль LVS_ICON;
    • мелкие иконки — стиль LVS_SMALLICON;
    • список — стиль LVS_LIST;
    • таблица — стиль LVS_REPORT.

     CONTROL "",3,WC_LISTVIEW,LVS_REPORT|WS_BORDER, 5, 45, 60, 70
     CONTROL "",4,WC_LISTVIEW,LVS_LIST|WS_BORDER, 70, 45, 120, 70
     CONTROL "",5,WC_LISTVIEW,LVS_ICON|WS_BORDER, 200, 45, 120, 70

    Приведенные в примере списки заполнялись в диалоговой
    процедуре при инициализации диалога (WM_INITDIALOG):

       /* Создание колонок для 1го списка */
       LV_COLUMN lc; lc.mask=LVCF_FMT|LVCF_TEXT|LVCF_SUBITEM|LVCF_WIDTH;
       lc.fmt=LVCFMT_LEFT;
       lc.pszText="Col1"; lc.iSubItem=0; lc.cx=40;
       SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,0,(LPARAM)&lc);
       lc.pszText="Col2"; lc.iSubItem=1; lc.cx=40;
       SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,1,(LPARAM)&lc);
    
       /* Создание списка иконок для 2го и 3го списков */
       HIMAGELIST himl1,himl2;
       himl1=ImageList_Create(16,16,ILC_MASK,1,0);   /* список маленьких иконок */
       ImageList_AddIcon(himl1,LoadIcon(h,"Ex4_Icon"));
       himl2=ImageList_Create(32,32,ILC_MASK,1,0);   /* список больших иконок */
       ImageList_AddIcon(himl2,LoadIcon(h,"Ex4_Icon"));
       SendDlgItemMessage(hw,4,LVM_SETIMAGELIST,LVSIL_SMALL,(LPARAM)himl1);
       SendDlgItemMessage(hw,5,LVM_SETIMAGELIST,LVSIL_NORMAL,(LPARAM)himl2);
    
       /* Заполнение списков */
       LV_ITEM li; li.mask=LVIF_TEXT|LVIF_IMAGE; 
       li.iImage=0; /* номер иконки в списке */
       for (int i=0; i<12; i++) {
        li.iItem=i;
        li.iSubItem=0; li.pszText=a[i];
        SendDlgItemMessage(hw,3,LVM_INSERTITEM,0,(LPARAM)&li);
        SendDlgItemMessage(hw,4,LVM_INSERTITEM,0,(LPARAM)&li);
        SendDlgItemMessage(hw,5,LVM_INSERTITEM,0,(LPARAM)&li);
        wsprintf(str,"%d",i); /* вторая колонка для 1го списка */
        li.iSubItem=1; li.pszText=str;
        SendDlgItemMessage(hw,3,LVM_SETITEM,0,(LPARAM)&li);
       }
    
    Status Windows (STATUSCLASSNAME)
    Поле статуса — это горизонтальное окно в нижней части
    родительского окна, которое программа обычно использует для отображения
    каких-либо характеристик, параметров или небольших текстовых сообщений.

    В примере 4б можно заменить статическое текстовое поле
    на поле статуса:

     Ex4_Dlg DIALOG 50,50,70,40
      STYLE WS_POPUP|WS_CAPTION|WS_BORDER
      CAPTION "MyDlg"
      FONT 10, "Arial"
     {
      CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 10, 60, 12
      CONTROL "Status text",1,STATUSCLASSNAME, 0, 0, 0, 0, 0
     }

    При создании поля статуса не требуется указывать ни размер, ни позицию
    окна, Windows сама выберет эти параметры подходящим образом.
    Для создания поля статуса можно использовать специальную функцию:

      HWND WINAPI CreateStatusWindow(
        LONG style,       /* стиль: обязательно указываем WS_CHILD|WS_VISIBLE */
        LPCTSTR lpszText, /* текст поля статуса */
        HWND hwndParent,  /* родительское окно */
        UINT wID          /* числовой id поля статуса */
      )

    С помощью сообщения SB_SETPARTS поле статуса можно разбить
    на части (wp=(int)nParts — количество частей;
    lp=(LPARAM)(int*)widths — указатель на массив размеров частей).
    В таком случае текст для каждой части поля статуса задается сообщением
    SB_SETTEXT (wp=(int)iPart — номер части;
    lp=(LPARAM)(LPSTR)lpszText — текстовая строка). Пример:

     CreateStatusWindow(WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP,"text",hw,100);
     int widths[5]={100,150,200,-1};
     SendDlgItemMessage(hw,100,SB_SETPARTS,4,(LPARAM)widths);
     SendDlgItemMessage(hw,100,SB_SETTEXT,2,(LPARAM)"part 2");
     SendDlgItemMessage(hw,100,SB_SETTEXT,3,(LPARAM)"last part");
    Up-Down Controls (UPDOWN_CLASS)
    Управляющий элемент «up-down» представляет собой пару небольших
    кнопок-стрелок, нажимая которые, пользователь увеличивает или уменьшает
    значение. Этот элемент, как правило, связывается с элементом-компаньоном
    (buddy window), обычно реализованным в виде поля редактирования.
    Для пользователя элемент «up-down» и его компаньон представляются единым
    управляющим элементом.

     CONTROL "0", 5, "EDIT", ES_LEFT|WS_BORDER, 5, 30, 30, 10
     CONTROL "", 6, UPDOWN_CLASS, UDS_AUTOBUDDY|UDS_SETBUDDYINT, 35, 30, 10, 10

    Если при создании элемента «up-down» указать стиль UDS_AUTOBUDDY,
    то компаньоном будет назначен предыдущий управляющий элемент диалога.
    Программа может также передать дескриптор окна-компаньона при помощи
    сообщения UDM_SETBUDDY (wp=(WPARAM)(HWND)hwndBuddy
    дескриптор окна-компаньона; lp=0). Если элементу «up-down»
    назначить стиль UDS_SETBUDDYINT, то он будет автоматически
    менять текст окна-компаньона, представляющий числовое значение.
    Другой способ создать элемент «up-down» — использовать функцию

    HWND WINAPI CreateUpDownControl(
      DWORD dwStyle,   /* стиль элемента */
      int x, int y,    /* позиция */	
      int cx, int cy,  /* размеры */
      HWND hParent,    /* дескриптор родительского окна */
      int ID,          /* id элемента */
      HINSTANCE hInst, /* дескриптор экземпляра программы */
      HWND hBuddy,     /* дескриптор окна-компаньона */
      int nUpper,      /* максимальное значение */
      int nLower,      /* минимальное значение */
      int nPos         /* текущее значение */
    )
    Progress Bars (PROGRESS_CLASS)
    Полоса прогресса — это окно, которое программа может использовать
    для индикации состояния выполнения какой-либо длительной операции.
    Окно представляет собой прямоугольник, заполняемый системным цветом
    слева направо.

       CONTROL "",3,PROGRESS_CLASS,WS_BORDER, 5, 45, 100, 10

    Каждый раз, когда приложение посылает этому окну сообщение
    PBM_STEPIT (wp=0; lp=0), заполнение полосы прогресса
    продвигается дальше вправо на некоторое значение.

    Tooltip Controls (TOOLTIPS_CLASS)
    Окно-подсказка — всплывающее окно, содержащее строку описательной
    информации о том или ином элементе интерфейса программы. Таким элементом
    интерфейса может быть конкретное окно (управляющий элемент) или прямоугольный
    участок клиентской области какого-либо окна. Большую часть времени
    подсказка скрыта. Она появляется, когда пользователь задерживат курсор
    мыши над тем или иным элементом интерфейса программы более, чем на полсекунды.
    Подсказка скрывается, когда пользователь кликает мышью или уводит курсор
    с этого элемента. Одно окно-подсказка может обслуживать любое количество
    элементов интерфейса. Чтобы назначить тому или иному элементу интерфейса
    программы подсказку, надо окну-подсказке послать сообщение TTM_ADDTOOL
    (wp=0; lp=(LPARAM)(TOOLINFO*)lpti — указатель на структуру,
    содержащую информацию об элементе). Пример:

      TOOLINFO ti;
      HWND hwTooltip=CreateWindow(TOOLTIPS_CLASS,"",TTS_ALWAYSTIP,
       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL,h,NULL);
      ti.cbSize=sizeof(TOOLINFO);
      ti.uFlags=TTF_SUBCLASS|TTF_IDISHWND;
      ti.hwnd=hMainWnd;
      ti.uId=(UINT)GetDlgItem(hMainWnd,ID_MYBUTTON);
      ti.hinst=h;
      ti.lpszText="Tooltip for my button";
      SendMessage(hwTooltip,TTM_ADDTOOL,0,(LPARAM)&ti);
    Property Sheets & Tab Controls
    Элементы вкладки свойств и переключатели вкладок
    обычно используются совместно. Пример использования вкладок:

    Каждая вкладка содержит свой набор управляющих элементов и может
    задаваться в ресурсах так же, как и отдельный диалог:

     /* Шаблон вкладки "Общие" */
     ODTab_General DIALOG 0,0,180,150
      CAPTION "Общие"
      STYLE WS_CHILD|WS_VISIBLE
      FONT 8,"Arial"
     {
      GROUPBOX "Интерфейс",-1,5,5,170,40
       PUSHBUTTON "Шрифт температуры",IDC_FONT,20,15,140,12
       AUTOCHECKBOX "На полный экран",IDC_FULLSCR,20,30,140,10,WS_DISABLED
      GROUPBOX "Алгоритм",-1,5,50,170,80
       LTEXT "Приоритет:",-1,15,65,50,10
       CTEXT "priority",IDC_PRIOTEXT,70,56,85,8
       CONTROL "",IDC_PRIOTRACK,TRACKBAR_CLASS,TBS_TOP|TBS_AUTOTICKS,70,64,85,17
       LTEXT "Период опроса датчика:",-1,15,85,110,10
       EDITTEXT IDC_TRATE,125,85,25,10,ES_RIGHT
       LTEXT "мс",-1,152,85,10,10
       LTEXT "Период опроса формирователя:",-1,15,100,110,10
       EDITTEXT IDC_FRATE,125,100,25,10,ES_RIGHT
       LTEXT "мс",-1,152,100,10,10
       LTEXT "Тайм-аут:",-1,15,115,110,10
       EDITTEXT IDC_TIMEOUT,125,115,25,10,ES_RIGHT
       LTEXT "мс",-1,152,115,10,10
     }
     /* Шаблон вкладки "Датчик" */
     ODTab_Sensor DIALOG 0,0,180,150
      CAPTION "Датчик"
      STYLE WS_CHILD|WS_VISIBLE
      FONT 8,"Arial"
     {
      /* ... */
     }
     /* Шаблон вкладки "Порты" */
     ODTab_Ports DIALOG 0,0,200,150
      CAPTION "Порты"
      STYLE WS_CHILD|WS_VISIBLE
      FONT 8,"Arial"
     {
      /* ... */
     }

    Для создания диалога с вкладками используется функция PropertySheet,
    перед вызовом которой надо заполнить соответствующие системные структуры:

     char *TabTemplts[NumTabs]={"ODTab_General","ODTab_Sensor","ODTab_Ports"};
     PROPSHEETPAGE psp[NumTabs]; /* заполняется для каждой вкладки */
     for (i=0; i<NumTabs; i++) {
      psp[i].dwSize=sizeof(PROPSHEETPAGE);
      psp[i].dwFlags=PSP_DEFAULT;
      psp[i].hInstance=hThisInstance;
      psp[i].pszTemplate=TabTemplts[i];
      psp[i].pfnDlgProc=(DLGPROC)GlobDlgProc;
      psp[i].pfnCallback=NULL;
     }
     PROPSHEETHEADER psh;        /* описывает весь диалог */
     psh.dwSize=sizeof(PROPSHEETHEADER);
     psh.dwFlags=PSH_NOAPPLYNOW|PSH_PROPSHEETPAGE;
     psh.hwndParent=hWnd;
     psh.hInstance=hThisInstance;
     psh.pszCaption="Настройки";
     psh.nPages=NumTabs;
     psh.nStartPage=0;
     psh.ppsp=(LPCPROPSHEETPAGE)&psp;
     psh.pfnCallback=NULL;
     PropertySheet(&psh);

    Для каждой вкладки может быть своя диалоговая процедура, а может быть общая
    для всех вкладок (как в этом примере).

    Trackbars (TRACKBAR_CLASS)
    Ползунок (бегунок) используется, если от пользователя
    требуется получить дискретное значение из определенного диапазона.
    Маркер ползунка пермещается на заданное программой значение.
    Пример ползунка показан на первой вкладке предыдущего примера.
    Ползунки бывают горизонтальные (TBS_HORZ) или вертикальные
    (TBS_VERT). Диапазон значений ползунка задается сообщением
    TBM_SETRANGE (wp=(BOOL)fRedraw — перерисовать маркер
    после изменения диапазона; lp=MAKELONG(lMinimum,lMaximum)
    диапазон значений: младшее слово — минимальное значение,
    старшее слово — максимальное значение). Переместить ползунок можно
    при помощи сообщения TBM_SETPOS (wp=TRUE;
    lp=(LONG)position — новая позиция ползунка). Чтобы получить
    текущее значение ползунка, следует послать ему сообщение TBM_GETPOS
    (wp=0; lp=0). Ползунок оповещает родительское окно о событиях
    через сообщение WM_HSCROLL (LOWORD(wp)=ScrollCode
    код события; HIWORD(wp)=posistion — позиция маркера ползунка;
    lp=(HWND)hwndTrackBar — дескриптор элемента).

    Toolbars (TOOLBARCLASSNAME)
    Панель инструментов — это окно, содержащее набор кнопок,
    посылающих командное сообщение родительскому окну, когда пользователь
    щелкает по ним. Как правило, кнопки на панели инструментов соответствуют
    часто используемым командам меню приложения. Панель инструментов
    располагается ниже строки меню.

    Информация о кнопках передается панели инструментов через структуру
    TBBUTTON, а для создания окна панели инструментов удобно
    использовать функцию CreateToolbarEx.

       TBBUTTON tbb[nButtons];
       /* 1я кнопка */
       tbb[0].iBitmap=STD_PROPERTIES;   /*id иконки*/
       tbb[0].idCommand=ID_OPTION;      /*id команды*/
       tbb[0].fsState=TBSTATE_ENABLED;  /*состояние*/
       tbb[0].fsStyle=TBSTYLE_BUTTON;   /*стиль*/
       tbb[0].iString=0;                /*подпись под кнопкой*/
       /* 2я кнопка */
       tbb[1].iBitmap=STD_REDOW;
       tbb[1].idCommand=ID_CALCOUT;
       tbb[1].fsState=TBSTATE_ENABLED;
       tbb[1].fsStyle=TBSTYLE_BUTTON; 
       tbb[1].iString=0;
       /* 3я кнопка */
       tbb[2].iBitmap=STD_UNDO;
       tbb[2].idCommand=ID_CALCIN;
       tbb[2].fsState=TBSTATE_ENABLED;
       tbb[2].fsStyle=TBSTYLE_BUTTON;
       tbb[2].iString=0;
       /* разделитель */
       tbb[3].fsStyle=TBSTYLE_SEP;
       /* и т.д. */
       HWND htb=CreateToolbarEx(
        (HWND) hWndMain,                /*дескриптор родительского окна*/
        (DWORD) WS_CHILD|WS_VISIBLE|TBSTYLE_TOOLTIPS, /*стиль*/
        (UINT) ID_TOOLBAR,              /*id окна*/
        (int) 15,                       /*количество иконок в указанном ресурсе*/
        (HINSTANCE) HINST_COMMCTRL,     /*дескриптор модуля, из которого берется ресурс с иконками*/
        (UINT) IDB_STD_SMALL_COLOR,     /*id ресурса с иконками*/
        (TBBUTTON*) tbb,                /*указатель на массив с информацией о кнопках*/
        (int) nButtons,                 /*количество кнопок на панели*/
        16,16,                          /*размеры кнопок*/
        15,15,                          /*размеры иконок для кнопок*/
        sizeof(TBBUTTON)                /*размер структуры TBBUTTON*/
       );
    Rich Edit Controls (RICHEDIT_CLASS)
    Продвинутое поле редактирования является развитием
    класса «EDIT» стандартных управляющих элементов. Элементы управления
    этого класса поддерживают форматирование текста (по отдельным символам и
    по отдельным абзацам) и позволяют внедрять OLE-объекты.

    Tree View Controls (WC_TREEVIEW)
    Элемент просмотра дерева позволяет представлять информацию
    об иерархии некоторых объектов (содержание документа, дерево каталогов
    файловой системы и т.п.) Каждый объект может быть представлен текстовой
    меткой и иконкой. Объект может иметь иерархию дочерних объектов,
    которая раскрывается по щелчку на этом элементе.

  • Стандартные диалоги
  • Windows предоставляет набор готовых стандартных диалогов посредством
    библиотеки Common Dialog Boxes Library (COMDLG32.DLL): диалог открытия
    и сохранения файла, диалог печати документа, диалог выбора цвета, шрифта и т.п.
    Чтобы создать один из перечисленных диалогов, надо заполнить определенную
    структуру и вызвать соответствующую функцию из этой библиотеки:

    • BOOL WINAPI ChooseColor(CHOOSECOLOR* lpcc)
      — создает диалог, отображающий палитру цветов и позволяющий
      пользователю выбрать тот или иной цвет или создать свой.
    • BOOL WINAPI ChooseFont(CHOOSEFONT* lpcf)
      — создает диалог, отображающий имена установленных в системе шрифтов,
      их кегль, стиль начертания и т.п.
    • BOOL WINAPI GetOpenFileName(OPENFILENAME* lpofn)
      и BOOL WINAPI GetSaveFileNAme(OPENFILENAME* lpofn)
      — создают диалог, отображающий содержимое того или иного каталога,
      и позвояющий пользователю выбрать уникальное имя файла для открытия или
      сохранения.
    • BOOL WINAPI PrintDlg(PRINTDLG* lppd)
      — создает диалог, позволяющий пользователю установить различные
      опции печати, например, диапазон страниц, количество копий и др.
    • BOOL WINAPI PageStupDlg(PAGESETUPDLG* lppsd)
      — создает диалог, позволяющий пользователю выбрать различные
      параметры страницы: ориентацию, поля, размер бумаги и т.п.
    • HWND WINAPI FindText(FINDREPLACE* lpfr)
      — создает диалог, позволяющий пользователю ввести строку для поиска
      и такие опции, как направление поиска.
    • HWND WINAPI ReplaceText(FINDREPLACE* lpfr)
      — создает диалог, позволяющий пользователю ввести строку для поиска,
      строку для замены и опции замены (направление поиска, область поиска).

    Все перечисленные диалоги, кроме последних двух, — модальные,
    т.е. указанная функция не вернет значение, пока пользователь тем или
    иным способом не закроет диалог. Значение TRUE означает,
    что пользователь закрыл диалог, нажав на «ОК», а соответствующая структура
    заполнена новыми значениями. Значение FALSE означает,
    что пользователь нажал на [Esc], выбрал команду системного меню «Закрыть»
    или нажал кнопку «Отмена», а соответствующая структура осталась неизменной.
    Пример использования диалога открытия файла:

      char filename[MAX_PATH]="";             /*буфер под имя файла*/
      OPENFILENAME of;
      of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; /*размер структуры OPENFILENAME*/
      of.hwndOwner=hw;                        /*дескриптор родительского окна*/
      of.hInstance=h;                         /*дескриптор экземпляра программы*/
      of.lpstrFilter="All files (*.*)\0*.*\0";/*фильтр файлов (тип)*/
      of.lpstrCustomFilter=NULL;              /*еще один фильтр: нам не надо*/
      of.nMaxCustFilter=0;                    /*нам не надо*/
      of.nFilterIndex=1;                      /*количество заданных нами фильтров*/
      of.lpstrFile=filename;                  /*адрес буфера под имя файла*/
      of.nMaxFile=MAX_PATH;                   /*размер буфера под имя файла*/
      of.lpstrFileTitle=NULL;                 /*буфер под рекомендуемый заголовок: нам не надо*/
      of.nMaxFileTitle=0;                     /*нам не надо*/
      of.lpstrInitialDir=NULL;                /*стартовый каталог: текущий*/
      of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; /*разные флаги*/
      if (GetOpenFileName(&of)) {
       /* действия в случае успешного выбора файла */
      }
    

  • Немодальный диалог в качестве главного окна
  • Ресурсы значительно облегчают работу программиста по созданию интерфейса
    программы, поэтому весьма привлекательно использовать ресурсы для описания
    главного окна. Это возможно в случае, если главное окно реализовано в виде
    немодального диалога.

    Пример 5 демонстрирует использование немодального диалога
    в приложении типа «блокнот».

    Файл example5.h содержит константы-идентификаторы команд меню
    и элементов диалога.

    #define IDC_OPEN	10
    #define IDC_SAVE	11
    #define IDC_SAVEAS	12
    #define IDC_EXIT	13
    #define IDC_ABOUT	14
    
    #define ID_EDIT		20
    #define ID_STATUS	21

    Файл example5.rc описывает ресурсы программы: иконку, меню и шаблон диалога.

    #include "example5.h"
    
    Ex5_Icon ICON "myicon.ico"
    
    Ex5_Menu MENU
    {
     POPUP "&File"
     {
      MENUITEM "&Open...", IDC_OPEN
      MENUITEM "&Save", IDC_SAVE
      MENUITEM "Save &As...", IDC_SAVEAS
      MENUITEM SEPARATOR
      MENUITEM "&Exit\tAlt-F4", IDC_EXIT
     }
     POPUP "&Help"
     {
      MENUITEM "&About...", IDC_ABOUT
     }
    }
    
    Ex5_Dlg DIALOG 50,50,300,200
     STYLE WS_OVERLAPPED|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_VISIBLE
     MENU "Ex5_Menu"
     CAPTION "Example 5"
     FONT 10, "Arial"
    {
     EDITTEXT ID_EDIT, 5, 5, 290, 180, ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL
     CONTROL "", ID_STATUS, STATUSCLASSNAME, 0, 0, 0, 0, 0
    }

    Файл example5.cpp — текст программы.

    #include <windows.h>
    #include <commctrl.h>
    #include <stdlib.h>
    #include "example5.h"
    
    BOOL CALLBACK MainProc(HWND,UINT,WPARAM,LPARAM);
    
    HINSTANCE hThisInstance;
    char filename[MAX_PATH]=""; /*буфер имени файла*/ 
    
    int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int) {
     hThisInstance=hInst;
     InitCommonControls();
     HWND hMainWnd=CreateDialog(hInst,"Ex5_Dlg",NULL,(DLGPROC)MainProc);
     if (!hMainWnd) return FALSE;
     MSG msg;                   /*цикл обработки событий*/
     while (GetMessage(&msg,NULL,0,0)) {
      if (!IsDialogMessage(hMainWnd,&msg)) {
       TranslateMessage(&msg); 
       DispatchMessage(&msg); 
      }
     }
     return msg.wParam; 
    }
    
    /* диалоговая процедура */
    BOOL CALLBACK MainProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
     static DWORD OldIcon=0;    /* id старой иконки диалога */
     static OPENFILENAME of;
     char* buf;
     HANDLE hf; 
     DWORD len,len1;
     switch (msg) {
      case WM_INITDIALOG:       /* меняем иконку диалога */
       OldIcon=SetClassLong(hw,GCL_HICON,(long)LoadIcon(hThisInstance,"Ex5_Icon"));
       return TRUE;
      case WM_COMMAND:
       switch (LOWORD(wp)) {
        case IDCANCEL:          /* посылается при закрытии диалога по [Esc]*/
        case IDC_EXIT:          /* команда меню "Exit" */
         DestroyWindow(hw);
         break;
        case IDC_OPEN:          /* команда меню "Open" */
         of.lStructSize=OPENFILENAME_SIZE_VERSION_400A;
         of.hwndOwner=hw;
         of.lpstrFilter="All files (*.*)\0*.*\0";
         of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0;
         of.nFilterIndex=1;
         of.lpstrFile=filename; of.nMaxFile=MAX_PATH;
         of.lpstrFileTitle=NULL; of.nMaxFileTitle=0;
         of.lpstrInitialDir=NULL;
         of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
         if (!GetOpenFileName(&of)) break;
         SetDlgItemText(hw,ID_STATUS,filename);
         /* открываем файл */
         hf=CreateFile(filename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
         if (hf==INVALID_HANDLE_VALUE) {
          MessageBox(hw,"Open failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         len=GetFileSize(hf,NULL);
         buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */
         if (!buf) {
          MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         ReadFile(hf,buf,len,&len1,NULL);
         buf[len1]=0;
         CloseHandle(hf);
         SetDlgItemText(hw,ID_EDIT,buf);
         free(buf);
         break;
        case IDC_SAVEAS:        /* команда меню "Save As" */
         of.lStructSize=OPENFILENAME_SIZE_VERSION_400A;
         of.hwndOwner=hw;
         of.lpstrFilter="All files (*.*)\0*.*\0";
         of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0;
         of.nFilterIndex=1;
         of.lpstrFile=filename; of.nMaxFile=MAX_PATH;
         of.lpstrFileTitle=NULL; of.nMaxFileTitle=0;
         of.lpstrInitialDir=NULL;
         of.Flags=OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY;
         if (!GetSaveFileName(&of)) break;
        case IDC_SAVE:          /* команда меню "Save" */
         if (lstrlen(filename)==0) {
          /* для нового файла - вызываем диалог "Save As" */
          PostMessage(hw,WM_COMMAND,IDC_SAVEAS,lp);
          break;
         }
         SetDlgItemText(hw,ID_STATUS,filename);
         /* сохраняем файл */
         hf=CreateFile(filename,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
         if (hf==INVALID_HANDLE_VALUE) {
          MessageBox(hw,"Save failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         len=SendDlgItemMessage(hw,ID_EDIT,WM_GETTEXTLENGTH,0,0);
         buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */
         GetDlgItemText(hw,ID_EDIT,buf,len+1);
         if (!buf) {
          MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         WriteFile(hf,buf,len,&len1,NULL);
         CloseHandle(hf);
         free(buf);
         break;
        case IDC_ABOUT:         /* команда меню "About" */
         MessageBox(hw,"Example N5 from http://dims.karelia.ru/win32","About",MB_OK|MB_ICONINFORMATION);
         break;
       }
       return TRUE;
      case WM_DESTROY:          /* при закрытии окна восстанавливаем старую иконку */
       SetClassLong(hw,GCL_HICON,(long)OldIcon);
       PostQuitMessage(0);
       return TRUE;
     }
     return FALSE;
    }

    Занятие первое

    Об инструментах

    Главное в Windows-программе

    Многие из вас хотели бы научиться разрабатывать программы для Windows, но, столкнувшись с трудностями, отступили. И теперь в редакцию приходят письма с просьбами

    Занятие первое


    Об инструментах
    Главное в Windows-программе

    Многие из вас хотели бы научиться разрабатывать программы для Windows, но, столкнувшись с трудностями, отступили. И теперь в редакцию приходят письма с просьбами открыть цикл статей, из которых бы читатели могли почерпнуть необходимую информацию о технике создания Windows-приложений. Признаться, автор этих строк и сам подумывал об этом, но никак не мог решиться. Однако почта, касающаяся вопроса Windows-программирования, которая была получена за последнее время, склонила чашу весов в сторону начала публикации. Обратите внимание на один важный момент: здесь вы не найдете тщательного «разжевывания», характерного для книг. Если вы совсем не знакомы с основными принципами функционирования Windows, непременно прочтите какую-нибудь книгу по данной тематике.

    Об инструментах

    За несколько последних лет программирование Windows-приложений претерпело значительные изменения, в первую очередь они связаны с переходом на 32-разрядные программы. Кроме того, наметилась тенденция все большего использования коммерческих библиотек классов, таких как Microsoft MFC, Borland OWL, Rogue Wave zApps и некоторых других.

    Как известно, на заре Windows практически все программы для этой, тогда еще оболочки, создавались с применением набора Microsoft Software Development Kit (SDK). В состав этого набора, который, кстати, продолжает выпускаться и сейчас, входит комплект полезных утилит, предназначенных для создания ресурсов приложений, тестирования программ и просто ускорения процесса разработки. SDK содержит полный набор библиотек для связывания Windows-программ, заголовочные файлы, электронную документацию и большое количество примеров программ на все случаи жизни.

    Мы с вами также будем опираться на SDK. И это вовсе не потому, что автор — фанат командной строки, не признающий достижений современного программирования, а потому, что исходные тексты Windows-приложений, разработанных на уровне программного интерфейса Windows (Windows API) без всяких библиотек классов, легко могут быть переработаны в готовые программы с помощью любого компилятора языков Си и Си++. Все, что потребуется, — это знание универсального программного интерфейса API Win32, на котором базируются приложения для Windows 95 и Windows NT.

    Пытавшиеся ранее программировать для Windows, наверняка вздрогнули на этом месте, вспоминая, как они мучились с API и как радовались появлению первых библиотек. А теперь им предлагают вернуться назад. Ну что же, если хотите, можете обойтись и без API. Но есть и контрдоводы. Главный из них: вы никогда не создадите программы для Windows, если не знаете API. Это не голословное утверждение. Разумеется, с помощью библиотеки можно создать работающий каркас с приятным внешним видом, но по мере усложнения вашей программы и добавления новых функций, вы все чаще и чаще будете сталкиваться с невозможностью решения проблем на уровне библиотек класса. И тогда вам опять придется прибегать к API. Кроме того, знание API дает понимание глубинных процессов, происходящих в ваших программах. А без этого, согласитесь, считать себя программистом нелепо. В лучшем случае это просто кодировщик.

    Но пусть уважаемый читатель не пугается трудностей. На самом деле многие фрагменты программ постоянно повторяются, так что их можно применять в разных местах. Помимо этого, мы будем использовать в нашей работе новый продукт компании Borland C++ Builder, представляющий собой аналог пакета Delphi, но построенного на базе языка программирования Си++. Это значительно облегчит нам работу, снимая с наших плеч труд по созданию окон программы, и позволит сконцентрироваться непосредственно на реализации логики программы.

    Главное в Windows-программе

    Любая программа для операционной системы Windows начинается всегда с одного и того же — вызова функции WinMain. Это точка входа в Windows-приложение, такая же, как функция main для DOS-программ. WinMain описана в документации к SDK следующим образом:

    int WINAPI WinMain(
     HINSTANCE hInstance, // дескриптор 
            // текущего экземпляра программы
     HINSTANCE hPrevInstance, // дескриптор 
             // предыдущего экземпляра программы 
     LPSTR lpCmdLine, 
             // указатель на командную строку
     int nCmdShow 
             // состояние показа окна программы
     );

    Я бы хотел извиниться перед читателями за использование сбивающего с толку слова дескриптор. Мне не удалось найти сколько-нибудь приличного перевода для слова handle. Предлагаемое некоторыми российскими издателями слово «описатель» совершенно не подходит по смыслу, а использовать словечко «хэндл» из программистского жаргона просто неуместно. Поэтому сойдемся на нейтральном слове «дескриптор», тем более, что чаще всего употребляется именно это слово.

    Разберем параметры, передаваемые функцей WinMain. Первый параметр, hInstance, представляет собой дескриптор экземпляра программы. В 16-разрядных версиях Windows этот параметр четко определял связь сегмента данных программы с ее экземпляром, позволяя определить точно, с каким из запущенных экземпляров программы мы работаем. С появлением 32-битовых программ, когда каждому экземпляру программы выделяется свой сегмент данных, не зависящий от других экземпляров этой же программы, параметр hInstance — это линейный базовый адрес, по которому программа загружена в память. Это значение для Windows 95 может быть от 0x0040000 и выше. Почему именно 0x0040000? Потому что, начиная с этого адреса памяти, загрузчик размещает программу при ее запуске. Обычно это значение можно изменить в опциях компоновщика (linker), но, как правило, это не требуется.

    Второй параметр, hPrevInstance, в 16-битовых программах трактуется, как дескриптор предыдущей запущенной копии данной программы. Но поскольку все экземпляры 32-битовых программ никак не связаны друг с другом, этот параметр всегда равен нулю, независимо от того, сколько экземпляров своей программы вы уже запустили. Это лишний раз подтверждает, что все запущенные копии 32-разрядных программ ничего друг о друге не знают.

    Третий параметр, lpCmdLine, является указателем на параметры командной строки, переданные вашей программе при запуске. Но это действует с одной поправкой. Дело в том, что, в отличие от командной строки программ DOS, вы не сможете извлечь из lpCmdLine полный путь к запущенной вами программе, и даже ее название. Все, что вы можете получить — это дополнительные параметры, передаваемые вместе с командной строкой. Если же вам нужно знать полную командную строку, то необходимо вызвать функцию Win32 API GetCommandLine().

    Последний параметр, nCmdShow, показывает, в каком виде после запуска пребывает окно вашей программы. В Win32 для этого определено 10 констант. Это довольно редко используемый параметр, поэтому не будем утруждаться его разбором.

    Итак, теперь вы знакомы с WinMain и, следовательно, готовы для написания пробной программы, которая поможет рассмотреть аспекты использования этой функции. Пусть наша программа покажет при запуске содержимое всех параметров, которые были переданы WinMain. Поскольку мы еще не разбирали процесс создания окна программы, то вместо последнего воспользуемся диалоговой панелью сообщений, которую можно получить вызовом функции API MessageBox(). Вот исходный текст нашей программы:

    Файл test1.c
    #include 
    #define STRICT
    
    int WINAPI WinMain(
     HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPSTR lpCmdLine,
     int nCmdShow )
    {
    char str[100];
    wsprintf(str, 
    "hInstance = %#08lx
    hPrevInstance = %#08lx
    
    lpCmdLine = %s
    nCmdShow = %u",
    hInstance, hPrevInstance, lpCmdLine, 
    nCmdShow);
    
    MessageBox(NULL, str, 
    "WinMain example", MB_OK);
    
     return 0;
    }       

    Произведите сборку программы с помощью любого 32-разрядного компилятора. Сгодится Borland C++ версий 4.x и 5.x, Watcom С++, Microsoft Visual C++ 4.x (я использовал Microsoft Visual C++ 4.2). Запустим нашу программу, набрав в командной строке «test1.exe Passed String». Лучше всего, если вы повторите эту операцию два-три раза.

    Внимательно посмотрите на параметры, которые показывает наша программа. Оказывается, между ними нет никакой разницы! Это очень важное открытие, подтверждающее сказанные ранее слова о том, что один экземпляр программы не знает о другом. Как и было обещано, система загрузила нашу программу по адресу 0x00400000, т. е. на отметке 4 Мбайт, о чем свидетельствует содержимое параметра hInstance. Обратите внимание, что все экземпляры программы загружены по одному и тому же адресу. Это не чудо, а умение процессоров Intel транслировать виртуальные адреса в физические. На самом деле, внутри физической памяти все запущенные экземпляры могут быть в совершенно разных ее уголках, а могут и вовсе быть сброшены на жесткий диск в процессе подкачки.

    Второй параметр, hPrevInstance, равен нулю у всех экземпляров, как уже было сказано. Параметр командной строки, а именно строка «Passed String», передана нашей программе без изменений. И наконец последний параметр, nCmdShow, содержит число 1, которому в API Win32 соответствует флаг SW_SHOWNORMAL. Этот флаг говорит о том, что окно отображается в нормальном режиме.

    Следующий обязательный блок для любой Windows-программы — цикл получения сообщений. Как вы, наверное, уже знаете, операционная система непрерывно бомбардирует программы потоком сообщений о тех или иных событиях. Большинство из них для программ бесполезны, и лишь небольшая их часть должна быть перехвачена и обработана. При запуске программы Windows создает очередь сообщений. Для получения сообщений из очереди внутри функции WinMain() должен быть специальный блок исходного текста. Он выглядит примерно так:

    MSG msg;
    while (GetMessage(&msg, (HWND) NULL, 0, 0))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }

    Разберем этот исходный текст подробнее. Прежде всего это цикл, созданный на основе оператора while. Разумеется, вы можете использовать операторы for и do-while. Данный цикл на базе while представляет собой лишь общепринятую форму. Цикл будет выполняться до тех пор, пока функция GetMessage() не возвратит FALSE. Это означает, что в очереди сообщений обнаружено сообщение WM_QUIT, которое говорит о том, что программа закончила свою работу и нужно прекратить обработку сообщений. Программист сам может послать такое сообщение, вызвав функцию API PostQuitMessage(), и передать через эту функцию код возврата из программы. Так вызов PostQuitMessage(100) приведет к завершению программы с кодом выхода 100.

    Функция GetMessage извлекает сообщение из очереди и сохраняет его в структуре MSG, адрес которой передается как первый параметр функции. Если полученное сообщение не WM_QUIT, то GetMessage возвратит TRUE, и цикл обработки сообщений будет продолжен. Второй параметр функции GetMessage — это дескриптор окна, от которого функция хочет получать сообщения. Если значение равно NULL, это означает, что мы хотим получать сообщения от всех окон, принадлежащих данной программе или потоку. Вообще-то этот параметр чаще всего равен NULL, потому что редко в практике программирования может возникнуть задача слежения за сообщениями конкретного окна.

    Предпоследний и последний параметры задают минимальное и максимальное числа, соответствующие минимальному и максимальному принимаемым сообщениям. Если эти параметры равны 0, то принимаются все возможные сообщения.

    После того как сообщение получено и упаковано в структуру MSG, за дело берутся две другие функции API: TranslateMessage() и DispatchMessage(). Функция TranslateMessage() занимается тем, что транслирует сообщения нажатия и отпускания клавиш WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN и WM_SYSKEYUP в сообщения WM_CHAR, WM_DEADCHAR, WM_SYSCHAR и WM_SYSDEADCHAR. Такая трансляция приводит к тому, что виртуальные коды клавиш транслируются в коды символов.

    DispatchMessage() завершает путь прохождения сообщений, пересылая их в оконную процедуру — сердце Windows-программы, определяющее поведение программы. Оконная процедура отбирает нужные ей сообщения, реагирует на них соответствующим образом, а необработанные сообщения пересылает системному обработчику DefWindowProc(). Обычно на долю именно этой функции выпадает основная работа по выполнению такой рутинной работы, как изменение размера окна, его перемещение и т. д.

    Следующим «номером» нашей программы будет методика создания окна. Как известно, большая часть программ в Windows имеют собственные окна. Поэтому программист должен уметь создавать окна и работать с ними. Создание окна в Windows происходит в три этапа. На первом этапе необходимо создать класс окна программы. В системе уже зарегистрировано несколько собственных классов различных окон, например класса «BUTTON», который служит для создания всех кнопок в Windows. Однако создание своего класса — это необходимость. Если, к примеру, вы создадите окно на базе своего класса, и будете проводить над ним манипуляции, то система может гарантировать, что другие окна, не относящиеся к вашему классу, не будут затронуты вашими действиями. Кроме того, если вы создаете свой класс окна, то можете настроить его по своему желанию и в дальнейшем использовать как шаблон для создаваемых окон. С системными классами эксперементировать не стоит. Если вы измените параметры класса «BUTTON», то это немедленно отразится на всех кнопках в Windows, что делать не стоит.

    Второй этап создания окна состоит в регистрации созданного класса вызовом функции API RegisterClass(). После регистрации класс можно использовать. С этого момента можно создавать свои окна на базе зарегистрированного класса. Это делается вызовом CreateWindow(). Но это еще не окно на экране, а лишь его образ в памяти. Показ окна на экране дисплея — это третий и последний этап создания окна. Для этого вызывается функция ShowWindow(), показывающая окно на экране, и функция UpdateWindow(), посылающая сообщение WM_PAINT, получив которое программа должна перерисовать свое окно. В принципе UpdateWindow() можно и не вызывать, но тогда Windows не может гарантировать, что в вашем окне будет именно то, что вы хотите.

    /*------------------------
    Файл test2.c
    ------------------------*/
    #include 
    #define STRICT
    
    /* Прототип оконной функции */
    LRESULT CALLBACK WndProc
     (HWND, UINT, WPARAM, LPARAM);  
    int WINAPI WinMain(
     HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPSTR lpCmdLine,
     int nCmdShow )
    {
     
     /* Структура для хранения сообщений */
     MSG msg;
     /* Структура класса окна */
     WNDCLASS wc;
     /* Дескриптор окна */
     HWND hWnd;
    
     /* создаем класс */
    
     /* имя класса */
     wc.lpszClassName = (LPCSTR)"OurClass";
     /* оконная процедура класса */
     wc.lpfnWndProc = WndProc;
     /* стиль окна */
     wc.style = CS_VREDRAW | CS_HREDRAW;
     /* дескриптор экземпляра программы */
     wc.hInstance = hInstance;
     /* загрузить пиктограмму для окон класса */
     wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
     /* загрузить курсор мыши для окон класса */
     wc.hCursor = LoadCursor( NULL, IDC_ARROW );
     /* установить закраску окна */
     wc.hbrBackground = (HBRUSH)( COLOR_WINDOW+1 );
     /* у окон этого класса нет меню */
     wc.lpszMenuName = NULL;
     /* дополнительные данные в структуре класса */
     /* и окна не требуются */
     wc.cbClsExtra = 0;
     wc.cbWndExtra = 0;
    /* Регистрируем класс окна */
    RegisterClass(&wc);
    
    /* создаем окно на базе нашего класса */
    hWnd = CreateWindow(
    /* Создается окно нашего класса */
    "OurClass",
    /* Заголовок создаваемого окна */
    "Пример создания окна",
    /* Стандартный тип окна */
    WS_OVERLAPPEDWINDOW,
    /* стандартное горизонтальное размещение окна */
    CW_USEDEFAULT,
    /* стандартное вертикальное */
    CW_USEDEFAULT,
    /* стандартная ширина окна */
    CW_USEDEFAULT,
    /* стандартная высота окна */
    CW_USEDEFAULT,
    /* дескриптор родительского окна */
    NULL,
    /* меню у окна отсутствует */
    NULL, 
    /* дескриптор экземпляра программы */
    hInstance,
    /* указатель на данные окна исполь-*/
    /* зуется при создании окон MDI */
    NULL
    );
    
    /* показать окно на экране */
    ShowWindow(hWnd,
    /* показать окно в нормальном режиме */
    SW_SHOWNORMAL 
    );
    
    /* обновить содержимое окна */
    UpdateWindow(hWnd);
    
    /* начать обработку сообщений */
    while(GetMessage(&msg, (HWND)NULL, 0, 0))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }
    
    /* отменить зарегистрированный класс, */
    /* чтобы освободить память */
    UnregisterClass("OurClass", hInstance);
    
    /* вернуть Windows код возврата */
    return msg.wParam;      
    }
    
    /* Оконная процедура */
    LRESULT CALLBACK WndProc(
    HWND hWnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam )
    {
    /* Не делать ничего, просто передать */
    /* управление обработчику по умолчанию */
    return DefWindowProc
            (hWnd, uMsg, wParam, lParam);
    }

    Чтобы помочь читателю разобраться с исходным текстом, приведем некоторые комментарии. Строка wc.style = CS_VREDRAW | CS_HREDRAW обозначает, что при изменении размеров окна по вертикали или горизонтали окно должно быть перерисовано. Еще одна непонятная строка — wc.hbrBackground = (HBRUSH)( COLOR_WINDOW+1 ). Но она поддается расшифровке, если знать, что системное число COLOR_WINDOW — это тот цвет, который выбран вами для фона. Однако массив цветовых настроек устроен так, что приходится прибавлять единицу для того, чтобы найти правильный его элемент. Два загадочных параметра wc.cbClsExtra и wc.cbWndExtra всегда сбивают с толку программистов. А между тем ничего загадочного здесь нет. Через эти параметры программист может запросить дополнительные байты памяти, размещенные в структуре параметров класса (cbClsExtra) или в структуре окна (cbWndExtra). Как правило, это необходимо для передачи каких-либо данных между окнами или экземплярами программы.

    Последний параметр lpParam, передаваемый функции CreateWindow(), также может вызвать замешательство. На самом деле, это указатель на структуру CREATESTRUCT, задающую параметры создания окна и предаваемую в качестве параметра при посылке сообщения WM_CREATE. Это сообщение посылается оконной функции в момент вызова функции CreateWindow(). Это дает шанс оконной процедуре поправить параметры или просто выявить параметры создаваемого окна для каких-то собственных нужд.

    Наверное читатель заметил непонятный вызов UnregisterClass(), происходящий при завершении программы. Это действительно очень важный момент. Когда вы регистрируете свой класс окна, для него выделяется блок памяти и делается запись в системной области, где хранятся все важные имена. Обычно при завершении программы Windows чистит эти области. Но не стоит надеяться на то, что это случится, мы должны быть приверженцами «дуракоустойчивого» программирования. Это значит, что мы сами освобождаем описанные выше системные области вызовом UnregisterClass().

    На самом деле все то, что мы с вами сделали, можно выполнить гораздо быстрее, если воспользоваться заготовкой, находящейся в каталоге GENERIC среди примеров, поставляемых с SDK. В этом каталоге располагается исходный текст полноценной программы с окном, файлом помощи, пиктограммами и т. д., короче, всеми необходимыми компонентами. От вас требуется лишь поменять необходимые параметры и откомпилировать программу.

    После того как вы сами написали Windows-программу, вы уже догадались, что в природе должен существовать такой инструмент, который способен значительно облегчить работу. Его название Borland C++ Builder — Delphi-подобная среда разработки программ. Он позволяет избавиться от необходимости написания исходного текста для создания окон и всех связанных с этой задачей проблем. С самого начала программист получает готовое приложение с окном, сразу же начиная решать главную задачу, а не вспомогательную. Поэтому в своих дальнейших занятиях мы будем пользоваться пакетом Borland C++ Builder.

    Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии
  • Невозможно занять порт 80 поскольку он уже используется cлужбы windows openserver
  • Как поставить пароль на компьютер windows 10 pro
  • Microsoft visual c все пакеты для windows 10 x64 2013
  • Активация windows 10 софт
  • Windows vista home prem russian oemact