(Ebook - Pdf) Kick Ass Delphi Programming
.pdfI stepped up to the microphone and tried to get a grip on myself. “What we’ll be covering today won’t quite be ‘finer points’ of Win95 programming. Instead you’ll hear some really basic information I’m sure you’ll all want to know—”
I felt a dull pain between my ribs, and looked down to see an old coot who had gotten up from his seat in the front row. “Listen, Sonny,” he said, poking me in the chest with the tip of his telescoping presentation pointer. “All I want to know is how you would have two different Win95 applications communicate between themselves, using standard Windows messaging.”
Specifying the Problem
“Yeah!” someone else shouted. Then, one by one, residents began to voice their requirements for a problem specification.
“What if you’ve got multiple instances of sender and receiver apps—how are you going to satisfy them all?” yelled a shriveled little man in the fourth row. “And how are you going to make sure any message you’re broadcasting throughout the system won’t trigger an unwanted side effect on some other application that happens to use the same message ID?”
“How about the ability to selectively send a message to a single form, all forms, or certain classes of forms with reception capability?” added the man in the first row with a bag over his head. He looked somehow familiar....
“Componentize it, componentize it!” shouted another man. One by one, every person in the audience began to pick up the chant. Pandemonium reigned for several minutes. I tried to get my bearings and come up with some sort of approach.
As luck would have it, several staff members came in, rolling carts loaded with takeout pepperoni pizza. Within two or three minutes, the entire room had quieted down once again, as the residents munched on the tasty fare. I was able to use those precious moments to create the diagram shown in Figure 15.1.
FIGURE 15.1 Relationship of entities comprising Sender and Receiver components.
“Ladies and gentlemen,” I began, as I projected my bitmap on the screen. “Here is one possible approach to the problem you’ve described. We could create two separate components, a sender component and a receiver component. Each of these components would share some common routines contained in a Dynamic Link Library (DLL). On creation, each receiver component would register itself with the DLL, which would maintain in shared memory an array of records. Each record would contain the handle values and class names of all active receivers, along with a boolean that indicates whether
or not that record was occupied. A receiver would unregister itself with the DLL as part of the receiver’s destructor routine.”
I swept my eyes across the crowd, looking for major objections. Seeing none, I continued. “Sender components can have access to the handles of the receivers by querying the DLL. By calling routines included in the DLL, a sender can send a message selectively to any or all of the handles it gets from the query.”
“Wait a second,” came a voice from the back of the room. “You skipped over a very important point. You’re talking about sending messages to a form in another process. How are you going to make sure the message IDs you’re going to broadcast aren’t used by some other process?”
“By being different,” I replied. “Windows gives us an API call named RegisterWindowMessage. This function accepts a string and returns a message ID that is guaranteed to be unique throughout the system. If a second application tries to register the same string, it is given the same unique message ID. This is how the sender and receiver components can share a unique message—by simply sharing a string that is made a property of each component.”
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
begin
if Msg.Msg = FMessageID then begin
if Assigned(FOnIDMessage)
then Msg.Result := FOnIDMessage(Msg.wParam, Msg.lParam); end
else Dispatch(Msg); end;
“Hold on, Wizard Boy,” came a voice from somewhere off to the left. “Now you’ve overstepped your bounds. You’re presuming the receiver component has somehow taken over the message processing for the form’s window. But you haven’t subclassed the window.”
“It will be part of the component’s design for it to automatically subclass the form on which it resides,” I explained. “But enough of this talk. Let’s write some code so we can explore this whole concept.”
I looked around the room. For the first time, they had all sat back in their chairs. I had their undivided attention. Now I just had to do the impossible: Make them happy.
Designing the DLL
By far, the DLL would be the biggest challenge. It was also the centerpiece of the whole process, so it seemed like a logical place to start. With the residents shouting out their own ideas for what needed to be included in the DLL, we hammered out a rough version. I couldn’t believe how quickly we came up with the final version of the code, shown in Listing 15.3.
Listing 15.3 DLL for sender/receiver component support
{———————————————————————————————————————————————————}
{ |
Message Broadcasting in Delphi 2 |
} |
{ |
BCASTDLL.DLL : Library File |
} |
{ |
By Ace Breakpoint, N.T.P. |
} |
{ |
Assisted by Don Taylor |
} |
{ |
|
} |
{ DLL that provides the underlying function of |
} |
|
{ managing a list of registered components in |
} |
|
{ shared memory. |
} |
|
{ |
|
} |
{ Written for *Kick-Ass Delphi Programming* |
} |
|
{ Copyright (c) 1996 The Coriolis Group, Inc. |
} |
|
{ |
Last Updated 5/2/96 |
} |
{———————————————————————————————————————————————————}
library BcastDll;
uses SysUtils, Classes, Windows;
type
PMsgReceiverRec = ^MsgReceiverRec; MsgReceiverRec =
record
Assigned : Boolean; WndHandle : HWND; WndClassName : ShortString; end; { record }
const
MaxReceivers = 100;
ArraySize = MaxReceivers * SizeOf(MsgReceiverRec); MemFileName = 'msg_reg_array';
SemaphoreName = 'msg_bcast_semaphore';
var |
|
ArrayHnd |
: HWND; |
ArrayBasePtr : PMsgReceiverRec; |
|
SemaphoreHnd |
: HWND; |
DLLValid |
: Boolean; |
SaveExit |
: Pointer; |
LastErr |
: Longint; |
{ BcastDLLValid returns a Boolean that lets the caller know that the shared memory and semaphores are operational, so all of the other exported routines are valid. }
function BcastDLLValid : Boolean; begin
Result := DLLValid; end;
{RegisterReceiver adds the specified handle and class name to the array of receivers in shared memory. It returns the
index (plus 1) of the new entry, or -1 if it couldn't be found. }
function RegisterReceiver(Hnd : THandle; CName : ShortString) : Integer; var
Idx : Integer; Found : Boolean;
RecPtr : PMsgReceiverRec; begin
Idx := 0; Result := -1;
RecPtr := ArrayBasePtr; Found := False;
WaitForSingleObject(SemaphoreHnd, INFINITE);
{ Find an unused slot } repeat
Found := RecPtr^.Assigned = False; if not Found
then begin Inc(Idx); Inc(RecPtr);
end;
until Found or (Idx >= MaxReceivers);
if Found
then begin { Put the record in the slot } FillChar(RecPtr^, 0, SizeOf(MsgReceiverRec)); with RecPtr^ do
begin
Assigned := True;
WndHandle := Hnd; WndClassName := CName;
end; { with } Result := Idx + 1;
end; ReleaseSemaphore(SemaphoreHnd, 1, nil);
end;
{ UnregisterReceiver removes the record with the specified handle from the array. It returns the former index (plus 1) of the specified receiver, or -1 if it couldn't be found. }
function UnregisterReceiver(Hnd : THandle) : Integer; var
Idx : Integer; Found : Boolean;
RecPtr : PMsgReceiverRec; begin
Idx := 0; Result := -1;
RecPtr := ArrayBasePtr; Found := False;
WaitForSingleObject(SemaphoreHnd, INFINITE);
{ Locate the record } repeat
Found := RecPtr^.Assigned and (RecPtr^.WndHandle = Hnd); if not Found
then begin Inc(Idx); Inc(RecPtr);
end;
until Found or (Idx >= MaxReceivers);
if Found then begin
RecPtr^.Assigned := False; Result := Idx + 1;
end;
ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
{BroadcastToClass uses SendMessage to broadcast the specified message to all registered components with the specified class. Returns as a result the value from the first message that returns a non-zero result. }
function BroadcastToClass(CName : ShortString; MessageID : Word; wParam : Word;
lParam : Longint) : Longint;
var
Idx : Integer;
RecPtr : PMsgReceiverRec; begin
Idx := 0; Result := 0;
RecPtr := ArrayBasePtr; WaitForSingleObject(SemaphoreHnd, INFINITE);
{ Send the message to all matching entries } repeat
if RecPtr^.Assigned and (RecPtr^.WndClassName = CName) then begin
if Result = 0
then Result :=
SendMessage(RecPtr^.WndHandle, MessageID, wParam, lParam) else SendMessage(RecPtr^.WndHandle, MessageID, wParam, lParam);
end;
Inc(Idx);
Inc(RecPtr);
until Idx >= MaxReceivers; ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
{ BroadcastToAll uses SendMessage to broadcast the specified message to all registered components. Returns as a result
the value from the first message that returns a non-zero result. } function BroadcastToAll(MessageID : Word;
wParam : Word;
lParam : Longint) : Longint;
var
Idx : Integer;
RecPtr : PMsgReceiverRec; begin
Idx := 0; Result := 0;
RecPtr := ArrayBasePtr; WaitForSingleObject(SemaphoreHnd, INFINITE);
repeat
if RecPtr^.Assigned then begin
if Result = 0 then Result :=
SendMessage(RecPtr^.WndHandle, MessageID, wParam, lParam) else SendMessage(RecPtr^.WndHandle, MessageID, wParam, lParam);
end;
Inc(Idx);
Inc(RecPtr);
until Idx >= MaxReceivers; ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
{BroadcastToOne uses SendMessage to broadcast the specified message to only one specified registered component. Returns as a result the value received from the component. }
function BroadcastToOne(MessageID : Word; RcvNum : Integer;
wParam : Word;
lParam : Longint) : Longint;
var
RecPtr : PMsgReceiverRec; begin
Result := 0;
RecPtr := ArrayBasePtr;
if (RcvNum > 0) and (RcvNum <= MaxReceivers) then begin
WaitForSingleObject(SemaphoreHnd, INFINITE);
Inc(RecPtr, RcvNum - 1); if RecPtr^.Assigned
then Result :=
SendMessage(RecPtr^.WndHandle, MessageID, wParam, lParam);
ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
end;
{NumClassMsgReceivers returns the quantity of registered receivers of the specified class. }
function NumClassMsgReceivers(CName : ShortString) : Integer; var
Idx : Integer;
RecPtr : PMsgReceiverRec; begin
Idx := 0; Result := 0;
RecPtr := ArrayBasePtr; WaitForSingleObject(SemaphoreHnd, INFINITE); repeat
if RecPtr^.Assigned and (RecPtr^.WndClassName = CName) then Inc (Result);
Inc(Idx);
Inc(RecPtr);
until Idx >= MaxReceivers;
ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
{NumMsgReceivers returns the total quantity of registered receivers, regardless of class. }
function NumMsgReceivers : Integer; var
Idx : Integer;
RecPtr : PMsgReceiverRec; begin
Idx := 0; Result := 0;
RecPtr := ArrayBasePtr; WaitForSingleObject(SemaphoreHnd, INFINITE); repeat
if RecPtr^.Assigned then Inc(Result); Inc(Idx);
Inc(RecPtr);
until Idx >= MaxReceivers; ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
{ First ReceiverAssigned returns the index + 1 of the first registered receiver in the list. If the list is empty, -1 is returned. }
function FirstReceiverAssigned : Integer; var
Idx : Integer; Found : Boolean;
RecPtr : PMsgReceiverRec;
begin
Idx := 0;
RecPtr := ArrayBasePtr; WaitForSingleObject(SemaphoreHnd, INFINITE);
repeat
Found := RecPtr^.Assigned; if not Found
then begin Inc(Idx); Inc(RecPtr);
end;
until Found or (Idx >= MaxReceivers);
if Found
then Result := Idx + 1 else Result := -1;
ReleaseSemaphore(SemaphoreHnd, 1, nil); end;
{NextReceiverAssigned returns the index number + 1
of the next registered receiver in the list, following a specified "receiver number" (i.e., the starting index + 1). If there are no more receivers in the list, -1 is returned. }
function NextReceiverAssigned(RcvrNum : Integer) : Integer; var
Idx : Integer; Found : Boolean;
RecPtr : PMsgReceiverRec; begin
if (RcvrNum >= 1) and (RcvrNum < MaxReceivers) then begin
Idx := RcvrNum;
RecPtr := ArrayBasePtr; Inc(RecPtr, Idx);
WaitForSingleObject(SemaphoreHnd, INFINITE);
repeat
Found := RecPtr^.Assigned; if not Found
then begin Inc(Idx); Inc(RecPtr);
end;
until Found or (Idx >= MaxReceivers);
if Found
then Result := Idx + 1 else Result := -1;
ReleaseSemaphore(SemaphoreHnd, 1, nil); end
else Result := -1; end;
{Finalize unmaps and releases the shared memory, then releases the semaphore. If there is another instance of
this DLL active at the moment, the memory and semaphore will not go away. When all other instances of the DLL have been closed, the OS will destroy the reserved memory and semaphore. }
procedure FinalizeLibrary; begin UnmapViewOfFile(ArrayBasePtr); CloseHandle(ArrayHnd); CloseHandle(SemaphoreHnd);
{ Restore the exit chain } ExitProc := SaveExit; end;
exports
BcastDLLValid index 1, RegisterReceiver index 2, UnregisterReceiver index 3, BroadcastToClass index 4, BroadcastToAll index 5, BroadcastToOne index 6, NumClassMsgReceivers index 7, NumMsgReceivers index 8, FirstReceiverAssigned index 9, NextReceiverAssigned index 10;
begin
{ Library initialization code }
DLLValid := False;
{Attempt to create a memory-mapped file. If it already exists, that’s fine. In either case we will get a valid handle. }
ArrayHnd := CreateFileMapping(HWND($FFFFFFFF), nil, PAGE_READWRITE, 0,
ArraySize,
MemFileName);
{If we failed, we’re done. } if (ArrayHnd = 0) then Exit;
{Map the file by getting a pointer to it } LastErr := GetLastError;
ArrayBasePtr := MapViewOfFile(ArrayHnd, FILE_MAP_WRITE, 0, 0, 0);
{If we didn't get a valid pointer, we’re done. }
if ArrayBasePtr = nil then begin
CloseHandle(ArrayHnd);
Exit;
end;
{ If we actually created the mapped memory, clear it. } if LastErr <> ERROR_ALREADY_EXISTS
then FillChar(ArrayBasePtr^, 0, ArraySize);