全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:3387
推到 Plurk!
推到 Facebook!

inter-application messaging in delphi

 
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#1 引用回覆 回覆 發表時間:2004-05-30 13:52:13 IP:61.64.xxx.xxx 未訂閱
inter-application messaging in delphi http://www.wamoz.com/inter-app-messages-in-delphi.asp Abstract Delphi message binding requires statically defined message IDs, but inter-application messaging requires dynamic assignment by RegisterWindowMessage(). Techniques to automate mapping are detailed. Downloadable code includes transparent translation function wrappers and a helper component.  
 Nothing happens in Windows without messages. Move the mouse and there’s a veritable storm of them. Microsoft defines many messages that are documented in MSDN, and countless more that aren’t.     First signs of madness (talking to yourself) 
You can define your own messages. Perhaps you have, in your travels, seen something like this in a form or component declaration:     procedure WMRequestHostSite(var msg:TMessage); message WM_REQUESTHOSTSITE;     When the form or component window receives a WM_REQUESTHOSTSITE message, Delphi calls this method, passing as a parameter a TMessage, which is a record with four members: hWnd, Msg, wParam and lParam. HWnd is the window handle to which the message was addressed. This information isn’t useless – the message could have been broadcast. Msg is the MessageID, which is a DWORD, in this case with the value of WM_REQUESTHOSTSITE .     Then there are wParam and lParam. Both of these are 32-bits wide, but whether they should be treated as pointers, literals or window handles is entirely up to whoever dreams up the message.     Talking to another application 
Sounds pretty easy so far, and within an application things are straightforward. But what happens when you want to send your message to a window that belongs to another process? Delphi needs to resolve the value of WM_REQUESTHOSTSITE at compile-time. That means you have to declare it as an untyped static constant, and here’s the catch: to have a non-system-defined MessageID honoured between separate processes it must be allocated at run-time by RegisterWindowMessage().     This presents us with an apparent conundrum, because we have to provide the compiler with a value that can’t be determined until run-time. The solution is to do both, and map between the two values using two dynamic arrays of LongWord. This is implemented in the DynaMsg unit which is available for download.     We start by declaring the constants as usual in the interface section. In the implementation section we declare some variables: the message maps and a counter for use by the initialization section.     Const
  WM_MYMESSAGE = WM_APP   1;     Const 
  EXPECTED_MAP_SIZE = 4; //presize map, dynamically extend 
Var 
  GlobalMessage,LocalMessage:TLongWordDynArray; 
  RegisteredMessageCount:integer;     The initialisation section looks like this:     Initialization 
  SetLength(GlobalMessage,EXPECTED_MAP_SIZE); 
  SetLength(LocalMessage,EXPECTED_MAP_SIZE); 
  RegisteredMessageCount := 0; 
  RegisterMessage(WM_MYNEWMESSAGE,'WM_MYNEWMESSAGE');     The real magic happens in RegisterMessage(), which looks like this:     procedure RegisterMessage(const StaticMessage:UINT; MessageName:string); 
var 
  MapSize:integer; 
begin 
  MapSize := Succ(High(GlobalMessage)); //zero-based. 
  if (RegisteredMessageCount = MapSize) then 
  begin 
    Inc(MapSize,4); 
    SetLength(GlobalMessage,MapSize); 
    SetLength(LocalMessage,MapSize); 
  end; 
  GlobalMessage[RegisteredMessageCount] := 
    RegisterWindowMessage(PChar(MessageName)); 
  LocalMessage[RegisteredMessageCount] := StaticMessage; 
  Inc(RegisteredMessageCount); 
end;     After guaranteeing that the maps are large enough to accommodate another message, RegisterMessage() appends the static local and dynamic global message identifiers to their respective maps. Later on this information is used by three helper functions:     procedure TranslateToLocalWindowMessage(var WindowMessage:UINT); 
var 
  i:integer; 
begin 
  if (WindowMessage >= $C000) then 
    for i := 0 to Pred(RegisteredMessageCount) do 
      if (WindowMessage = GlobalMessage[i]) then 
      begin 
        WindowMessage := LocalMessage[i]; 
        break; 
      end; 
end;     procedure TranslateToGlobalWindowMessage(var WindowMessage:UINT); 
var 
  i:integer; 
begin 
  for i := 0 to Pred(RegisteredMessageCount) do 
    if (WindowMessage = LocalMessage[i]) then 
    begin 
      WindowMessage := GlobalMessage[i]; 
      break; 
    end; 
end;     function GlobalWindowMessage(const WindowMessage:LongWord):UINT; 
begin 
  result := WindowMessage; 
  TranslateToGlobalWindowMessage(result); 
