Глобальные переменные не использовать! 2 страница

IDTRYAGAIN – …….на кнопку TRY AGAIN

IDYES – …….на кнопку YES

 

 

Порядок выполнения работы

Рассмотрим сначала, как можно "вручную" создать минимальное приложение Win32. Загрузив Visual Studio 2010, выполним команду File | New | Project... и выберем тип проекта — Win32 Project. В раскрывающемся списке Location вы­берем путь к рабочей папке, а в поле Name имя проекта (рисунок 3). В следующем диалоговом окне, приведенном на рисунке 4, нажимаем кнопку Next, а в окне опций проекта (рисунок 5) выберем флажок Empty project (Пустой проект) и нажмем кноп­ку Finish — получим пустой проект, в котором нет ни одного файла.

 

Рисунок 3. Выбор типа проекта

 

Рисунок 4. Стартовое окно построителя приложения

 

 

Рисунок 5. Окно опций проекта

 

С помощью контекстного меню (рисунок 6) добавим файл для кода приложения, имя файла введем в ходе диалога выбора шаблона объекта на рисунке 7. (Тот же самый диалог можно получить по команде меню Project | Add New Item)

 

Рисунок 6. Добавление к проекту нового объекта с помощью контекстного меню

 

Рисунок 7. Выбор шаблона объекта

 

Программа не делает ничего полезного, поэтому, запустив ее на выполнение кноп­кой ► (Start Debugging), мы получим изображенное на рисунке 8 пустое окно, имеющее заголовок и набор стандартных кнопок.

 

Рисунок 8. Окно первой Windows-программы

Варианты заданий на выполнение

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

2. В рабочей области окна приложения рядом друг с другом расположить 4 временных окна, в заголовках которых указан номер окна. После нажатия левой клавиши мыши временное окно выдает сообщение, содержащее номер окна.

3. Окно размером в четверть площади экрана расположено в центре экрана. После нажатия левой клавиши мыши окно несколько раз меняет подсветку и перемещается в угол экрана так, что курсор мыши оказыва­ется за пределами окна.

4. Дочернее окно размером 100*100 пикселей при перемещении курсора мыши над ним "убегает" от курсора мыши в произвольном направлении, оставаясь в пределах рабочей области родительского окна.

5. В центре рабочей области окна расположено окно без заголовка с вертикальной и горизонтальной полосами просмотра размером в четверть рабочей области. При нажатии разных клавиш мыши временное окно выдает разный звуковой сигнал

 

Контрольные вопросы

1. Что такое Win32 API?

2. Какие операционные системы обслуживает API Win32?

3.Какие особенности имеет Win32 API?

4. Какие преимущества программирования дает Win32 API?

5. Какой основной тип переменных используется в Win32?

6. Для управления каких систем могут быть написаны программы с использованием Win32?


Лабораторная работа №2

Тема: Процессы и их создание в Win32 API

 

Цель работы:

1. Изучение основных функций Win32API, используемых для управления процессами

2. Разработка простейшей программы, демонстрирующей создание и завершение процесса.

3. Разработка приложения Win32 API, реализующего функции указанные в варианте.

 

 

Краткое теоретическое введение

1. Процессы в Windows

1.1. Создание процесса

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

Новый процесс в Windows создается вызовом функции CreateProcess, которая имеет следующий прототип:

BOOL CreateProcess(

LPCTSTR lpApplicationName,// имя исполняемого модуля

LPTSTR lpCommandLine, // командная строка

LPSECURITY_ATTRIBUTES lpProcessAttributes,//атрибуты защиты процесса

LPSECURITY_ATTRIBUTES lpThreadAttributes,//атрибуты защиты потока

BOOL bInheritHandle, // наследуемый ли дескриптор

DWORD dwCreationFlags, // флаги создания процесса

LPVOID lpEnvironment, // блок новой среды окружения

LPCTSTR lpCurrentDirectory, // текущий каталог

LPSTARTUPINFO lpStartUpInfo, // вид главного окна

LPPROCES S_INFORMATION lpProcessInformation // информация о процессе

);

Функция CreateProcess возвращает значение TRUE, если процесс был создан успешно. В противном случае эта функция возвращает значение FALSE. Процесс, который создает новый процесс, называется родительским процессом (parent process) по отношению к создаваемому процессу. Новый же процесс, который создается другим процессом, называется дочерним процессом (child process) по отношению к процессу родителю.

