Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

(Ebook - Pdf) Kick Ass Delphi Programming

.pdf
Скачиваний:
284
Добавлен:
17.08.2013
Размер:
5.02 Mб
Скачать

{Create a semaphore to control access to the mapped memory. If the handle comes back zero, we failed for some reason. }

SemaphoreHnd := CreateSemaphore(nil, 0, 1, SemaphoreName); LastErr := GetLastError;

if (SemaphoreHnd = 0) then begin

CloseHandle(ArrayHnd);

UnmapViewOfFile(ArrayBasePtr);

Exit;

end;

{Save then exit chain and insert the finalization routine. } SaveExit := ExitProc;

ExitProc := @FinalizeLibrary;

{If we created the semaphore, then force it to its "signaled" state. } if LastErr <> ERROR_ALREADY_EXISTS

then ReleaseSemaphore(SemaphoreHnd, 1, nil);

DLLValid := True; end.

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.

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

Go!

Keyword

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Startup Code

Perhaps the most interesting part of the DLL is what happens when it starts and ends. It was pointed out by several of the residents that each process which employs a sender or receiver component would undoubtedly load its own copy of the DLL, meaning there could be several DLL instances loaded and running simultaneously. To allow that, several things must be true:

There must be only one (global) memory area containing the references to which form handles have been registered;

All instances of the DLL must be capable of cooperatively managing the global memory area;

There must be a mechanism for a DLL instance to create the shared memory area if there is none, and to destroy the memory area when there is no longer a need for it, and

Only one DLL at a time can be allowed to change the contents of the memory.

As it turns out, this gnarly little problem was not as bad as it sounded. Win95 provides some good tools not only for creating shared memory, but for managing access to it as well.

The CreateFileMapping function returns a handle to a memory file object. If the object doesn’t exist, it gets created, and a chunk of the system’s paging file—in this case, a chunk of memory big enough to hold the data for 100 receivers—is reserved. The “file” is given a name to ensure it is exclusive to this DLL.

If the memory file already exists, the handle to it is returned, but an ERROR_ALREADY_EXISTS error value is generated. By testing for this value, an instance of the DLL can determine if it has created the memory file. If so, it is free to use FillChar to clear the entire area—once we know its

location. (Since the memory file can be considered merely as a chunk of memory, that’s how it will be treated from here on.)

The location of the memory is mapped to a pointer within the application through a call to MapViewOfFile, using the handle returned by CreateFileMapping. In this example, the value is assigned to ArrayBasePtr. The combination of calling CreateFileMapping and MapViewOfFile is a lot like making a call to GetMem.

Signals from a Semaphore

“Okay, Wunderkind,” said the old guy wearing the shopping bag. “How are you gonna keep multiple instances of the DLL from clobbering the shared memory? What if one instance adds something, and then another instance comes in right behind it and adds it again?”

“That’s a fair question,” I replied. “I think this situation calls for a semaphore.”

Win95 provides several synchronization objects, ranging from the trivial to the complex—semaphores, events, and mutexes. But for restricting access to the memory in this situation, the clear choice would be a semaphore. This puppy would enable different processes to cooperatively use the memory through a signal-and-counter system. Like the call to CreateFileMapping, the call to CreateSemaphore either returns the handle to a newly created object (in this case a semaphore), or the handle to an existing object, while generating an ERROR_ALREADY_EXISTS error code. If a new instance of the DLL creates the semaphore, it is given the sole responsibility for setting the semaphore to its signaled state.

By “signaled,” it means the semaphore’s counter has a non-zero value. Each time a process executes a call to WaitForSingleObject using the semaphore’s handle, the semaphore’s counter is examined. If it is non-zero, the counter is decremented by one and the next program statement in the process is executed. If the counter is already zero, the process goes into an efficient time-wasting loop, waiting for the counter to change to a non-zero value (or a timeout value to be exceeded).

