Здавалка
Главная | Обратная связь

Файлы, проецируемые в память



Объекты ядра «проекции файлов» (memory-mapped files) позволяют резервировать регион адресного пространства процесса. Особенностью данного механизма является то, что физическая память не выделяется из страничного файла, а берется из файла, уже находящегося на диске. Как только файл спроецирован в память, к нему можно обращаться так, будто он целиком в нее загружен.

Проецируемые файлы применяются для:

- загрузки и выполнения EXE- и DLL-файлов (это позволяет существенно экономить как на размере страничного файла, так и на времени, необходимом для подготовки приложения к выполнению);

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

- разделения данных между несколькими процессами, выполняемыми на одной машине.

Для использования проецируемых файлов нужно выполнить три операции:

- создать или открыть объект ядра «файл», идентифицирующий дисковый файл, который необходимо спроецировать в память;

- создать объект ядра «проекция файла»;

- указать системе, как спроецировать на адресное пространство процесса объект «проекция файла» ­­­–­­­­­ целиком или частично.

Заканчивая работу с проецируемым фалом необходимо:

- указать системе об отмене проецирования на адресное пространство процесса объекта ядра «проекция файла»;

- закрыть этот объект;

- закрыть объект ядра «файл».

Для создания объекта ядра «проекция файла» необходимо вызвать функцию CreateFileMapping:

HANDLE CreateFileMapping(

HANDLE hFile,

PSECURITY_ATTRIBUTES sa,

DWORD access,

DWORD maximumSizeHigh,

DWORD maximumSizeLow,

PCSTR name);

Параметр hFile определяет дескриптор дискового файла, для которого создается проекция, в параметре access указывается режим доступа к данным. Атрибуты maximumSizeHigh и maximumSizeLow – соответственно старшее и младшее слово размера проекции (размер округляется до значения, кратного размеру страницы). Наконец, параметр name задает имя объекта ядра.

Определены следующие режимы доступа: FILE_MAP_READ – доступ только для чтения, FILE_MAP_WRITE или FILE_MAP_ALL_ACCESS – доступ и для чтения и для записи, FILE_MAP_COPY – изменение данных приводит к созданию закрытой копии изменяемой страницы в страничном файле. В последнем случае доступ к измененным данным будет иметь только вызывающий процесс, что особенно удобно, если не нужно изменять исходный файл.

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

HANDLE OpenFileMapping(

DWORD access,

BOOL inheritHandle,

PCSTR name);

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

PVOID MapViewOfFile(

HANDLE hFileMapping,

DWORD access,

DWORD fileOffsetHigh,

DWORD fileOffsetLow,

SIZE_T numberOfBytesToMap);

В первом параметре передается дескриптор объекта «проекция файла», во втором – режим доступа к данным. Функция MapViewOfFile позволяет проецировать определенные участки файла на адресное пространство процесса, что очень удобно при обработке больших файлов данных. Для этого в параметрах fileOffsetHigh и fileOffsetLow – соответственно старшее и младшее слово – передается смещение относительно начала файла, а параметр numberOfBytesToMap определяет размер проецируемой области (размер округляется до значения, кратного размеру страницы).

Можно было заметить, что и при создании (открытии) объекта «проекция файла» и непосредственно при отображении файла на адресное пространство процесса (вызове функции MapViewOfFile) необходимо было указывать режим доступа к данным access. Это сделано с той целью, чтобы максимально полно контролировать защиту данных. Также следует отметить, что режим доступа к проекции файла должен соответствовать режиму доступа к самому файлу. Например, если сам файл открыт только для чтения (GENERIC_READ), то нельзя создать проекцию с режимом доступа, который разрешает запись (FILE_MAP_WRITE).

Когда необходимость в проекции, полученной вызовом функции MapViewOfFile, отпадает, этот регион необходимо освободить вызовом функции UnmapViewOfFile:

BOOL UnmapViewOfFile(PVOID address);

Ее единственный параметр, address, указывает на адрес возвращаемого системе региона (он должен совпадать со значением, полученным после вызова MapViewOfFile). Если своевременно не вызывать данную функцию, то регион не освободится до завершения процесса. Подобные ошибки могут привести к утечке ресурсов.

