Ищете альтернативу сообщениям Windows, используемым в межпроцессном взаимодействии
У меня есть многопоточное приложение (MIDAS), которое использует сообщения Windows для связи с самим собой.
ОСНОВНАЯ ФОРМА
Основная форма получает сообщения Windows, отправленные RDM LogData ('DataToLog')
Поскольку сообщения Windows используются, они имеют следующие атрибуты
- Полученные сообщения неделимы
- Полученные сообщения помещаются в очередь в порядке их отправки
ВОПРОС:
Можете ли вы предложить лучший способ сделать это без использования сообщений Windows?
ГЛАВНЫЙ КОД ФОРМЫ
const
UM_LOGDATA = WM_USER+1002;
type
TLogData = Record
Msg : TMsgNum;
Src : Integer;
Data : String;
end;
PLogData = ^TLogData;
TfrmMain = class(TForm)
//
private
procedure LogData(var Message: TMessage); message UM_LOGDATA;
public
//
end;
procedure TfrmMain.LogData(var Message: TMessage);
var LData : PLogData;
begin
LData := PLogData(Message.LParam);
SaveData(LData.Msg,LData.Src,LData.Data);
Dispose(LData);
end;
Код RDM
procedure TPostBoxRdm.LogData(DataToLog : String);
var
WMsg : TMessage;
LData : PLogData;
Msg : TMsgNum;
begin
Msg := MSG_POSTBOX_RDM;
WMsg.LParamLo := Integer(Msg);
WMsg.LParamHi := Length(DataToLog);
new(LData);
LData.Msg := Msg;
LData.Src := 255;
LData.Data := DataToLog;
WMsg.LParam := Integer(LData);
PostMessage(frmMain.Handle, UM_LOGDATA, Integer(Msg), WMsg.LParam);
end;
РЕДАКТИРОВАТЬ:
Почему я хочу избавиться от сообщений Windows:
- Я хотел бы преобразовать приложение в службу Windows
- Когда система занята - буфер сообщений Windows заполняется, и все замедляется
7 ответов
Используйте именованные каналы. Если вы не знаете, как их использовать, то сейчас самое время учиться.
Используя именованные каналы, вы можете отправлять любой тип структуры данных (если сервер и клиент знают, что это за структура данных). Я обычно использую массив записей для отправки больших коллекций информации туда и обратно. Очень кстати.
Я использую бесплатные (и с открытым исходным кодом) компоненты именованных каналов Рассела Либби. Поставляется с TPipeServer и визуальным компонентом TPipeClient. Они делают использование именованных каналов невероятно простым, а именованные каналы отлично подходят для межпроцессного взаимодействия (IPC).
Вы можете получить компонент здесь. Описание из источника: // Описание: набор клиентских и серверных компонентов именованных каналов для Delphi, а также // компонент перенаправления консольного канала.
Кроме того, Рассел помог мне в Experts-Exchange с использованием более старой версии этого компонента для работы в консольном приложении для отправки / получения сообщений по именованным каналам. Это может помочь вам начать работу с использованием его компонентов. Обратите внимание, что в приложении или службе VCL вам не нужно писать свой собственный цикл сообщений, как я делал в этом консольном приложении.
program CmdClient;
{$APPTYPE CONSOLE}
uses
Windows, Messages, SysUtils, Pipes;
type
TPipeEventHandler = class(TObject)
public
procedure OnPipeSent(Sender: TObject; Pipe: HPIPE; Size: DWORD);
end;
procedure TPipeEventHandler.OnPipeSent(Sender: TObject; Pipe: HPIPE; Size: DWORD);
begin
WriteLn('On Pipe Sent has executed!');
end;
var
lpMsg: TMsg;
WideChars: Array [0..255] of WideChar;
myString: String;
iLength: Integer;
pcHandler: TPipeClient;
peHandler: TPipeEventHandler;
begin
// Create message queue for application
PeekMessage(lpMsg, 0, WM_USER, WM_USER, PM_NOREMOVE);
// Create client pipe handler
pcHandler:=TPipeClient.CreateUnowned;
// Resource protection
try
// Create event handler
peHandler:=TPipeEventHandler.Create;
// Resource protection
try
// Setup clien pipe
pcHandler.PipeName:='myNamedPipe';
pcHandler.ServerName:='.';
pcHandler.OnPipeSent:=peHandler.OnPipeSent;
// Resource protection
try
// Connect
if pcHandler.Connect(5000) then
begin
// Dispatch messages for pipe client
while PeekMessage(lpMsg, 0, 0, 0, PM_REMOVE) do DispatchMessage(lpMsg);
// Setup for send
myString:='the message I am sending';
iLength:=Length(myString) + 1;
StringToWideChar(myString, wideChars, iLength);
// Send pipe message
if pcHandler.Write(wideChars, iLength * 2) then
begin
// Flush the pipe buffers
pcHandler.FlushPipeBuffers;
// Get the message
if GetMessage(lpMsg, pcHandler.WindowHandle, 0, 0) then DispatchMessage(lpMsg);
end;
end
else
// Failed to connect
WriteLn('Failed to connect to ', pcHandler.PipeName);
finally
// Show complete
Write('Complete...');
// Delay
ReadLn;
end;
finally
// Disconnect event handler
pcHandler.OnPipeSent:=nil;
// Free event handler
peHandler.Free;
end;
finally
// Free pipe client
pcHandler.Free;
end;
end.
Да - Габр, вы можете использовать сообщения Windows в сервисе.
==============================
До Windows Vista вы могли настроить службу для взаимодействия с рабочим столом. Это заставляет службу работать на том же рабочем столе, что и вошедший в систему пользователь, поэтому программа, работающая от имени этого пользователя, может отправлять сообщения в окна вашей службы. Windows Vista изолирует сервисы; они больше не могут взаимодействовать с рабочим столом любого пользователя.
=============================
Цитата из ответа Роба Кеннеди на "TService не будет обрабатывать сообщения"
Но я не смогу использовать 'frmMain.Handle' для отправки сообщений из RDM в основную форму в Windows Vista.
Все, что мне нужно сделать, это найти другой способ размещения и получения сообщения
Вариант 1: Пользовательская очередь сообщений
Вы можете создать собственную очередь сообщений и помещать сообщения в очередь, сортировать очередь на основе бизнес-правил и извлекать сообщения из очереди из основного потока для обработки. Используйте критическую секцию для синхронизации.
Вариант 2: Обратные вызовы
Используйте обратные вызовы для отправки данных из потоков. Опять же, используйте критический раздел для синхронизации.
OmniThreadLibrary содержит очень эффективную очередь сообщений в OtlComm.pas
Блок.
Документация не очень хорошая в данный момент ( начните здесь), но вы всегда можете использовать форум.
Сообщения Windows все еще можно использовать в Windows Vista! Проблема в том, что технология Vista под названием "Изоляция привилегий пользовательского интерфейса" (UIPI) не позволяет процессам с более низким уровнем целостности (IL) отправлять сообщения процессу с высоким IL (например, служба Windows имеет высокий IL и пользовательский интерфейс). приложения в режиме имеют средний IL).
Однако это можно обойти, и средним IL-приложениям можно разрешить отправлять wm в процессы с высоким IL.
Википедия говорит это лучше всего:
UIPI не является границей безопасности и не ставит своей целью защиту от всех разрушительных атак. Специальные возможности пользовательского интерфейса Приложения могут обойти UIPI, установив для их значения "uiAccess" значение ИСТИНА в составе своего файла манифеста. Для этого необходимо, чтобы приложение находилось в каталоге Program Files или Windows, а также чтобы оно было подписано действующим органом по подписи кода, но эти требования не обязательно помешают вредоносным программам уважать их.
Кроме того, некоторые сообщения по-прежнему пропускаются, например WM_KEYDOWN, что позволяет процессу с более низким IL направлять ввод в командную строку с повышенными правами.
Наконец, функция ChangeWindowMessageFilter позволяет среднему процессу IL (все процессы без повышенных прав, кроме защищенного режима Internet Explorer) изменять сообщения, которые процесс с высоким IL может получать от процесса с более низким IL. Это эффективно позволяет обойти UIPI, если он не запущен из Internet Explorer или одного из его дочерних процессов.
Кто-то из Delphi-PRAXIS (ссылка на немецком языке. Используйте Google для перевода страницы) уже решил эту проблему и опубликовал свой код с помощью ChangeWindowMessageFilter. Я считаю, что их проблема в том, что WM_COPYDATA не будет работать в Vista, пока они не изменят свой код, чтобы обойти UIPI для WM_COPYDATA.
Оригинальная ссылка (немецкий)
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, uallHook, uallProcess, uallUtil, uallKernel;
type
TfrmMain = class(TForm)
lbl1: TLabel;
tmrSearchCondor: TTimer;
mmo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure tmrSearchCondorTimer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private-Deklarationen }
fCondorPID : DWord;
fInjected : Boolean;
fDontWork : Boolean;
procedure SearchCondor;
procedure InjectMyFunctions;
procedure UnloadMyFunctions;
function GetDebugPrivileges : Boolean;
procedure WriteText(s : string);
procedure WMNOTIFYCD(var Msg: TWMCopyData); message WM_COPYDATA;
public
{ Public-Deklarationen }
end;
var
frmMain: TfrmMain;
ChangeWindowMessageFilter: function (msg : Cardinal; dwFlag : Word) : BOOL; stdcall;
implementation
{$R *.dfm}
type Tmydata = packed record
datacount: integer;
ind: boolean;
end;
const cCondorApplication = 'notepad.exe';
cinjComFuntionsDLL = 'injComFunctions.dll';
var myData : TMydata;
procedure TfrmMain.WMNOTIFYCD(var Msg: TWMCopyData);
begin
if Msg.CopyDataStruct^.cbData = sizeof(TMydata) then
begin
CopyMemory(@myData,Msg.CopyDataStruct^.lpData,sizeof(TMyData));
WriteText(IntToStr(mydata.datacount))
end;
end;
procedure TfrmMain.WriteText(s : string);
begin
mmo1.Lines.Add(DateTimeToStr(now) + ':> ' + s);
end;
procedure TfrmMain.InjectMyFunctions;
begin
if not fInjected then begin
if InjectLibrary(fCondorPID, PChar(GetExeDirectory + cinjComFuntionsDLL)) then fInjected := True;
end;
end;
procedure TfrmMain.UnloadMyFunctions;
begin
if fInjected then begin
UnloadLibrary(fCondorPID, PChar(GetExeDirectory + cinjComFuntionsDLL));
fInjected := False;
end;
end;
procedure TfrmMain.SearchCondor;
begin
fCondorPID := FindProcess(cCondorApplication);
if fCondorPID <> 0 then begin
lbl1.Caption := 'Notepad is running!';
InjectMyFunctions;
end else begin
lbl1.Caption := 'Notepad isn''t running!';
end;
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
UnloadMyFunctions;
end;
function TfrmMain.GetDebugPrivileges : Boolean;
begin
Result := False;
if not SetDebugPrivilege(SE_PRIVILEGE_ENABLED) then begin
Application.MessageBox('No Debug rights!', 'Error', MB_OK);
end else begin
Result := True;
end;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
@ChangeWindowMessageFilter := GetProcAddress(LoadLibrary('user32.dll'), 'ChangeWindowMessageFilter');
ChangeWindowMessageFilter(WM_COPYDATA, 1);
fInjected := False;
fDontWork := not GetDebugPrivileges;
tmrSearchCondor.Enabled := not fDontWork;
end;
procedure TfrmMain.tmrSearchCondorTimer(Sender: TObject);
begin
tmrSearchCondor.Enabled := False;
SearchCondor;
tmrSearchCondor.Enabled := True;
end;
end.
Я использую эту библиотеку для IPc (использует общую память + мьютекс): http://17slon.com/gp/gp/gpsync.htm
Он имеет TGpMessageQueueReader и TGpMessageQueueWriter. Используйте "Global\" перед именем, чтобы вы могли использовать его для связи между службой Windows и "помощником по графическому интерфейсу службы" при входе пользователя в систему (префикс Global \ необходим для Vista из-за кольцевых сессий безопасности, но также для Windows XP/2003 между сессиями пользователей).
Это очень быстрый, многопоточный и т. Д. Я бы использовал этот вместо WM_COPYDATA (медленный и много накладных расходов, если вы используете его много, но для мелких вещей сообщения могут быть в порядке)
Создатели библиотеки madExcept и т. Д. Предоставляют функциональность IPC, которую можно использовать вместо сообщений Windows.
http://help.madshi.net/IPC.htm
Я разработал заставку Windows на одном этапе, и я хотел, чтобы заставка заставляла отправлять уведомления другим программам, и, пока заставка была активной, я не мог получать сообщения окна для работы между двумя приложениями.
Я заменил его на функциональность IPC, упомянутую выше.
Работал лакомство