The code in this example permits only two values (0 and 1) for the semaphore counter, effectively turning it into an off/on switch. One of our design goals was that, as long as one instance of a DLL was accessing the shared memory—for any purpose—no other instance could gain access at the same time. Listing 15.4 is an example routine that shows how we could use a semaphore to achieve that goal.

Listing 15.4 Using a semaphore to control memory access

procedure DoSomething; begin

WaitForSingleObject(SemaphoreHnd, INFINITE); DoSomeStuffWithTheMemory; ReleaseSemaphore(SemaphoreHnd, 1, nil);

end;

In this simple example, we first test the value of the semaphore. If another process is accessing the memory at the moment, the semaphore will be in its non-signaled state (0), and we will wait until forever (as specified by the INFINITE timeout constant), if necessary, for the semaphore to change to its signaled state (1).

Once the semaphore is signaled, its counter is automatically decremented by one (to zero), and our process goes ahead and DoSomeStuffWithTheMemory executes. While this is going on, any other process that examines the semaphore will find it unsignaled and (if the process has properly used a call to WaitForSingleObject) it will bide its time until we’re through.

When we’re all done with the memory, we place a call to ReleaseSemaphore, which will increment the semaphore’s counter by the amount we specify. We use a value of one, in effect turning “on” the semaphore “switch.”

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.

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

Go!

Keyword

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Shutdown Code

If we make it all the way through the DLL startup code without error, we set a variable (DLLValid) to attest to our success. But if we have succeeded, several handles have been created that must be released when Win95 unloads the library.

This is taken care of by creating FinalizeLibrary, an exit routine that will be executed when the DLL is shut down. The procedure is inserted in the program’s chain of events by saving the pointer to the normal exit routine (ExitProc) in the variable SaveExit, and then pointing ExitProc to

FinalizeLibrary.

When the library is unloaded, FinalizeLibrary will execute. It will release the map view assigned to ArrayBasePtr, and then release the handles to the memory file object and the semaphore. Finally, it will restore the link to the standard exit procedure, so the program will terminate normally.

And what about the memory file object itself? Win95 automatically takes care of that for us. Like many of the system calls that create objects, Win95 associates a counter with the memory file object. Each time a CreateFile Mapping call is executed, the system increments a counter for the associated object. Whenever a handle to that object is released, the counter is decremented. When the count reaches zero, Win95 knows the object is no longer being used, so it destroys it. Last one out, turn out the lights.

Examining the DLL Routines

Most of the routines exported by the DLL busy themselves with stepping through the records in the memory array. Since we know the base address of the array, the size of a record, and the maximum number of records, we can easily tiptoe our way through the array using the pointer arithmetic provided

by Object Pascal.

The NumMsgReceivers function provides a good example of this technique. A pointer of type PMsgReceiverRec is assigned to the base address. Each time a record is examined, the Inc procedure is used to advance the pointer by exactly the size of a TMsgReceiverRec. Tracking the process with an index variable prevents us from accessing memory outside the bounds of the memory block we have been allocated. The NumMsgReceivers function simply counts all the records that have their Assigned field set True.

The Assigned field is managed by two routines, RegisterReceiver and

UnregisterReceiver. RegisterReceiver looks for the first unused record and, after clearing it, stores the handle value and class name passed into

RegisterReceiver. It also sets the record’s Assigned field True.

UnregisterReceiver, to no great surprise, does pretty much the opposite. It searches the fields with Assigned set True, looking for a handle value that matches the one specified. If the record is found, it sets the Assigned field

False.

BroadcastToOne, BroadcastToClass, and BroadcastToAll use the

SendMessage API function to transmit a message to the handle of a specific form, handles of all active forms of a given class name, or the handles of all active forms in the list. BroadcastToOne returns the value returned to it from the call to SendMessage; the other two return the first non-zero value returned by a call to SendMessage.