Для повышения производительности при работе с представлением файла система буферизует страницы данных в файле и не обновляет немедленно дисковый образ файла. При необходимости можно заставить систему записать измененные данные (все или частично) в дисковый образ файла, вызвав функцию FlushViewOfFile:

BOOL FlushViewOfFile(PVOID address, SIZE_T numberOfBytesToFlush);

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

Ниже приведен пример работы с объектом ядра «проекция файла». В данном примере производится отображение дискового файла на адресное пространство процесса, после чего осуществляется перестановка местами первых двух байтов соответствующего файла.

// Создание (открытие) дискового файла

HANDLE hFile = CreateFile("SomeFile.txt", GENERIC_READ | GENERIC_WRITE,

FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

 

if (hFile != INVALID_HANDLE_VALUE)

{

// Создание объекта ядра "проекция файла"

HANDLE hFileMap = CreateFileMapping(hFile, NULL,

PAGE_READWRITE, 0, 2, NULL);

 

if (hFileMapping != NULL)

{

// Проецирование файла на адресное пространство процесса

char* address = (char*)MapViewOfFile(hFileMap,

FILE_MAP_ALL_ACCESS, 0, 0, 0);

 

if (address != NULL)

{

// Перестановка местами первых двух байт в файле

char tmp = address[0];

address[0] = address[1];

address[1] = tmp;

 

printf("%s", address);

 

// Освобождение выделенного региона

UnmapViewOfFile(address);

}

 

CloseHandle(hFileMap);

}

 

CloseHandle(hFile);

}

Разделяемая память

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

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

Создавать файл на диске и хранить там данные только с этой целью очень неудобно. Поэтому существует возможность проецирования файлов непосредственно на физическую память из страничного файла, а не из специально создаваемого дискового файла. Для этого при создании объекта ядра «проекция файла» в первом параметре hFile функции CreateFileMapping необходимо передать INVALID_HANDLE_VALUE. При этом объем выделенной памяти в страничном файле также определяется параметрами maximumSizeHigh и maximumSizeLow.

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

// Process1.cpp

 

// Создание объекта ядра "проекция файла" (256 байт в страничном файле)

HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,

PAGE_READWRITE, 0, 256, "SomeName");

 

if (hSharedMemory != NULL)

{

// Проецирование файла на адресное пространство вызывающего процесса

char* sharedMemory = (char*)MapViewOfFile(hSharedMemory,

FILE_MAP_ALL_ACCESS, 0, 0, 256);

 

if (sharedMemory != NULL)

{

// Запись в файл

char* message = "Message from other process";

CopyMemory(sharedMemory, message, strlen(message) + 1);

 

getch();

 

// Освобождение выделенного региона

UnmapViewOfFile(sharedMemory);

}

 

// Закрытие объекта ядра

CloseHandle(hSharedMemory);

}

 

// Process2.cpp

 

// Открытие объекта ядра "проекция файла"

HANDLE hSharedMemory = OpenFileMapping(FILE_MAP_READ, FALSE, "SomeName");

 

if (hSharedMemory != NULL)

{

// Проецирование файла на адресное пространство вызывающего процесса

char* sharedMemory = (char*)MapViewOfFile(hSharedMemory,

FILE_MAP_READ, 0, 0, 256);

 

if (sharedMemory != NULL)

{

// Чтение из файла

printf(sharedMemory);

getch();

 

// Освобождение выделенного региона

UnmapViewOfFile(sharedMemory);

}

 

// Закрытие объекта ядра

CloseHandle(hSharedMemory);

}

Каналы

Канал – это объект ядра, предназначенный для организации межпроцессной коммуникации. Можно сказать, что канал – это поток данных между двумя или несколькими процессами, имеющий интерфейс, аналогичный чтению или записи в файл. Но в отличие от файлов каналы не хранят информацию, а лишь накапливают её до следующей операции чтения из канала другим процессом, образуя очередь.

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

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

Анонимные каналы

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

Анонимный канал создается процессом-сервером при помощи функции CreatePipe:

BOOL CreatePipe(

PHANDLE hReadPipe,

PHANDLE hWritePipe,

PSECURITY_ATTRIBUTES sa,

DWORD size);

