• No results found

Coding for Flexibility

Many products provide “hooks” to which third parties can hang on additional modules. Windows Help, for example, defines an interface through which developers can add custom macros and embedded windows that provide some very interesting features to Windows Help files. Borland’s new C++ 5.0 IDE also has an add-on interface that other companies are using to add features.

Version control and a Java development add-on are shipped with BC++ 5.0. Both are implemented using the DLL add-on interface.

I’ve offered the example of word processor format conversion in this chapter as an example of one possible use of DLLs. Let’s develop that idea a little further by writing a mini text editor that offers an add-on format conversion interface. The text editor itself is very simple-minded—just a Memo component and menu options to open and save files. That’s okay, though. What we’re really interested in is the format conversion interface.

Previous Table of Contents Next

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.

Brief Full Advanced Search

Search Tips

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:

Previous Table of Contents Next

Creating the Text Editor

Since we’re all programmers here, I’m going to move fairly rapidly through the mechanics of creating the text editor’s shell. I’ll slow down when we get to the add-on interface.

Starting with a new project, add a Memo component to the form and set its Align property to alClient so that it takes up the entire form. Then add MainMenu, OpenDialog, and SaveDialog components to the form. In the Menu Designer, add three items to the menu: Open, Save, and Exit. Save the unit as EDITFORM.PAS and the project file as TEXTEDIT.DPR. The completed form is shown in Figure 3.1, and the complete program listing is shown in Listing 3.6.

FIGURE 3.1 The completed text editor form.

Listing 3.6 The text editor form, EDITFORM.PAS

interface uses

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

Menus, StdCtrls;

type

TForm1 = class(TForm) Memo1: TMemo;

OpenDialog1: TOpenDialog;

SaveDialog1: TSaveDialog;

MainMenu1: TMainMenu;

File1: TMenuItem;

Open1: TMenuItem;

Save1: TMenuItem;

Go!

Keyword

---Go!

N1: TMenuItem;

Exit1: TMenuItem;

procedure Exit1Click(Sender: TObject);

procedure Open1Click(Sender: TObject);

procedure Save1Click(Sender: TObject);

private

{ Private declarations } FileName : String;

procedure OpenFile(Filename: String);

procedure SaveFile(Filename: String);

public

{ Public declarations } end;

var

Form1: TForm1;

implementation {$R *.DFM}

procedure TForm1.Exit1Click(Sender: TObject);

begin Close;

end;

procedure TForm1.Open1Click(Sender: TObject);

begin

if OpenDialog1.Execute then

OpenFile (OpenDialog1.FileName);

end;

procedure TForm1.Save1Click(Sender: TObject);

begin

if SaveDialog1.Execute then

SaveFile (SaveDialog1.FileName);

end;

procedure TForm1.OpenFile (Filename: String);

begin

Memo1.Lines.LoadFromFile (Filename);

end;

procedure TForm1.SaveFile (Filename: String);

begin

Memo1.Lines.SaveToFile (Filename);

end;

end.

Test the program and make sure that it’ll load and save an ASCII file (any file with a .TXT extension will work, as will .PAS and .DPR).

Now what we want to do is have the program read other file formats, convert to straight text, and display the text. Since we don’t know exactly what formats might need to be converted, we need

the ability to add new formats as the need arises. Probably the easiest way to do this is with an initialization (.INI) file.

The idea is to save a description of the file format, a default extension, and the name of the DLL that contains the format conversion function. An example .INI file is shown in Listing 3.7.

Listing 3.7 TEXTEDIT.INI

; TEXTEDIT.INI

; Example of file conversion add-on interface [Text]

What we do is modify the OpenFile procedure so that it examines the extension of the file that you choose to open, and then calls the conversion function in the proper DLL. The DLL reads the file, converts the text, and returns the result in a string list. All of the conversion functions have a function called Convert, which the text editor program calls. Listing 3.8 contains the modified OpenFile function (be sure to add IniFiles to the form’s uses list), and Listings 3.9 and 3.10 contain the code for the text conversion DLL (TEXTCONV.DLL).

Listing 3.8 The new OpenFile function

procedure TForm1.OpenFile (Filename: String);

type

ConvertFunc = function (Filename: String;

Strings: TStrings): boolean; stdcall;

var

ConvertIni : TIniFile;

ConvertList : TStringList;

FileExt : String;

Extension : String;

DLLName : String;

x : Integer;

