Лучший способ добавить элементы в модальное окно другого приложения?

Как лучше всего добавлять элементы в модальное окно другого приложения?

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

Например, предположим, что вы врач, заполняющий модальное окно данными рецепта. Вы вводите его на 30 дней с 11 пополнениями, а затем пациент говорит, что хочет 90 дней с 3 пополнениями. Исходное приложение (для которого у вас нет доступа к исходному коду) не имеет простого преобразования. Я написал небольшую утилиту, которая следит за этим конкретным окном (используя таймер и findwindow) и, когда находит его, делает себя видимой и размещает себя на пустом месте в целевом модальном окне. При нажатии кнопки «30» Rx записывается на 30 дней с 11 пополнениями, а при нажатии кнопки «90» он делает то, что вы ожидаете. Если модальное окно перемещается, кнопки 30 и 90 перемещаются вместе с ним. Хотя это работает, меня беспокоят накладные расходы, связанные с повторным запуском findwindow по таймеру.

1) есть ли лучший способ? 2) правильно ли я беспокоюсь об этом? 3) ты смеешься над тем, насколько неэффективен мой клуге?

Заранее спасибо - я был очень впечатлен людьми здесь!


person b-p    schedule 18.08.2011    source источник
comment
Что я могу придумать в качестве альтернативы, так это установить глобальный хук CBT, гораздо более подверженный ошибкам и проблематичный, и он также не бесплатен, если речь идет о ресурсах. Если вы не слишком быстро опрашиваете, таймер хорош, я бы сказать.   -  person Sertac Akyuz    schedule 19.08.2011
comment
@Sertac: Итак... что бы вы назвали слишком быстрым опросом? Я не замечаю ухудшения производительности системы или увеличения загрузки процессора (я уверен, что оба показателя очень грубые) с интервалом опроса 250 мс, но это просто кажется неправильным... Все, что медленнее, мешает кнопкам плавно следовать форме.   -  person b-p    schedule 19.08.2011
comment
@b-p Вы можете использовать интервал 1 с для поиска (опроса) окна, а когда есть одно найденное, переключиться на 250 мс, чтобы оно оставалось плавным. Таким образом, вы еще меньше нагружаете систему, когда целевое окно отсутствует.   -  person ain    schedule 19.08.2011
comment
Я бы порекомендовал использовать хук Shell, прослушивающий активацию Windows.   -  person iamjoosy    schedule 19.08.2011
comment
@iamjoosy - Какой крючок? Я верю Сертаку в его слова о крючках CBT и не уверен, какой из них будет лучшим. Я новичок в использовании крючков.   -  person b-p    schedule 21.08.2011


Ответы (1)


Лучший способ - это... DLL-инъекция.

// The DLL:
library dll_inj;

uses
  ShareMem,
  System.SysUtils,
  System.Classes, Vcl.Dialogs,
  windows, messages;

{$R *.res}

var
  hWndMain, hDemoButton, hEdit, hNewButton, hWndEnter: THandle;
  OldWndProc: TFarProc;
  hBtnFont: hFont;
  Times: integer;
  dwThreadId: DWORD;

function NewWndProc(hWnd: hWnd; Msg: UINT; wParam: wParam; lParam: lParam)
  : Longint; stdcall;
begin
  if Times = 0 then
  begin
    Times := Times + 1;
    hWndMain := FindWindowEx(0, 0, 'TForm1', 'Injection test');
    if hWndMain = 0 then
      OutputDebugString('hWndMain is 0!');

    hNewButton := CreateWindow('button', 'NewBtn', WS_CHILD or WS_VISIBLE, 20,
      20, 100, 24, hWndMain, 2000, GetWindowLong(hWndMain, GWL_HINSTANCE), nil);
    if hNewButton = 0 then
      OutputDebugString('CreateWindow failed!')
    else
    begin
      hWndEnter := FindWindowEx(hWndMain, 0, 'TBitBtn', 'Enter');
      if hWndEnter <> 0 then
        hBtnFont := SendMessage(hWndEnter, WM_GETFONT, 0, 0);
      if hBtnFont <> 0 then
        SendMessage(hNewButton, WM_SETFONT, hBtnFont, 1);
    end;

    hDemoButton := FindWindowEx(hWndMain, 0, 'TButton', 'Demo');
    if hDemoButton <> 0 then
    begin
      if not EnableWindow(hDemoButton, true) then
        OutputDebugString('EnableWindow failed!');
    end
    else
      OutputDebugString('hDemoButton is 0!');

    hEdit := FindWindowEx(hWndMain, 0, 'TEdit', 'Serial');
    if hEdit = 0 then
      OutputDebugString('hEdit is 0!')
    else if not SetWindowText(hEdit, 'You have been hacked') then
      OutputDebugString('SetWindowText failed!');
  end;

  case Msg of
    WM_COMMAND:
      if (hNewButton <> 0) and (DWORD(lParam) = hNewButton) then
        MessageBox(HWND_DESKTOP, 'You pressed a new button!', 'Yay!', MB_OK)
      else if (hWndEnter <> 0) and (DWORD(lParam) = hWndEnter) then
      begin
        MessageBox(HWND_DESKTOP, 'This message is not default anymore!',
          'Override!', MB_OK);
        Exit(0); // Suppress default event completely
      end;
  end;
  Result := CallWindowProc(OldWndProc, hWnd, Msg, wParam, lParam);
