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

(Ebook - Pdf) Kick Ass Delphi Programming

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

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!

-----------

Creating Forms in DLLs

Probably the most common use of DLLs in Delphi programming is to store common forms. If you’re building a suite of programs, you’ve probably got dozens of forms that are common across all of the programs. Rather than building all of those forms into each program, you can put them all into a single DLL, which will save you disk space and memory, and (perhaps most importantly) maintenance problems. A DLL that contains Delphi forms does incur the overhead of the runtime library code (about 100 K), but if you put many forms into a single DLL, the overhead isn’t a problem.

Accessing a form from a DLL is slightly different from accessing the form in a program. Since you’re not linking the unit that contains the form, you can’t just show it as you would from within a normal program (that is, by calling Form1.ShowModal). Instead, you have to create a wrapper function in the DLL, and then call the wrapper function from the program. The wrapper function creates the form, displays it, gathers any data, destroys the form when it’s closed, and returns any required information to the calling program.

Listings 3.4 and 3.5 contain PICKCLR.DPR and COLORFRM.PAS, which implement a color selection form in a DLL.

Listing 3. PICKCLR.DPR

library pickclr;

uses SysUtils, Classes,

ColorFrm in 'colorfrm.pas' {ColorSelectionForm};

Exports

ColorFrm.PickColors index 1 name 'PickColors';

begin end.

Listing 3.5 COLORFRM.PAS

unit colorfrm;

interface

uses

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

type

TColorSelectionForm = class(TForm) ColorGrid1: TColorGrid;

BtnOk: TButton; BtnCancel: TButton;

private

{Private declarations } public

{Public declarations } function Execute : boolean;

end;

function PickColors (var Foreground, Background : TColor) : boolean; stdcall; export;

implementation

{$R *.DFM}

function TColorSelectionForm.Execute : boolean; begin

Result := (ShowModal = mrOk); end;

function PickColors (var Foreground, Background : TColor) : boolean; stdcall;

var

ColorForm : TColorSelectionForm; begin

ColorForm := TColorSelectionForm.Create (Application); Result := ColorForm.Execute;

if (Result = True) then begin

Foreground := ColorForm.ColorGrid1.ForegroundColor; Background := ColorForm.ColorGrid1.BackgroundColor;

end;

ColorForm.Free;

end;

end.

You should note that COLORFRM.PAS can be linked with a program or with a DLL without changes. This makes moving forms from programs into DLLs fairly easy. For ease of debugging, you develop the form and test it with a program. Once you’ve got it working, you add it to a DLL shell that you’ve set up.

As you can see from Listing 3.4, the project file for a DLL is very simple. The most important part is getting the Exports statement right. If you want more forms in the DLL, simply add their unit names to the uses statement, and add definitions of their wrapper functions to the Exports statement.

A DLL that contains forms should have an interface unit similar to the BEEPDLL.PAS unit shown in Listing 3.3. Like BEEPDLL, the unit can provide compile-time or runtime DLL linkage. For brevity, I haven’t included an interface unit for the PICKCLR DLL.

To use a form that’s stored in a DLL, you simply link with the DLL’s interface unit and call the form’s wrapper function, which will display the form and return any required values.

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.

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!

-----------

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;

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]

Extension=.TXT

ConvertDLL=textconv.dll

[Word for Windows] Extension=.DOC ConvertDLL=wfwconv.dll

[WordCruncher]

Extension=.WCX

ConvertDLL=wcxconv.dll

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

Application.MessageBox (

PChar ('Can''t load DLL '+DLLName), 'TextEdit',

MB_ICONEXCLAMATION or MB_OK);

end

else begin

Converter := GetProcAddress (LibInstance, 'Convert'); if Not Assigned (Converter) then begin

Application.MessageBox (

PChar ('Can''t find Convert function in '+DLLName), 'TextEdit',

MB_ICONEXCLAMATION or MB_OK);

end

else begin

if not Converter (Filename, Memo1.Lines) then begin Application.MessageBox (

'Error loading file', 'TextEdit', MB_ICONEXCLAMATION or MB_OK);

end;

end;

FreeLibrary (LibInstance); end;

end

else begin Application.MessageBox (

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

MB_ICONEXCLAMATION or MB_OK);

end;

ConvertList.Free;

ConvertIni.Free;

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.

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!

-----------

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

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

begin

Strings.LoadFromFile (Filename); Result := True;

end;

end.

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.