Ищете альтернативу сообщениям Windows, используемым для взаимодействия между процессами

StackOverflow https://stackoverflow.com/questions/360289

Вопрос

У меня есть многопоточное приложение (MIDAS), которое использует сообщения Windows для связи с самим собой.

ОСНОВНАЯ ФОРМА

Основная форма получает сообщения Windows, отправленные RDM LogData (‘Логолог данных’)

Поскольку используются сообщения Windows, они имеют следующие атрибуты

  1. Полученные сообщения неделимы
  2. Полученные сообщения помещаются в очередь в том порядке, в каком они были отправлены

ВОПРОС:

Можете ли вы предложить лучший способ сделать это без использования сообщений 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 заполняется, и работа замедляется
Это было полезно?

Решение

Используйте именованные каналы.Если вы не знаете, как ими пользоваться, то сейчас самое время научиться.

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

Я использую бесплатные (с открытым исходным кодом) именованные компоненты канала Рассела Либби.Поставляется с 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.

Другие советы

Вариант 1:Пользовательская очередь сообщений

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

Вариант 2:Обратные вызовы

Используйте обратные вызовы для отправки данных из потоков туда и обратно.Опять же, используйте критический раздел для синхронизации.

Многопоточная библиотека содержит очень эффективную очередь сообщений в OtlComm.pas единица измерения.

Документация на данный момент не очень хороша (начните здесь) но вы всегда можете использовать Форум.

Да, Gabr, вы можете использовать сообщения Windows в службе.

==============================

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

=============================

Цитата из ответа Роба Кеннеди на ‘TService не будет обрабатывать сообщения’

Но я не смогу использовать 'frmMain.Handle' для отправки сообщений из RDM в основную форму в Windows Vista.

Все, что мне нужно сделать, это найти другой способ публикации и получить сообщение

Сообщения Windows по-прежнему можно использовать в Windows Vista!Проблема заключается в том, что технология в Vista, называемая изоляцией привилегий пользовательского интерфейса (UIPI), не позволяет процессам с более низким уровнем целостности (IL) отправлять сообщения процессу с высоким IL (напримерслужба Windows имеет высокий IL, а приложения пользовательского режима - средний IL).

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

Википедия говорит об этом лучше всего:

UIPI не является границей безопасности, и не нацелен на защиту от всех разрушающих атак.Доступность пользовательского интерфейса Приложения могут обходить UIPI, установив для своего значения "UIAccess" значение TRUE как часть своего файла манифеста.Для этого требуется, чтобы приложение находилось в каталоге Program Files или Windows, а также чтобы оно было подписано допустимым кодом уполномоченным на подпись, но эти требования не обязательно помешают вредоносным программам соблюдать их.

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

И, наконец, функция ChangeWindowMessageFilter позволяет выполнять средний IL-процесс (все номера-повышенной процессы, кроме Интернет Эксплорер Защищенный Режим) чтобы изменить сообщения , которые может получать процесс с высоким уровнем 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.

Создатели библиотеки MadExcept и т.д. предоставляют функциональность IPC, которую можно использовать вместо сообщений Windows.

http://help.madshi.net/IPC.htm

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

Я заменил его функциональностью IPC, упомянутой выше.

Сработало на славу.

Я использую эту библиотеку для IPc (использует общую память + мьютекс):http://17slon.com/gp/gp/gpsync.htm

В нем есть TGpMessageQueueReader и TGpMessageQueueWriter.Используйте "Global \" перед именем, чтобы вы могли использовать его для связи между службой Windows и "Вспомогательным графическим интерфейсом службы" при входе пользователя в систему.(Глобальный префикс\ необходим для Vista из-за вызовов безопасности сеанса, но также и для Windows XP / 2003 между сеансами пользователя).

Он очень быстрый, многопоточный и т.д.Я бы использовал это вместо WM_COPYDATA (медленно и с большими накладными расходами, если вы используете его часто, но для небольших сообщений все может быть в порядке)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top