Сейчас мы опишем только назначение некоторых параметров функции CreateProcess. Остальные параметры этой функции будут описываться по мере их использования. Первый параметр lpApplicationName определяет строку с именем exe-файла, который будет запускаться при создании нового процесса. Эта строка должна заканчиваться нулем и содержать полный путь к запускаемому файлу. Для примера рассмотрим следующую программу, которая выводит на консоль свое имя и параметры.

 

Листинг 1.Консольный процесс, который выводит на консоль свое имя и параметры

#include <conio.h>

int main(int argc, char *argv[])

{

int i;

_cputs("I am created.");

_cputs("\nMy name is: ");

_cputs(argv[0]);

for (i = 1; i < argc; i++)

_cprintf ("\n My %d parameter = %s", i, argv[i]);

_cputs("\nPress any key to finish.\n");

_getch();

return 0;

}

Создадим из этой программы exe-файл, который расположим на диске C и назовем ConsoleProcess.exe. Тогда этот exe-файл может быть запущен из другого приложения следующим образом.

 

Листинг 2. Пример консольного процесса, который создает другое консольное приложение с новой консолью и ждет завершения работы этого приложения.

#include <windows.h>

#include <conio.h>

int main()

{

char lpszAppName[] = "C:\\ConsoleProcess.exe";

STARTUPINFO si;

PROCESS_INFORMATION piApp;

ZeroMemory(&si, sizeof(STARTUPINFO));

si.cb = sizeof(STARTUPINFO);

// создаем новый консольный процесс

if (!CreateProcess(lpszAppName, NULL, NULL, NULL, FALSE,

CREATE_NEW_CONSOLE, NULL, NULL, &si, &piApp))

{

_cputs("The new process is not created.\n");

_cputs("Check a name of the process.\n");

_cputs("Press any key to finish. \n");

_getch(); return 0;

}

_cputs("The new process is created.\n");

//ждем завершения созданного процесса WaitForSingleObject(piApp.hProcess, INFINITE);

// закрываем дескрипторы этого процесса в текущем процессе CloseHandle(piApp.hThread);

CloseHandle(piApp.hProcess);

return 0;

}

 

Отметим в последней программе два момента. Во-первых, перед запуском консольного процесса ConsoleProcess.exe все поля структуры si типа STARTUPINFO должны заполняться нулями. Это делается при помощи вызова функции ZeroMemory, которая предназначена для этой цели и имеет следующий прототип:

VOID ZeroMemory(

PVOID Destination, // адрес блока памяти

SIZE_T Length // длина блока памяти

);

В этом случае вид главного окна запускаемого приложения определяется по умолчанию самой операционной системой Windows. Во-вторых, в параметре dwCreationFlags устанавливается флаг CREATE_NEW_CONSOLE. Это говорит системе о том, что для нового создаваемого процесса должна быть создана новая консоль. Если этот параметр будет равен NULL, то новая консоль для запускаемого процесса не создается и весь консольный вывод нового процесса будет направляться в консоль родительского процесса.

Структура piApp типа PROCESS_INFORMATION содержит идентификаторы и дескрипторы нового создаваемого процесса и его главного потока. Мы не используем эти дескрипторы в нашей программе и поэтому закрываем их. Значение FALSE параметра bInheritHandle говорит о том, что эти дескрипторы не являются наследуемыми. О наследовании дескрипторов мы поговорим подробнее в одном из следующих параграфов этой главы.

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

 

Листинг 3.Пример процесса, который создает новое консольное приложение с новой консолью

#include <windows.h>

#include <conio.h>

int main()

{

char lpszCommandLine[] = "C:\\01-1-ConsoleProcess.exe p1 p2 p3";

STARTUPINFO si;

PROCESS_INFORMATION piCom;

ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO);

// создаем новый консольный процесс

CreateProcess(NULL, lpszCommandLine, NULL, NULL, FALSE,

CREATE_NEW_CONSOLE, NULL, NULL, &si, &piCom);

// закрываем дескрипторы этого процесса CloseHandle(piCom. hThread);

CloseHandle(piCom.hProcess);

_cputs("The new process is created.\n");

_cputs("Press any key to finish.\n");

_getch(); return 0;

}

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

– каталог из которого запущено приложение;