FirstReceiverAssigned and NextReceiverAssigned together provide a way for a user of the DLL to construct a list of all currently active record numbers in the array. It works a bit like the DOS FindFirst and FindNext routines. In this case, FirstReceiverAssigned returns an index to the first active form in the array. If that index value is then passed to NextReceiverAssigned, it will return an index to the next form, starting its search from the specified index. If no active form is found, these routines return a negative one.

Finally, BcastDLLValid returns a boolean that indicates whether or not the DLL was able to set up (or connect to) the required shared memory and the semaphore.

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.

Go!

Keyword

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Creating the Sender Component

The residents were quietly buzzing among themselves. In a back corner, the murmuring broke out into shouting, as two nearly bald-headed programmers began arguing the various merits of their favorite programming languages at the tops of their lungs. The altercation soon turned to fisticuffs, and it took three staff members to pull the septuagenarians apart.

“Is it always like this around here?” I asked Dinah.

“No,” she replied. “But yesterday one of the guests asked for a cup of Java with creamer, and someone else thought he said the Java language was for dreamers, and clobbered the poor man along side of the head with his standard-issue wrist brace. That one took five stitches.”

I examined the crowd more carefully. Sure enough, nearly everyone was wearing an identical brace.

She had apparently noticed my look of dismay. “Carpal Tunnel Syndrome,” she sighed, with a look full of sorrow. “It gets even the best of them, sooner or later. Researchers are still looking for a cure.”

I nodded in sympathy. The dispute now under control, I decided it would be an opportune time to segue back to developing the program.

With the DLL now under my belt, I was able to move to the next piece of the puzzle. I chose what I would call the MsgSender component. This component would, on command, take a snapshot of the list of registered form handles maintained by the DLL. The component would also be able to call selected DLL routines to broadcast messages and to determine the number of receiver components registered at any given time.

The MsgSender component, I reasoned, would need only one property: the unique message string that would be set to match a corresponding receiver component. The unique message ID, returned by using the message string in a call to RegisterWindowMessage, would not be made available outside the component.

Listing 15.5 illustrates the final version of the code for the MsgSender component.

Listing 15.5 Code for the MsgSender component

{———————————————————————————————————————————————————}

{

Message Broadcasting in Delphi 2

}

{

MSGSENDR.PAS : MsgSender Component

}

{

By Ace Breakpoint, N.T.P.

}

{

Assisted by Don Taylor

}

{

 

}

{ This component registers a special system-wide

}

{ message with Win95, and then is able to send

}

{ system-wide messages to other forms in the

}

{ system via MsgReceiver components.

}

{

 

 

}

{ Written for *Kick-Ass Delphi Programming*

}

{ Copyright (c) 1996

The Coriolis Group, Inc.

}

{

Last

Updated 5/2/96

}

{———————————————————————————————————————————————————}

unit MsgSendr;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type

TBcastDLLValid = function : Boolean;

TRegisterReceiver = procedure(Hnd : THandle; CName : ShortString); TUnregisterReceiver = procedure(Hnd : THandle);

TBroadcastToClass = function(CName : ShortString; MessageID : Word; wParam : Word;

lParam : Longint) : Longint;

TBroadcastToAll = function(MessageID : Word; wParam : Word;

lParam : Longint) : Longint;

TBroadcastToOne = function(MessageID : Word; RcvNum : Integer; wParam : Word;

lParam : Longint) : Longint;

TNumClassMsgReceivers = function(CName : ShortString) : Integer; TNumMsgReceivers = function : Integer;

TFirstReceiverAssigned = function : Integer; TNextReceiverAssigned = function(RcvrNum : Integer) : Integer;

TMsgSender = class(TComponent) private

FIDString : ShortString; FMessageID : Word; LibraryHandle : THandle; DLLValid : Boolean; BcastDLLValid : TBcastDLLValid;

BroadcastToClass : TBroadcastToClass; BroadcastToAll : TBroadcastToAll; BroadcastToOne : TBroadcastToOne; NumClassMsgReceivers : TNumClassMsgReceivers; NumMsgReceivers : TNumMsgReceivers; FirstReceiverAssigned : TFirstReceiverAssigned; NextReceiverAssigned : TNextReceiverAssigned; procedure SetIDStr(NewID : ShortString); procedure RegisterIDStr;