end;

procedure EntryPoint(Reason: integer);
begin
  hWndMain := FindWindowEx(0, 0, 'TForm1', 'Injection test');
  if hWndMain = 0 then
  begin
    OutputDebugString('hWndMain is 0!');
    Exit;
  end;

  OldWndProc := TFarProc(SetWindowLong(hWndMain, GWL_WNDPROC,
    LONG(@NewWndProc)));

  MessageBox(0, 'DLL Injected', 'OK', 0);
end;

begin
  CreateThread(nil, 0, @EntryPoint, nil, 0, &dwThreadId);

end.

// The injector:
program exe_inj2;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, windows, TLHelp32;

Function EnumThreadProc(wnd: HWND; Var appHwnd: HWND): LongBool; stdcall;
Var
  buf: array [0 .. 256] of Char;
Begin
  Result := LongBool(1);
  if GetClassname(wnd, buf, sizeof(buf)) > 0 then
    If StrComp(buf, 'TApplication') = 0 Then
    Begin
      appHwnd := wnd;
      Result := False;
    End;
End;

Function FindApplicationWindow(forThreadID: DWORD): HWND;
Begin
  Result := 0;
  EnumThreadWindows(forThreadID, @EnumThreadProc, lparam(@Result));
End;

Function ProcessIDFromAppname32(appname: String): DWORD;
{ Take only the application filename, not full path! }
Var
  snapshot: THandle;
  processEntry: TProcessEntry32;
Begin
  Result := 0;
  appname := UpperCase(appname);
  snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  If snapshot <> 0 Then
    try
      processEntry.dwSize := sizeof(processEntry);
      If Process32First(snapshot, processEntry) Then
        Repeat
          If Pos(appname,
            UpperCase(ExtractFilename(StrPas(processEntry.szExeFile)))) > 0 Then
          Begin
            Result := processEntry.th32ProcessID;
            Break;
          End; { If }
        Until not Process32Next(snapshot, processEntry);
    finally
      CloseHandle(snapshot);
    End; { try }
End;

function InjectDLL(dwPID: DWORD; DLLPath: PWideChar): integer;
var
  dwThreadID: Cardinal;
  hProc, hThread, hKernel: THandle;
  BytesToWrite, BytesWritten: SIZE_T;
  pRemoteBuffer, pLoadLibrary: Pointer;
begin
  if not FileExists(DLLPath) then
  begin
    MessageBox(0, PWideChar('File ' + DLLPath + ' does not exist'), 'Error', 0);
    Exit(0);
  end;

  hProc := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION or
    PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ, False, dwPID);
  if hProc = 0 then
    Exit(0);
  try
    BytesToWrite := sizeof(WideChar) * (Length(DLLPath) + 1);
    pRemoteBuffer := VirtualAllocEx(hProc, nil, BytesToWrite, MEM_COMMIT,
      PAGE_READWRITE);
    if pRemoteBuffer = nil then
      Exit(0);
    try
      if not WriteProcessMemory(hProc, pRemoteBuffer, DLLPath, BytesToWrite,
        BytesWritten) then
        Exit(0);
      hKernel := GetModuleHandle('kernel32.dll');
      pLoadLibrary := GetProcAddress(hKernel, 'LoadLibraryW');
      hThread := CreateRemoteThread(hProc, nil, 0, pLoadLibrary, pRemoteBuffer,
        0, dwThreadID);
      try
        WaitForSingleObject(hThread, INFINITE);
      finally
        CloseHandle(hThread);
      end;
    finally
      VirtualFreeEx(hProc, pRemoteBuffer, 0, MEM_RELEASE);
    end;
  finally
    CloseHandle(hProc);
  end;
  Exit(1);
end;

const
  PROCESS_NAME = 'Default_project.exe';

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    WriteLn(PROCESS_NAME + ' PID: ' +
      IntToSTr(ProcessIDFromAppname32(PROCESS_NAME)));
    InjectDLL(ProcessIDFromAppname32(PROCESS_NAME), 'dll_inj.dll');
    ReadLn;
  except
    on E: Exception do
      WriteLn(E.ClassName, ': ', E.Message);
  end;

end.
person Edijs Kolesnikovičs    schedule 22.08.2014