– текущий каталог родительского процесса;

– системный каталог Windows;

– каталог Windows;

– каталоги, которые перечислены в переменной PATH среды окружения.

Для иллюстрации сказанного запустим приложение Notepad.exe, используя командную строку. Программа, запускающая блокнот из командной строки, выглядит следующим образом.

 

Листинг 4.Пример запуска процесса Notepad

#include <windows.h>

#include <iostream> using namespace std;

int main()

{

STARTUPINFO si;

PROCESS_INFORMATION pi;

// заполняем значения структуры STARTUPINFO по умолчанию ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO);

// запускаем процесс Notepad if (!CreateProcess(

NULL, // имя не задаем

"Notepad.exe", // командная строка, первая лексема указывает имя программы NULL, // атрибуты защиты процесса устанавливаем по умолчанию

NULL, // атрибуты защиты первичного потока по умолчанию

FALSE, // дескрипторы текущего процесса не наследуются новым процессом

0, // по умолчанию NORMAL_PRIORITY_CLASS

NULL, // используем среду окружения вызывающего процесса

NULL, // текущий диск и каталог, как и в вызывающем процессе

&si, // вид главного окна - по умолчанию

&pi // здесь будут дескрипторы и идентификаторы

// нового процесса и его первичного потока

)

)

{

cout << "The mew process is not created." << endl << "Check a name of the process." << endl;

return 0;

}

Sleep(1000); // немного подождем и закончим свою работу

// закроем дескрипторы запущенного процесса в текущем процессе CloseHandle(pi.hThread);

CloseHandle(pi.hProcess);

return 0;

}

 

1.2. Завершение процессов

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

VOID ExitProcess(

UINT uExitCode // код возврата для всех потоков

);

При вызове функции ExitProcess завершаются все потоки процесса с кодом возврата, который является параметром этой функции. Приведем пример программы, которая завершает свою работу вызовом функции ExitProcess.

 

Листинг 5.Пример завершения процесса функцией ExitProcess

#include <windows.h>

#include <iostream> using namespace std;

volatile UINT count; volatile char c;

void thread()

{

for ( ; ; )

{

count++;

Sleep(100);

}

}

int main()

{

HANDLE hThread;

DWORD IDThread;

hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thread, NULL, 0, &IDThread); if (hThread == NULL)

return GetLastError();

for ( ; ; )

{

cout << "Input 'y' to display the count or 'e' to exit: "; cin >> (char)c; if (c == 'y')

cout << "count = " << count << endl; if (c == 'e')

ExitProcess(l);

}

}

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

BOOL TerminateProcess(

HANDLE hProcess,

UINT uExitCode

);

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

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

 

Листинг 6.Пример бесконечного процесса

#include <windows.h>

#include <iostream> using namespace std; int count;

void main()

{

for ( ; ; )

{

count++;

Sleep(1000);

cout << "count = " << count << endl;

}

}

Ниже приведена программа, которая создает этот процесс, а потом завершает его по требованию пользователя.

 

Листинг 7. Пример процесса, который создает другое консольное приложение с новой консолью, а потом завершает его при помощи функции TerminateProcess

#include <windows.h>

#include <conio.h>

int main()

{

char lpszAppName[] = "C:\\ConsoleProcess.exe";

STARTUPINFO si;

PROCESS_INFORMATION pi;

ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb=sizeof(STARTUPINFO);

// создаем новый консольный процесс

if (!CreateProcess(lpszAppName, NULL, NULL, NULL, FALSE,

CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi))

{

_cputs("The new process is not created.\n");

_cputs("Check a name of the process.\n");

_cputs("Press any key to finish. \n");

_getch(); return 0;

}

_cputs("The new process is created.\n");

while (true)

{

char c;

_cputs("Input 't' to terminate the new console process: ");

c = _getch(); if (c == 't')

{

_cputs("t\n");

// завершаем новый процесс

TerminateProcess(pi.hProcess, 1)

; break;

}

}

// закрываем дескрипторы нового процесса в текущем процессе

CloseHandle(pi.hThread);

CloseHandle(pi.hProcess);

return 0;

}

Заданиe на выполнение

Написать программы двух консольных процессов Parent и Child, которые выполняют следующие действия.

Процесс Parent:

1. Создает бинарный файл, записи которого имеют следующую структуру:

struct emp