public

ReceiverList : TStringList; { Read only -- do not modify! } constructor Create(AOwner : TComponent); override; destructor Destroy; override;

function ClassBroadcast(CName : ShortString; wParam : Word;

lParam : Longint) : Longint;

function AllBroadcast(wParam : Word; lParam : Longint) : Longint; function OneBroadcast

(RcvNum : Integer; wParam : Word; lParam : LongInt) : Longint; function NumClassReceivers(CName : ShortString) : Integer; function NumReceivers : Integer;

procedure UpdateReceiverList;

published

property IDString : ShortString read FIDString write SetIDStr; end;

procedure Register;

implementation

constructor TMsgSender.Create(AOwner : TComponent); begin

DLLValid := False; inherited Create(AOwner); FMessageID := 0;

if not (csDesigning in ComponentState) then begin

LibraryHandle := LoadLibrary('BCASTDLL.DLL'); if LibraryHandle > HINSTANCE_ERROR then

then begin

@BroadcastToClass :=

GetProcAddress(LibraryHandle, 'BroadcastToClass'); @BroadcastToAll :=

GetProcAddress(LibraryHandle, 'BroadcastToAll'); @BroadcastToOne :=

GetProcAddress(LibraryHandle, 'BroadcastToOne'); @NumClassMsgReceivers :=

GetProcAddress(LibraryHandle, 'NumClassMsgReceivers'); @NumMsgReceivers :=

GetProcAddress(LibraryHandle, 'NumMsgReceivers'); @FirstReceiverAssigned :=

GetProcAddress(LibraryHandle, 'FirstReceiverAssigned'); @NextReceiverAssigned :=

GetProcAddress(LibraryHandle, 'NextReceiverAssigned'); @BcastDLLValid :=

GetProcAddress(LibraryHandle, 'BcastDLLValid'); DLLValid := BcastDLLValid;

RegisterIDStr; end

else MessageDlg('Could not load DLL "BCASTDLL.DLL"', mtError, [mbOK], 0);

end;

ReceiverList := TStringList.Create; end;

destructor TMsgSender.Destroy; begin

ReceiverList.Free;

if not (csDesigning in ComponentState)

then if LibraryHandle > HINSTANCE_ERROR then

FreeLibrary(LibraryHandle); inherited Destroy;

end;

procedure TMsgSender.SetIDStr(NewID : ShortString); begin

if NewID <> FIDString then begin

FIDString := NewID; RegisterIDStr;

end;

end;

procedure TMsgSender.RegisterIDStr; var

IDStr : Array [0..255] of Char; begin

if not (csDesigning in ComponentState) and (Length(FIDString) > 0) then begin

StrPCopy(IDStr, FIDString);

FMessageID := RegisterWindowMessage(IDStr); end

else FMessageID := 0; end;

function TMsgSender.ClassBroadcast(CName : ShortString; wParam : Word;

lParam : Longint) : Longint;

begin

if DLLValid

then Result := BroadcastToClass(CName, FMessageID, wParam, lParam) else Result := -1;

end;

function TMsgSender.AllBroadcast(wParam : Word; lParam : Longint) : Longint;

begin

if DLLValid

then Result := BroadcastToAll(FMessageID, wParam, lParam) else Result := -1;

end;

function TMsgSender.OneBroadcast(RcvNum : Integer; wParam : Word;

lParam : Longint) : Longint;

begin

if DLLValid

then Result := BroadcastToOne(FMessageID, RcvNum, wParam, lParam) else Result := -1;

end;

function TMsgSender.NumClassReceivers(CName : ShortString) : Integer; begin

if DLLValid

then Result := NumClassMsgReceivers(CName) else Result := -1;

end;

function TMsgSender.NumReceivers : Integer; begin