Subclassing - jak to zrobic

0

Wtam
Mam pytanie, od jakiegos czasu usiluj osiagnac pewien cel. Jest sobie pewien program, do ktorego dodalem nowa pozycje w menu glownym, wraz z podmenu (GetMenu, SetMenu i takie tam) ale teraz chce byc poinformowany jak uzytkownik kliknie jakas pozycje w tym programie. Szukalem na necie ale nie za bardzo moge sobie z tym poradzic. Czytalem ze trzeba podmienic procedure obslugi tego okna na swoja ktora wysle komunikat do mojego proramu, ale wiem jak to zrobic. Jesli ktos zna jakies rozwiazanie to bede bardzo wdzieczny.
Czyli w skrocie.
Jak dostac komunikat o nacisnieciu menu z innego programu.

Pozdrawiam

0

Witam
Jeżeli dobrze zrozumiałem to menu jest w innym programie, więc już subclasing nie wchodzi raczej w grę. Wydeje mi się, że jedynym (przynajmniej mi znanm wyjściem) jest zastosowanie hooka. Musisz napisać sobie bibliotekę DLL ponieważ chcąc używać hooka na okna nie należące do do twojej aplikacji procedura obsługi hooka musi znajdować si w bibliotece DLL po to aby tamta aplkacja mogła ją dołączyć.
W skrócie, na moje oko to wygląda tak (chociaż nie sprawdzałem czy działa) Za pomocą SetWindowsHookEx z parametrem WH_CALLWNDPROC tworzysz hook i obserwujesz komunikaty (jeżeli chodzi o menu to intersuje cię WM_COMMAND), jeżeli wywołano odpowiedni komunkat sprawszasz czy chodzi o tą pozycje memu (np. sprawdzajac ID), jak sie zgadza to za pomocą SendMessage wysyłasz komunikat do twojego programu.
Nie piszę przykładu, bo to troche roboty jest a w tej chwili nie mam czasu. Mam nadzieję, że to przynajmniej trochę pomogło.
Pozdrawiam

0

A czy nie można podmienić procedury obsługi okna w tym innym programie, tak żeby jak dostanie komunikat o nacisnieciu manu to wysyla komunikat do mojego programu, no a reszte komunikatow przepuszcza do standardowej procedury obslugi okna (SetWindowLong, CallWndProc) .. ? Wie ktos czy to jest mozliwe i jak to zrobic ?

Z gory dziekuje

0

Niestety tak jak pisałem nie ma takiej możliwości jeżeli memu znajduje się w innym programie. Ale zrobienie hooka nie jest takie trudne. Poczytaj o funkcjach które wymieniłem w moim poprzdnim poscie , w google znajdziesz też kilka przykładów a gdyby coś sprawiało problemy to pisz tylko napisz kod co i jak robisz, wtedy ewentualnie postram się poprawić kod.

0

Trochę mi się nudziło i napisałem prosty przykład jak za pomocą hooka obsługiwać menu dodane do innej aplikacji (Autorowi pytania wysyłem na e-mail a tu piszę dla potomnych, którzy wiedzą do czego służy Szukaj)

To jest kod pprogramu który ma otrzymać komnikat o kliknięciu na dodane do programu menu.
Aby przykład się ładnie skompilował trzeba w Object Inspektorze zmienić nazwę formularza z domyslnej Form1 na frmMain, natępnie dodać button i Label, mieniając ich nazwy na btnClose i lblInfo. Następnie zapisać projeekt pod nazwą HookDemo.dpr a moduł UMain.pas. Teraz pozostaje wszystko w pliku UMain.pas zastąpić tym kodem.

Plik UMain.pas

unit UMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

const
  WM_MYMENUCLICK = WM_USER + 111; //wParam =0, lParam Menu ID
  ID_MENU = 1000;
  TEXT_MENU = 'Nowe Menu';
  HookLib = 'HookLib.dll';
  InstallHook = 'InstallHook';
  DeinstallHook = 'DeinstallHook';
type
  TInstallHook = function (hMyAppWnd, hHookWnd:THandle; MenuID: DWord): THandle;
  TDeinstallHook = function (hHook: THandle): Boolean;
  TfrmMain = class(TForm)
    lblInfo: TLabel;
    btnClose: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure btnCloseClick(Sender: TObject);
  private
    { Private declarations }
    hHookLib: Dword;
    lpInstallHook: TInstallHook;
    lpDeinstallHook: TDeinstallHook;
    hHook: Dword;
  public
    { Public declarations }
    procedure WMMyMenuClick(var Message: TMessage); message WM_MYMENUCLICK;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.WMMyMenuClick(var Message: TMessage);