Неименованные каналы позволяют осуществлять полудуплексную передачу данных, поэтому функция CreatePipe возвращает два дескриптора – hReadPipe и hWritePipe (для чтения и записи в канал соответственно). Параметр sa задает атрибуты безопасности, а значение атрибута size определяет размер канала в байтах. После создания канала необходимо передать клиентскому процессу полученные дескрипторы (или один из них), что обычно делается с помощью механизма наследования (п. 3.6.1).

Получив нужный дескриптор, клиентский процесс, также как и серверный, может далее взаимодействовать с каналом при помощи функций ReadFile и WriteFile. По окончании работы с каналом оба процесса должны закрыть описатели при помощи функции CloseHandle.

В приведенной ниже программе создается анонимный канал, в него записывается строка, затем эти данные считываются из канала и выводятся на экран.

HANDLE hReadPipe, hWritePipe;

 

// Создание канала

if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 256))

{

DWORD bytes;

 

// Запись в канал

char* bufOut = "0123456789";

WriteFile(hWritePipe, bufOut, strlen(bufOut) + 1, &bytes, NULL);

 

// Чтение из канала

char bufIn[256];

ZeroMemory(bufIn, 256);

ReadFile(hReadPipe, bufIn, bytes, &bytes, NULL);

 

// Закрытие дескрипторов

CloseHandle(hReadPipe);

CloseHandle(hWritePipe);

 

// Вывод результата чтения

printf(bufIn);

}

Именованные каналы

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

Каналы должны иметь уникальные в рамках сети имена в соответствии с правилами именования ресурсов в сетях Windows (Universal Naming Convention, UNC):

\\ServerName\pipe\PipeName

где ServerName – имя машины, PipeName – имя, задаваемое пользователем. Слово pipe в составе имени фиксировано. Данный формат используется клиентом при подключении к каналу.

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

\\.\pipe\PipeName

где «.» обозначает локальную машину.

Сервер создает именованный канал с использованием функции CreateNamedPipe:

HANDLE CreateNamedPipe(

PCSTR name,

DWORD openMode,

DWORD pipeMode,

DWORD maxInstances,

DWORD outBufferSize,

DWORD inBufferSize,

DWORD defaultTimeout,

PSECURITY_ATTRIBUTES sa);

Первый параметр определяет имя канала, которое должно удовлетворять правилам именования, описанным выше.

Параметр openMode определяет модель передачи данных: PIPE_ACCESS_DUPLEX – дуплексный режим, PIPE_ACCESS_INBOUND – данные могут передаваться только от клиента серверу, PIPE_ACCESS_OUTBOUND – данные могут передаваться только от сервера к клиенту.

Значение параметра pipeMode задает режим работы канала: потоковая модель (PIPE_TYPE_BYTE ­– данные записываются в канал, как поток байт, PIPE_READMODE_BYTE – данные считываются из канала, как поток байт) или модель, ориентированная на сообщения (аналогично, PIPE_TYPE_MESSAGE и PIPE_READMODE_MESSAGE). Совместно с режимом работы канала можно определить режим обмена данными: синхронный (PIPE_WAIT) или асинхронный (PIPE_NOWAIT).

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

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

Максимальное количество экземпляров канала (фактически максимальное число одновременных соединений по данному каналу) задает параметр maxInstances. Данное значение должно находиться в диапазоне от 1 до PIPE_UNLIMITED_INSTANCES (255).

Параметры outBufferSize и inBufferSize определяют размер исходящего и входящего буфера канала. Значение defaultTimeout задает значение по умолчанию для максимального времени ожидания подключения к именованному каналу.

Следующий код создает именованный канал, работающий в дуплексном режиме. При этом определена модель, ориентированная на сообщения с синхронным режимом обмена данными. Максимальный размер входящего и исходящего буфера – 1024 байт.

HANDLE hPipe = CreateNamedPipe(

"\\\\.\\pipe\\SomeName",

PIPE_ACCESS_DUPLEX,

PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE |

PIPE_WAIT,

PIPE_UNLIMITED_INSTANCES,

1024, 1024, 5000, NULL);

