(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var
reg: TRegistry;
subkeys: TStrings;
i: Integer;
begin
if Integer (Node.getFirstChild.Data) <> -1 then
//Список подразделов был зачитан ранее
Exit;
Node.DeleteChildren(); //Удаление фиктивного элемента дерева
reg := TRegistry.Create();
//Загрузка списка подразделов выбранного раздела
reg.RootKey := GetRootKey(Node);
if reg.OpenKey(GetKeyPath(Node), False) then
begin
//Получение списка подразделов
subkeys := TStringList.Create();
reg.GetKeyNames(subkeys);
for i := 0 to subkeys.Count – 1 do
begin
//Добавление элемента для дочернего раздела (не забываем
//проверять подразделы у каждого дочернего раздела)
CheckSubKeys (keys.Items.AddChild(Node, subkeys[i]));
end;
subkeys.Free();
reg.CloseKey ();
end;
reg.Free();
end;
В листинге 7.25 используются две дополнительные функции: для определения полного пути раздела, соответствующего элементу дерева (без имени ко рневого раздела), и для получения дескриптора корневого раздела (хранится в пoлeData корневого элемента каждой ветви дерева).
Путь раздела определить несложно: просто поднимаемся к корню соответствующей верви дерева, собирая по ходу имена элементов дерева (листинг 7.26).
Листинг 7.26.
Определение пути раздела в дереве
function GetKeyPath(item: TTreeNode): String;
var
temp: TTreeNode;
path: String;
begin
temp := item;
while temp.Parent <> nil do
begin
path := temp.Text + '\' + path;
temp := temp.Parent;
end;
GetKeyPath := path;
end;
Аналогичным образом, даже проще, определяется дескриптор корневого раздела определенной ветви реестра: для этого нужно просто добраться до корня ветви дерева и прочитать значение поля Data корневого элемента (листинг 7.27).
Листинг 7.27.
Определение дескриптора корневого раздела ветви
function GetRootKey (item: TTreeNode): HKEY;
var
temp: TTreeNode;
begin
temp := item;
while temp.Parent <> nil do
temp := temp.Parent;
GetRootKey := HKEY (temp.Data);
end;
При выделении элемента дерева происходит отображение параметров соответствующего раздела в списке в правой части формы. Как заполнять список, представлено в листинге 7.28.
Листинг 7.28.
Составление списка параметров раздела реестра
procedure TForm1.keysChange(Sender: TObject; Node: TTreeNode);
var
reg: TRegistry;
valueItem: TListItem;
item: TTreeNode;
valueNames: TStrings;
i: Integer;
begin
item := keys.Selected;
if item <> nil then
begin
//Зачитаем содержимое выбранного раздела в ListView (values)
values.Clear;
reg := TRegistry.Create();
reg.RootKey := GetRootKey(item);
if reg.OpenKeyReadOnly(GetKeyPath(item)) then
begin
valueNames := TStringList.Create ();
//Получение списка названий параметров
reg.GetValueNames (valueNames);
//Добавление каждого параметра в список
for i := 0 to valueNames.Count – 1 do
begin
valueItem := values.Items.Add();
if valueNames[i] = '' then
valueItem.Caption := '<По умолчанию>'
else
valueItem.Caption := valueNames[i];
//Получение типа и значения параметра
case reg.GetDataType(valueNames [i]) of
rdUnknown:
valueItem.SubItems.Add('Неизвестно');
rdString, rdExpandString:
begin
valueItem.SubItems.Add('Строка');
valueItem.SubItems.Add (reg.ReadString(valueNames [i]));
end;
rdInteger:
begin
valueItem.SubItems.Add('Число ');
valueItem.SubItems.Add(IntToStr(
reg.ReadInteger(valueNames [i])));
end;
rdBinary:
valueItem.SubItems.Add('Двоичные данные ');
end;
end;
valueNames.Free();
reg.CloseKey ();
end;
reg.Free();
end;
end;
Процедура, приведенная в листинге 7.28, не считывает значения двоичных параметров. Так сделано для упрощения этого и так громоздкого фрагмента кода. В считывании значений двоичных параметров на самом деле нет ничего сложного: нужно лишь заранее определить размер данных (метод GetDataSize) и создать буфер соответствующего размера.
Глава 8 Обмен данными между приложениями
• СообщениеWM_COPYDATA
• Использованиебуфераобмена
• Проецируемыевпамятьфайлы
Организация обмена данными между приложениями, а именно между процессами этих приложений, является достаточно трудоемкой задачей. Архитектура Win32 подразумевает максимальную изоляцию выполняющихся приложений друг от друга. Каждое приложение исполняется в своем виртуальном адресном пространстве, которое изолировано и не имеет доступа к памяти других процессов приложений. Но довольно часто возникает необходимость передачи данных из одного выполняющегося процесса в другой. Это вызвано тем, что функциональные приложения и пакеты программ исполняются не в одном процессе, поэтому для нормальной работы используются основные возможности межпроцессного взаимодействия. Наиболее простым, понятным, но не всегда удобным является передача данных с использованием сообщения WM_COPYDATA. Также для передачи данных между приложениями широко используются проецируемые в память файлы (Mapping Files). Существуют и такие высокоуровневые средства, как буфер обмена или уже рассмотренная технология СОМ. Перечисленные способы будут подробно рассматриваться в этой главе. За рамки этой книги выходит рассмотрение способа передачи данных через каналы (трубы, или Pipe), который считается устаревшим и по этой причине не вызывает интереса.
8.1. Сообщение WM_COPYDATA
Сообщение WMCOPYDATA позволяет приложениям копировать данные между их адресными пространствами. Для передачи сообщения должна использоваться функция синхронной отправки сообщения SendMessage, а не PostMessage, которая асинхронным образом передает сообщение. Данные, предназначенные для передачи, не должны содержать указателей или других ссылок на объекты, недоступные для программы, принимающей эти данные. Рассмотрим параметры, передаваемые с сообщением WM_COPYDATA:
//дескриптор передающего окна
wParam = (WPARAM) (HWND) hwnd;
//указатель на структуру с данными
lParam = (LPARAM) (PCOPYDATASTRUCT) pcds;
На использование сообщения налагаются следующие ограничения:
• данные, которые будут приняты, должны быть только для чтения, так как изменение структуры с данными может привести к непредсказуемым последствиям;
• если приложению, получающему данные, требуется использовать их после возврата из обработчика WMCOPYDATA, оно должно скопировать их в локальный буфер.
Итак, приступим к созданию приложения, демонстрирующего работу WM_COPYDATA Для создания хорошего примера потребуется создать два приложения. Первое будет отправлять данные (например, строку текста), другое приложение будет их получать. На главной форме первого приложения помещаем элемент управления TextBox, в который будет записываться передаваемая строка, и кнопку, нажатие которой инициирует передачу данных. Для второго приложения достаточно элемента для отображения текстовой информации типа Label. Перейдем к рассмотрению исходных текстов созданных приложений.
Мы будет посылать сообщение окну, и сообщений может быть различное количество, поэтому для уникальной идентификации операции введем специальную константу:
const
CMD_SE TLABELTEXT = 1; // Задаем ID команды
На форме находится кнопка отправки данных другому приложению, ее обработчик выглядит следующим образом (листинг 8.1).
Листинг 8.1.
Отправка данных другому приложению
procedure TDataSender.bnSendClick (Sender: TObject);
var
CDS: TCopyDataStruct;
begin
//Устанавливаем тип команды
CDS.dwData := CMD_SETLABELTEXT;
//Устанавливаем длину передаваемых