begin
  MessageBox(Handle, TEXT_MENU, 'Hook Demo', MB_ICONINFORMATION or MB_SYSTEMMODAL);
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var
  WinPath: array [0..MAX_PATH] of Char;
  NotepadPath: string;
  fd: _WIN32_FIND_DATA;
  hFind: Cardinal;
  si: STARTUPINFO;
  pi: _PROCESS_INFORMATION;
  MenuItem: MENUITEMINFO;
  hNotepadWnd: THandle;
  hNotepadMenu: THandle;
  hHelpMenu: THandle;
  i: integer;
  MenuCaption: array [0..10] of Char;
begin
  lblInfo.Caption:= 'W menu Pomoc programu Notatnik, który powinien się'#13#10'' +
                    'otworzyć po uruchomieniu tego programu znjduje się'#13#10'' +
                    'nowa pozycja "Nowe Menu".'#13#10'' +
                    'Po kliknięciu na nią pojawi się komunikat.';
  hHookLib:= 0;
  hHook:= 0;
  //Znajdź scieżkę do programu Notatnik
  GetWindowsDirectory(winpath, MAX_PATH);
  NotepadPath:= WinPath + '\Notepad.exe';
  hFind:= FindFirstFile(PChar(NotepadPath), fd);
  if hFind = INVALID_HANDLE_VALUE then
  begin
    NotepadPath:= WinPath + '\System32\Notepad.exe';
    hFind:= FindFirstFile(PChar(NotepadPath), fd);
    if hFind = INVALID_HANDLE_VALUE then
    begin
      MessageBox(Handle, 'Nie znaleziono programu Notanik!',
                     'Hook Demo', MB_ICONERROR);
      exit;
    end;
  end;
  Windows.FindClose(hFind);
  
  //Uruchom Program Notatnik
  ZeroMemory(@si, sizeof(STARTUPINFO));
  si.cb:= SizeOf(STARTUPINFO);
  if CreateProcess(nil, PChar(NotepadPath), nil, nil, False, NORMAL_PRIORITY_CLASS,
           nil, nil, si, pi) then
  begin
    WaitForInputIdle(pi.hProcess, INFINITE); //Poczekaj na zaladowanie notatnika
    hNotepadWnd:= FindWindow('Notepad', nil); //Znajdz okno
    hNotepadMenu:= GetMenu(hNotepadWnd);
    i:=0;
    hHelpMenu:= GetSubMenu(hNotepadMenu, i);
    //Znajdź menu Pomoc
    while (hHelpMenu  <> 0) do
    begin
      GetMenuString(hNotepadMenu, i, MenuCaption, 10, MF_BYPOSITION);
      if lstrcmpi(MenuCaption, 'Pomo&c') = 0 then
        break;
      Inc(i);
      hHelpMenu:= GetSubMenu(hNotepadMenu, i);
    end;

    if (hNotepadWnd = 0) or (hHelpMenu = 0) then
    begin
      MessageBox(Handle, 'Nie znaleziono okna lub menu programu Notanik!',
                     'Hook Demo', MB_ICONERROR);
      exit;               
    end;
    //Wstaw nowe menu (jako pod menu Pomoc)
    MenuItem.cbSize:= SizeOf(MENUITEMINFO);
    MenuItem.fMask:= MIIM_TYPE or MIIM_STATE or MIIM_ID;
    MenuItem.fType:= MFT_STRING;
    MenuItem.fState:= MFS_ENABLED;
    MenuItem.wID:= ID_MENU;
    MenuItem.hSubMenu:= 0;
    MenuItem.hbmpChecked:= 0;
    MenuItem.hbmpUnchecked:= 0;
    MenuItem.dwItemData:= 0;
    MenuItem.dwTypeData:= TEXT_MENU;
    MenuItem.cch:= lstrlen(TEXT_MENU);
    MenuItem.hbmpItem:= 0;
    if InsertMenuItem(hHelpMenu, 0, True, MenuItem) = False then
    begin
       MessageBox(Handle, 'Nie udało się dodać menu!',
                     'Hook Demo', MB_ICONERROR);
       exit;
    end;
    //Ładuj biblioteke DLL i pobierz adresy funkcji
    hHookLib:= LoadLibrary(HookLib);
    if (hHookLib <> 0) then
    begin
       lpInstallHook:= GetProcAddress(hHookLib, InstallHook);
       lpDeinstallHook:= GetProcAddress(hHookLib, DeinstallHook);
       hHook:= lpInstallHook(Handle, hNotepadWnd, ID_MENU);
       if (hHook = 0) then
           MessageBox(Handle, 'Wystąpł błąd podczas próby założenia hooka!',
                     'Hook Demo', MB_ICONERROR)
    end
    else
    begin
      MessageBox(Handle, 'Wystąpł błąd podczas próby załadowania biblioteki DLL!',
                     'Hook Demo', MB_ICONERROR);
    end;
  end
  else
     MessageBox(Handle, 'Nie udało się uruchomić programu Notanik!',
                     'Hook Demo', MB_ICONERROR);
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if (hHook <> 0) then
    lpDeinstallHook(hHook);
  if (hHookLib <> 0) then
    FreeLibrary(hHookLib);