После создания именованного канала сервер ожидает подключения вызовом функции ConnectNamedPipe, в которую передает дескриптор канала hNamedPipe:

BOOL ConnectNamedPipe(HANDLE hNamedPipe, POVERLAPPED overlapped);

В параметре overlapped передается ссылка на структуру, которая используется при асинхронном взаимодействии.

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

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

BOOL DisconnectNamedPipe(HANDLE hNamedPipe);

Клиент осуществляет подключение к именованному каналу, используя функцию CreateFile:

HANDLE CreateFile(

PCSTR name,

DWORD desiredAccess,

DWORD shareMode,

PSECURITY_ATTRIBUTES sa,

DWORD creationDisposition,

DWORD attributes,

HANDLE templateFile);

Применительно к каналам особый интерес представляют первые два параметра функции (подробнее о функции CreateFile см. MSDN). Параметр name определяет имя канала, к которому осуществляется подключение. Второй параметр desiredAccess определяет способ доступа, который должен соответствовать модели передачи данных именованного канала. Так например, если канал работает в дуплексном режиме, то параметр desiredAccess может быть равен GENERIC_READ и/или GENERIC_WRITE – это значит, что клиент может как получать данные от сервера, так и отправлять соответствующие запросы. Если же канал создан в режиме PIPE_ACCESS_INBOUND, клиент может только записывать данные в канал. В последнем случае параметр desiredAccess должен быть равен GENERIC_WRITE. Аналогично, когда данные могут передаваться только от сервера клиенту (режим PIPE_ACCESS_OUTBOUND) в параметре desiredAccess следует передать GENERIC_READ ­– клиент может только читать данные из канала.

Ниже приведен пример межпроцессного обмена данными с использованием именованного канала. Серверное приложение (Server.cpp) создает именованный канал, ожидает подключения очередного клиента, читает запрос и выводит его на экран. Клиент (Client.cpp) подключается к существующему каналу и отправляет запрос серверу.

#define SIZE 1024

#define TIMEOUT 5000

 

// Server.cpp

 

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

HANDLE hPipe = CreateNamedPipe(

"\\\\.\\pipe\\SomeName",

PIPE_ACCESS_DUPLEX,

PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE |

PIPE_WAIT,

PIPE_UNLIMITED_INSTANCES,

SIZE, SIZE, TIMEOUT, NULL);

 

if (hPipe != INVALID_HANDLE_VALUE)

{

DWORD bytesRead;

char request[SIZE];

 

// Цикл ожидания подключений

while (...)

{

// Если клиент подключился

if (ConnectNamedPipe(hPipe, NULL)?

true : GetLastError() == ERROR_PIPE_CONNECTED)

{

ZeroMemory(request, SIZE);

 

// Чтение запроса

if (ReadFile(hPipe, request, SIZE, &bytesRead, NULL))

{

printf("%s\n", request);

FlushFileBuffers(hPipe);

}

 

// Закрытие подключения

DisconnectNamedPipe(hPipe);

}

else

{

CloseHandle(hPipe);

}

}

 

CloseHandle(hPipe);

}

 

// Client.cpp

 

// Подключение к именованному каналу

HANDLE hPipe = CreateFile("\\\\.\\pipe\\SomeName", GENERIC_WRITE,

0, NULL, OPEN_EXISTING, 0, NULL);

 

if (hPipe != INVALID_HANDLE_VALUE)

{

DWORD bytesWrite;

char request[SIZE];

strcpy(request, "Request");

 

// Отправка запроса

WriteFile(hPipe, request, SIZE, &bytesWrite, NULL);

 

CloseHandle(hPipe);

}

Почтовые ящики

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

Каждый процесс, который создаёт почтовый ящик, является сервером. Другие процессы, называемые клиентами, посылают сообщения серверу, записывая их в почтовый ящик. Входящие сообщения всегда дописываются в почтовый ящик и сохраняются до тех пор, пока сервер их не прочтет. Каждый процесс может одновременно быть и сервером и клиентом почтовых ящиков, создавая, таким образом, двунаправленные коммуникации между процессами [7].

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

\\Destination\mailslot\MailslotName

где Destination – имя машины или домена, MailslotName – имя, задаваемое пользователем. Слово mailslot в составе имени фиксировано.

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