Found : Boolean;

LibInstance : HMODULE;

Converter : ConvertFunc;

IniFileName : String;

begin

FileExt := UpperCase (ExtractFileExt (Filename));

IniFileName := ExtractFileDir (ParamStr (0)) + '\TEXTEDIT.INI';

ConvertIni := TIniFile.Create (IniFileName);

ConvertList := TStringList.Create;

{ Read the list of available conversions } ConvertList.Add ('Hello, world');

ConvertIni.ReadSections (ConvertList);

{

For each conversion, read the Extension entry and compare it against the extension of the selected file.

}

x := 0;

Found := False;

while ((x < ConvertList.Count) and (Not Found)) do begin Extension := ConvertIni.ReadString (

ConvertList.Strings[x], 'Extension', '');

if (UpperCase (Extension) = FileExt) then Found := True

else

x := x + 1;

end;

if Found then begin

DLLName := ConvertIni.ReadString (

ConvertList.Strings[x], 'ConvertDLL', '');

{

Load the DLL, get the address of the Convert function, and call it.

}

LibInstance := LoadLibrary (PChar(DLLName));

if LibInstance = 0 then begin

Converter := GetProcAddress (LibInstance, 'Convert');

if Not Assigned (Converter) then begin

Application.MessageBox (

PChar('No conversion supplied for file type '+FileExt), 'TextEdit',

MB_ICONEXCLAMATION or MB_OK);

end;

ConvertList.Free;

ConvertIni.Free;

end;

Previous Table of Contents Next

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.

Brief Full Advanced Search

Search Tips

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:

Previous Table of Contents Next

Listing 3.9 TEXTCONV.DPR

library textconv;

{ Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select View-Project 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 DELPHIMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using DELPHIMM.DLL, pass string information using PChar or ShortString parameters. }

uses

ShareMem, SysUtils, Classes,

textc in 'textc.pas';

Exports

textc.Convert index 1 name 'Convert';

begin end.

Listing 3.10 TEXTC.PAS

unit textc;

interface uses Classes;

function Convert (Filename: String; Strings: TStrings) : boolean;

stdcall; export;

implementation

Go!

Keyword

---Go!

function Convert (Filename: String; Strings: TStrings) : boolean;

Pay particular attention to the note at the top of Listing 3.9 (TEXTCONV.DPR). The really cool thing about this note is that it’s automatically placed in your project file when you select File|New|DLL.

Truthfully, I’m not sure if I should be referencing the ShareMem unit or not in this case. I’ve tried the program without ShareMem, and it appears to work okay. And I can make the argument that I’m not passing a class to the Convert function—only a pointer to a TStrings object. I rather suspect, though, that the note applies to pointers to classes as well, so I’ve included ShareMem in the uses list for the program and the DLL. If you do have to use ShareMem, remember to ship the DELPHIMM.DLL file with your application.

Do note that the OpenFile function in Listing 3.8 is by no means good enough for a commercial program.

This is an example that illustrates the concept. A commercial implementation would require that your program actually go read the file to determine what type it is (if possible), and prompt the user for permission to perform the conversion before actually doing anything. This example shows you one way that you could implement an add-on interface to provide support for third-party additions to your products.

Sharing Memory Between Applications

Fortunately for us Delphi programmers, Delphi’s DLLs by default allow multiple instances, so there’s one less worry. However, just because multiple instances are allowed doesn’t mean that it’s easy to share information between processes that are using the same DLL. Under Windows 95 and Windows NT, each instance of a DLL has its own data segment. You can’t use a simple global variable in a DLL to share information between two running applications. For this, you need to set up a shared memory block in Windows. And to do that, you need to understand a little more about how Windows and Delphi load and map DLLs.

The DLLProc Variable

When Delphi loads a DLL, the DLL’s startup code (the code between the begin and end at the bottom of your DLL) is executed. If your DLL needs to load resources, allocate memory, or do any other processing when it’s first loaded and before any other functions are called, then that code should be placed here. This code is executed for every application that loads the DLL.

Windows will also notify your DLL when a process or thread attaches to it or detaches from it. But you have to request that notification from Delphi. The way you do that is by setting up a DLL handler function and setting the DllProc variable (defined in the System unit) to point to that function. Your DLL handler function should be defined like this:

procedure DLLHandler (Reason: Integer);

The Reason parameter will be one of four constants: DLL_PROCESS_ATTACH,

DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, or DLL_THREAD_DETACH.

To set up a shared memory block, you need to respond to DLL_PROCESS_ATTACH messages and call CreateFileMapping to create (or obtain a pointer to an already-created) shared memory block. Your DLL must also respond to DLL_PROCESS_DETACH messages and release the memory block so that Windows can release it when no more processes need it.

SHAREME.DPR (Listing 3.11), implements a shared memory block. In this example, the shared memory is just an integer that gets incremented every time a process attaches, and decremented when a process detaches.

Previous Table of Contents Next

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.

Brief Full Advanced Search

Search Tips

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:

Previous Table of Contents Next

Listing 3.11 Implementing shared memory in a DLL

library shareme;

uses

Windows, SysUtils, Classes;

const

pCounter: ^Longint = nil;

function GetProcessCount : Longint; stdcall; export;

begin

Result := pCounter^;

end;

procedure MyDLLHandler (Reason: Integer);

const

hMapObject : THandle = 0;

var

fInit : Boolean;

begin

case Reason of

DLL_PROCESS_ATTACH : begin

{ create a named file mapping object } hMapObject := CreateFileMapping (

$FFFFFFFF, { use paging file }

nil, { no security attributes } PAGE_READWRITE, { read/write access }

0, { high 32 bits of size } sizeof (longint), { low 32 bits of size }

Go!

Keyword

---Go!

'SharedMem' { name of object } );

{ the first process to attach initializes the memory } fInit := (GetLastError <> ERROR_ALREADY_EXISTS);

{ get a pointer to the file-mapped shared memory } pCounter := MapViewOfFile (

hMapObject, { object to map view of } FILE_MAP_WRITE, { read/write access }

0, { high 32-bits of offset } 0, { low 32-bits of offset } 0 { default: map entire file } );

{ initialize or increment the count } if (fInit) then

pCounter^ := 1 else

pCounter^ := pCounter^ + 1;

end;

DLL_PROCESS_DETACH : begin { decrement the count } pCounter^ := pCounter^ - 1;

{ unmap shared memory from the process's address space } UnmapViewOfFile (pCounter);

{ close the handle to the file mapping object } CloseHandle (hMapObject);

end;

(*

Thread attach and thread detach aren't handled DLL_THREAD_ATTACH :

DLL_THREAD_DETACH : *)

end;

end;

Exports

GetProcessCount index 1 name 'GetProcessCount';

begin

DLLProc := @MyDLLHandler;

MyDLLHandler (DLL_PROCESS_ATTACH);

end.

You should take special note of the two lines of code in the DLL’s initialization section. The first line of code initializes the System unit’s DLLProc variable to point to the DLL’s handler function. I thought that this was all that was required, but it appears that Delphi won’t call the handler with a DLL_PROCESS_ATTACH value. So, the library’s initialization code calls its own handler function. In my opinion, this is a bug in Delphi’s handling of DLL initialization.

To test the shared memory, create a form that calls the DLL’s GetProcessCount function when the form is created, and have it display the count in a label field on the form. If you run multiple copies of the application, you should see the counter increment once for each process that attaches to the DLL. If you close one or more of the applications and open new ones, the process counter should reflect the net effect (that is, if you opened three, closed one, and then opened another, the last one you open should have a process count of 3).

Global memory handles like those allocated by SHAREME consume valuable Windows resources, so be careful how you allocate them. If you’ll be sharing a lot of different fields from a single DLL, you should put them all together into a single memory block (i.e. a record), and allocate just one memory block for the entire structure. This will minimize the Windows resources that the program uses. And be sure that your DLL correctly frees the memory blocks. If your DLL crashes or otherwise exits without freeing the memory block, that memory and the Windows resource will remain allocated until you reboot Windows.

There is no way to free it once you’ve trashed the handle to the memory block.

Movin On!

If you’re interested in digging, there’s lots of stuff to learn about DLLs. In this chapter, I’ve given you enough information for you to go exploring. If you have the Microsoft

Developer’s Network CD-ROMs, you’ll want to look up DLLs in the index and read

everything you can find. You also should learn more about CreateFileMapping and related file mapping functions, paying special attention to the differences between Windows 95 and Windows NT. You can do a lot of cool things with DLLs, but you’ve got to be careful. Good luck!

Previous Table of Contents Next

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.

Brief Full Advanced Search

Search Tips

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:

Previous Table of Contents Next

CHAPTER 4