end;

procedure TfrmMain.btnCloseClick(Sender: TObject);
begin
  Close;
end;

end.

Oczywiście potrzebna jest jeszcze biblioteka DLL , więc trzeba steorzyć projekt nowej biblioteki DLL zapisać go pod nazwą HookLib.dpr a następnie zmienić kod pliku HookLib.dpr na ten:

Plik HookLib.dpr:

library HookLib;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  Windows, Messages;

const
  WM_MYMENUCLICK  = WM_USER + 111;  //wParam =0, lParam Menu ID
type

  TSharedData = record
    hMyAppWnd: THandle;
    hHookedWnd: THandle;
    MenuID: Dword;
    hHook: THandle;
  end;

  PSharedData = ^TSharedData;

var
  hSharedData: THandle;
  SharedDataPtr: PSharedData;


function GetMessageProc(nCode:DWORD;wParam:WPARAM;lParam:LPARAM): Longint; export; stdcall;
var
  hFile: THandle;
  DataPtr: PSharedData;
  hHook: THandle;
  hMyAppWnd: THandle;
  MenuID: Dword;
  Msg: tagMSG;
begin
  hFile:= OpenFileMapping(FILE_MAP_WRITE, True, 'SharedData');
  DataPtr:= MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  hHook:= DataPtr^.hHook;
  hMyAppWnd:= DataPtr^.hMyAppWnd;
  MenuID:= DataPtr^.MenuID;

  if (nCode = PM_NOREMOVE) then
  begin
    CopyMemory(@Msg, Pointer(lParam), SizeOf(tagMSG));

    if (Msg.message = WM_COMMAND) then
    begin
      if LOWORD(Msg.wParam) = MenuID then //Czy to dodane menu?
         SendMessage(hMyAppWnd, WM_MYMENUCLICK, 0, LOWORD(Msg.wParam));
    end;
    
  end;
  CallNextHookEx(hHook, nCode, wParam, lParam);
  UnmapViewOfFile(DataPtr);
  CloseHandle(hFile);
  result:= 0;
end;

//hMyAppWnd - uchwy okna programu
//hHookWnd - uchwyt okna na ktore zakladamy hook (0 - hook globalny)
//MenuID - ID menu na które zakladamy hook

function InstallHook(hMyAppWnd, hHookWnd:THandle; MenuID: DWord): THandle;
var
  hThread: THandle;
begin
//Instaluje Hook
result:=0;

//Pobieramy uchwyt watku, okna ktore chcemy przechwycic
hThread:= GetWindowThreadProcessId(hHookWnd, nil);
if hThread = 0 then
  exit;

//Tworzymy sobie tymczasowy plik na dane
hSharedData:= CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,
                        sizeof(TSharedData), 'SharedData');
if (hSharedData = 0) then
  exit;
SharedDataPtr:=MapViewOfFile(hSharedData, FILE_MAP_ALL_ACCESS, 0, 0, 0);

if (SharedDataPtr = nil) then
begin
  CloseHandle(hSharedData);
  exit;
end;

SharedDataPtr^.MenuID:= MenuID;
SharedDataPtr^.hHookedWnd:= hHookWnd;
SharedDataPtr^.hMyAppWnd:= hMyAppWnd;
SharedDataPtr^.hHook:= SetWindowsHookEx(WH_GETMESSAGE, @GetMessageProc,
                     hInstance, hThread);


result:= SharedDataPtr^.hHook;
end;

function DeinstallHook(hHook: THandle): Boolean;
begin
//Deinstaluje Hook
UnmapViewOfFile(SharedDataPtr);
CloseHandle(hSharedData);
result:= UnhookWindowsHookEx(hHook);
end;

exports
  InstallHook, DeinstallHook;

begin
end.

Aby przykład działał bllioteka DLL musi znajdować się w tymsamym katalogu co program HookDemo. Podczas uruchomiania programu powinien się także uruchomić Notatnik z Windows w którego menu Pomoc powinno się znajdować dodane menu "Nowe Menu". Po kliknięciu na nie zostanie wysłany komunikat do okna przykładowego programu (HookDemo), który go obsłuży i wyświetli MessageBox.
Przykład kompilowany na Delphi 6, sprawdzany na Windows 98 i XP

// wrzuć to do gotowców - Ł

1 użytkowników online, w tym zalogowanych: 0, gości: 1