end;     As you can see, these routines use a linear search to get the index, then look up the translation in the corresponding position in the other map. If they don’t find a value in the map then they return the value untranslated.     Using a linear search sounds inefficient, but my experience has been that the maps seldom have more than half a dozen entries. With such short lists, a linear search is not only the simplest but also the fastest due to the low overheads. You would have to need several dozen inter-application messages to contemplate a more elaborate search algorithm.     Sending a message 
Basically you need to translate the message and then send the message normally. The function GlobalWindowMessage() allows you to translate in-line, so you could write:     PostMessage(theHandle,GlobalWindowMessage(WM_MYMESSAGE),wParam,lParam);     Happily, you don’t have to do this yourself. The DynaMsg unit wraps the message sending APIs and performs this translation automatically (messages not in the maps are unaffected). So just add your message to the DynaMsg unit as described above, and include it in your uses clause.     Receiving a message 
To translate your messages before Delphi attempts to dispatch them, you need to subclass the WndProc method of the TComponent you’re working on, like so:     procedure TForm1.WndProc(var Message: TMessage); 
begin 
  TranslateToLocalWindowMessage(Message.Msg); 
  inherited; 
end;     This translates any dynamically allocated message IDs to their static counterparts and then lets dispatching proceed naturally.     This isn’t difficult, but we can make it tidier. Descendants of TControl have a WindowProc property you can exploit to conveniently chain their window procedure. Since TForm descends from TControl, we can exploit this property and create a component so that simply dropping it on a form will both add our unit to the uses clause and subclass the form’s window procedure.     Getting 'em hooked 
Since we're going to all the trouble of creating a component to subclass the window procedure, we may as well take the opportunity to provide a convenient message hook for further extension. This is a comparatively trivial extension; all we need to do is declare a private member to store a method reference, declare a corresponding property and, in the event [sic] that a handler method has been assigned, call it. Standard procedure, straight out of the book. It looks like this:     type
  TMsgEvent = procedure(var msg:TMessage; var Handled:boolean) of object;
  TDynaMsg = class(TComponent)
  private
    FOwnerWndProc:TWndMethod;
    FOnMessage: TMsgEvent;
  public
    destructor Destroy; override;
    constructor Create(AOwner: TComponent); override;
    procedure WindowProcedure(var msg: TMessage);
  published
    property OnMessage:TMsgEvent read FOnMessage write FOnMessage;
  end;     procedure TDynaMsg.WindowProcedure(var msg: TMessage);
var
  Handled:boolean;
begin
  TranslateToLocalWindowMessage(msg.Msg);
  Handled := false;
  if Assigned(FOnMessage) and ReadyAtRuntime(Self) then FOnMessage(msg,Handled);
  if Assigned(FOwnerWndProc) and not Handled then FOwnerWndProc(msg);
end;     The ReadyAtRuntime() function is a helper function that determines whether a component is ready for use at run-time. You've probably seen code like this: if not (csDesigning in ComponentState) then //do run-time only action and the helper function simply takes this a little further. It's worth looking at, since it illustrates the use of the * operator to perform set intersection. Here's the code.    function ReadyAtRuntime(AComponent:TComponent):boolean;
begin
  result := [] = AComponent.ComponentState *
    [csLoading,csReading,csWriting,csDestroying,
    csDesigning,csUpdating,csFixups];
end;     Our component as it now stands presents an OnMessage event in the property inspector. There are two parameters allow you to access the message and optionally suppress further handling. If you want to define your own message cracker then start by having a look at how Borland did it in the Messages unit. The typecast is a little unconventional - illegal, in fact. Don't worry about it. Cast the address to a generic pointer, cast that to your type and dereference it, like this.    type
  TWMDisplayChange = packed record
    Msg: Cardinal;
    BitsPerPixel: Integer;
    Width: Word;
    Height: Word;
    Result: Longint;
   end;
  PWMDisplayChange = ^TWMDisplayChange;    procedure TForm1.DynaMsg1Message(var msg:TMessage; var Handled: Boolean);
var
  MyMsg:TWMDisplayChange;
begin
  MyMsg := PWMDisplayChange(Pointer(@msg))^;
  //do something with MyMsg
end;    Something to bear in mind is that although it's always possible to sidestep type checking like this, it's rarely a smart thing to do. You must be absolutely sure that the typecast is either safe or wrapped in an exception handler.     Extension
Now that we have all the pieces, it would be a good idea to separate the declaration of custom messages from all the supporting machinery, especially since part of the supporting machinery is hooked into the Delphi IDE and we don't want to be rebuilding it all the time. The process is not difficult, requiring changes only to the interface visibility of some elements. You won't see this because I've already sorted it out in the example code, which is split into DynaMsg.pas and CustomMessages.pas.    Downloads
One of the sample programs requires a freeware custom control that makes a form into an AppBar. This is not my work. I present it here complete with source and copyright notices mostly because I don't remember where I found it. It's a nice piece of work, well worth adding to your toolbox.    You should begin by installing the AppBar custom control. Then install TDynaMsg which is in DynaMsg.pas. After that you should be able to open the sample application group NewUI.bpg.    All this code was written with Delphi 7, but it should be compatible all the way back to Delphi 3. Earlier versions of Delphi will complain about missing properties when you open forms in the sample programs. These properties are irrelevant to the demo so just ignore the messages and re-save the files and all should be fine.    AppBar14.zip this is NOT my work but you'll need it    WM_EXTENSIONS.zip  contains article source code and sample programs    
系統時間:2024-05-18 23:39:21
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!