\\.\mailslot\MailslotName

где «.» обозначает локальную машину.

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

\\*\mailslot\MailslotName

Почтовый ящик создается вызовом функции CreateMailslot:

HANDLE CreateMailslot(

PCSTR name,

DWORD maxMessageSize,

DWORD readTimeout,

PSECURITY_ATTRIBUTES sa);

Первый параметр определяет имя объекта, которое должно удовлетворять правилам именования, описанным выше.

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

Чтение и запись данных в почтовый ящик осуществляется с помощью функций ReadFile и WriteFile соответственно. Максимальное время ожидания (в миллисекундах) при выполнении операции чтения задается параметром readTimeout. При этом значение 0 означает, что управление вернется сразу, если нет входящих сообщений. Значение MAILSLOT_WAIT_FOREVER означает, что функция ReadFile не вернет управление вызывающему потоку до тех пор, пока в почтовом ящике не появится сообщение, которое можно будет считать. При чтении сообщение автоматически удаляется из почтового ящика.

Определить состояние почтового ящика можно с помощью функции GetMailslotInfo:

BOOL GetMailslotInfo(

HANDLE hMailslot,

PDWORD maxMessageSize,

PDWORD nextSize,

PDWORD messageCount,

PDWORD readTimeout);

В параметре hMailslot передается дескриптор объекта. Если функция выполнена успешно, то в параметре maxMessageSize возвращается максимальный размер одного сообщения, в параметре nextSize – размер очередного сообщения (или MAILSLOT_NO_MESSAGE, если нет входящих сообщений), в параметре messageCount – число входящих сообщений, наконец, в параметре readTimeout – максимальное время ожидания при выполнении операции чтения.

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

BOOL SetMailslotInfo(HANDLE hMailslot, DWORD readTimeout);

Значения 0 и MAILSLOT_WAIT_FOREVER параметра readTimeout будут иметь тот же смысл, что и значения одноименного параметра функции CreateMailslot (см. выше).

Клиент осуществляет подключение к почтовому ящику, используя функцию CreateFile:

HANDLE CreateFile(

PCSTR name,

DWORD desiredAccess,

DWORD shareMode,

PSECURITY_ATTRIBUTES sa,

DWORD creationDisposition,

DWORD attributes,

HANDLE templateFile);

Применительно к почтовым ящикам особый интерес представляют первые два параметра функции (подробнее о функции CreateFile см. MSDN). Параметр name определяет имя объекта, к которому осуществляется подключение. Второй параметр desiredAccess определяет способ доступа и должен быть равен GENERIC_WRITE.

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

#define SIZE 256

#define TIMEOUT 5000

 

// Server.cpp

 

// Создание почтового ящика

HANDLE hMailslot = CreateMailslot("\\\\.\\mailslot\\SomeName",

SIZE, TIMEOUT, NULL);

 

if (hMailslot != INVALID_HANDLE_VALUE)

{

DWORD readBytes;

DWORD messageSize;

char message[SIZE];

 

// Цикл обработки сообщений

while (...)

{

// Если есть необработанные сообщения

if (GetMailslotInfo(hMailslot, NULL, &messageSize, NULL, NULL)

&& (messageSize != MAILSLOT_NO_MESSAGE))

{

ZeroMemory(message, SIZE);

 

// Чтение сообщения

if (ReadFile(hMailslot, message, messageSize,

&readBytes, NULL))

{

printf(message);

}

}

}

 

CloseHandle(hMailslot);

}

 

// Client.cpp

 

// Открытие почтового ящика

HANDLE hMailslot = CreateFile("\\\\*\\mailslot\\SomeName", GENERIC_WRITE,

0, NULL, OPEN_EXISTING, 0, NULL);

 

if (hMailslot != INVALID_HANDLE_VALUE)

{

DWORD writeBytes;

char* message = "Request";

 

// Отправка сообщения всем (*) процессам текущего домена

WriteFile(hMailslot, message, strlen(message), &writeBytes, NULL);

 

CloseHandle(hMailslot);

}

 

 








©2015 arhivinfo.ru Все права принадлежат авторам размещенных материалов.