{

int num; // номер зачетки

char name[10]; // имя студента

double grade; // средний бал

};

Имя файла и данные о студентах вводятся с консоли.

2. Выводит созданный файл на консоль.

3. Запрашивает с консоли номер зачетки, имя студента и новый средний бал этого студента.

4. Формирует командную строку, которая содержит имя созданного файла и информацию, полученную в пункте 3.

5. Запускает дочерний процесс Child, которому как параметр передается командная строка, сформированная в пункте 4.

6. Ждет завершения работы процесса Child.

7. Выводит откорректированный файл на консоль.

8. Завершает свою работу.

Процесс Child:

1. Выводит на консоль информацию, полученную через командную строку.

2. Корректирует в файле, созданном процессом Parent, нужную запись, т.е. устанавливает новый средний бал студента.

3. Завершает свою работу.

Для ожидания завершения работы процесса Child использовать функцию:

DWORD WaitForSingleObject(

HANDLE hHandle, // дескриптор объекта

DWORD dwMilliseconds // интервал ожидания в миллисекундах

);

где второй параметр установить равным INFINITE, например

WaitForSingleObject(hProcess, INFINITE); // ждать завершения процесса

Здесь hProcess – дескриптор процесса Child.

 

Контрольные вопросы

1. С помощью каких функций можно создать процесс?

2. С помощью каких функций можно удалит процесс?

3. Какую фунцию выполняет CreateProcess?

4. Какую фунцию выполняет OpenProcess?

5. Какую фунцию выполняет ExitProcess ?

6. Какую фунцию выполняет WaitForSingleObject?

 

Лабораторная работа №3

Тема: Создание потоков в Win32 API

 

Цель работы:

1. Изучение основных функций Win32 API, используемых для управления потоками.

2. Разработка простейшей программы, демонстрирующей создание и завершение процесса.

3. Разработка приложения Win32 API, реализующую указанные в варианте функции.

 

Краткое теоретическое введение

Обзор потоков

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

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

− что такое потоки;

− разница между потоком и процессом;

− преимущества потоков;

− функции Win32 для работы с потоками;

− реализация многопоточного приложения;

Определение потока довольно простое: потоки – это объекты, получающие время процессора. Время процессора выделяется квантами (quantum, time slice). Квант времени — это интервал, имеющийся в распоряжении потока до тех пор. пока время не будет передано в распоряжение другого потока.

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

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

Сделав приложение многопоточным, программист получает дополнительные возможности управления им. Например, через управление приоритетами потоков. Если один из них "притормаживает" приложение, занимая слишком много процессорного времени, его приоритет может быть понижен.

Другое важное преимущество внедрения потоков — при возрастании "нагрузки" на приложение можно увеличить количество потоков и тем самым снять проблему.

Потоки упрощают жизнь тем программистам, которые разрабатывают приложения в архитектуре клиент/сервер. Когда требуется обслуживание нового клиента, сервер может запустить специально для этого отдельный поток. Такие потоки принято называть симметричными потоками (symmetric threads) – они имеют одинаковое предназначение, исполняют один и тот же код и могут разделять одни и те же ресурсы. Более того, приложения, рассчитанные на серьезную нагрузку, могут поддерживать пул (pool) однотипных потоков.

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

Асимметричные потоки (asymmetric threads) – это потоки, решающие различные задачи и, как правило, не разделяющие совместные ресурсы. Необходимость в асимметричных потоках возникает:

− когда в программе необходимы длительные вычисления, при этом необходимо сохранить нормальную реакцию на ввод;

− когда нужно обрабатывать асинхронный ввод/вывод с использованием различных устройств (СОМ-порта, звуковой карты, принтера и т. п.);

− когда вы хотите создать несколько окон и одновременно обрабатывать ввод в них.

Потоки и процессы

Когда мы говорим "программа" (application), то обычно имеем в виду понятие, в терминологии операционной системы обозначаемое как "процесс". Процесс состоит из виртуальной памяти, исполняемого кода, потоков и данных. Процесс может содержать много потоков, но обязательно содержит, по крайней мере, один. Поток, как правило, имеет "в собственности" минимум ресурсов; он зависит от процесса, который и распоряжается виртуальной памятью, кодом, данными, файлами и другими ресурсами ОС.

Почему мы используем потоки вместо процессов, хотя, при необходимости, приложение может состоять и из нескольких процессов?

Дело в том, что переключение между процессами — значительно более трудоемкая операция, чем переключение между потоками. Другой довод в пользу использования потоков — то, что они специально задуманы для разделения ресурсов; разделить ресурсы между процессами (имеющими раздельное адресное пространство) не так-то просто.

Приоритеты потоков

Интерфейс Win32 API позволяет программисту управлять распределением времени между потоками; это распространяется и на приложения, написанные на Delphi. Операционная система планирует время процессора в соответствии с приоритетами потоков.

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

В свою очередь, процессы могут иметь следующие классы приоритетов.

− Real time;

− Normal;

− High;

− Below normal;

− Above normal;

− Idle.

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

Использование класса High ограничено процессами, которые должны завершаться за короткое время, чтобы не вызвать сбойной ситуации. Пример – процесс, который посылает сигналы внешнему устройству; причем устройство отключается, если не получит своевременный сигнал. Если у вас возникли проблемы с производительностью вашего приложения, было бы неправильно решать их просто за счет повышения его приоритета до high – такой процесс также влияет на всю ОС. Возможно, в этом случае следует модернизировать компьютер.

Большинство процессов запускается в рамках класса с нормальным приоритетом. Нормальный приоритет означает, что процесс не требует какого-либо специального внимания со стороны операционной системы.

И, наконец, процессы с фоновым приоритетом запускаются лишь в том случае, если в очереди Диспетчера задач нет других процессов. Обычные виды приложений, использующие такой приоритет, – это программы сохранения экрана и системные агенты (system agents).

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

Приоритеты имеют значения от 0 до 31. Процесс, породивший поток, может впоследствии изменить его приоритет; в этой ситуации программист имеет возможность управлять скоростью отклика каждого потока.

Нормальная практика для асимметричных потоков – это назначение потоку, обрабатывающему ввод, более высокого приоритета, а всем остальным – более низкого или даже приоритета idle, если этот поток должен выполняться только во время простоя системы.


Функции работы с потоками

Создается поток функцией CreateThread, которая имеет следующий прототип:

HANDLE CreateThread (

LPSECURITY ATTRIBUTES lpThreadAttributes,// атрибуты защиты

DWORD dwStackSize,// размер стека потока в байтах

LPTHREAD_START_ROUTINE lpStartAddress,// адрес исполняемой функции

LPVOID lpParameter,// адрес параметра

DWORD dwCreationFlags,// флаги создания потока

LPDWORD lpThreadId// идентификатор потока

) ;

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

Кратко опишем назначение параметров функции CreateThread. Параметр lpThreadAttributes устанавливает атрибуты защиты создаваемого потока. До тех пор пока мы не изучим структуру системы безопасности в Windows, то есть раздел Windows NT Access Control из интерфейса программирования приложений Win32 API, мы будем устанавливать значения этого параметра в NULL при вызове почти всех функций ядра Windows. Это означает, что атрибуты защиты потока совпадают с атрибутами защиты создавшего его процесса. О процессах будет подробно рассказано в следующем разделе.

Параметр dwStackSize определяет размер стека, который выделяется потоку при запуске. Если этот параметр равен нулю, то потоку выделяется стек, размер которого равен по умолчанию 1 Мб. Это наименьший размер стека, который может быть выделен потоку. Если величина параметра dwStackSize меньше, значения, заданного по умолчанию, то все равно потоку выделяется стек размеров в 1Мб. Операционная система Windows округляет размер стека до одной страницы памяти, который обычно равен 4 Кб.

Параметр lpStartAddress указывает на исполняемую потоком функцию. Эта функция должна иметь следующий прототип:

DWORD WINAPI ThreadProc (LPVOID lpParameters);

Параметр lpParameter является единственным параметром, который будет передан функции потока.

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

равно CREATE_SUSPENDED, то поток создается в подвешенном состоянии. В дальнейшем этот поток можно запустить вызовом функции ResumeThread.

Параметр lpThreadId является выходным, то есть его значение устанавливает Windows. Этот параметр должен указывать на переменную, в которую Windows поместит идентификатор потока, который уникален для всей системы и может в дальнейшем использоваться для ссылок на поток.

Приведем пример программы, которая использует функцию CreateThread для создания потока, и продемонстрируем способ передачи параметров исполняемой потоком функции.