Inside Delphi 2006 (Wordware Delphi Developers Library)

Now we're going to create a DLL that uses PChars to pass string data to the calling application. This application is displayed in Figure 21-6.

Figure 21-6: The MessageDlg text is from the DLL.

The first thing you have to do is create the host application:

  1. Create a new VCL Forms application project.

  2. Add two TRadioGroup components to the Designer Surface.

  3. Add English, German, and Croatian items to both TRadioGroup components.

Once you've created the host application's user interface, you have to create a unit for the languages enumeration that will be used by the host application and the DLL and then add a new DLL project to the project group.

Here's the MyTypes unit that contains the languages enumeration:

unit MyTypes; interface type TMyLanguage = (mlEnglish, mlGerman, mlCroatian); implementation end.

There's only one decision that you have to make when you want to pass a PChar between the application and the DLL — how to manage the PChar's memory. Here's a list of things that you can and cannot do:

The following listing shows how to allocate and deallocate the PChar's memory in the DLL. The listing contains two routines: the GetStringData function that allocates memory for and returns a localized string, and the FreeString- Data procedure that must be used to release the memory allocated by the GetStringData function.

Listing 21-8: Routines that manage the memory in the DLL

library LanguageLib; uses SysUtils, MyTypes; const STRING_DATA: array[TMyLanguage] of string = ('Good morning.', 'Guten Morgen.', 'Dobro jutro.'); { return a localized string } function GetStringData(Language: TMyLanguage): PChar; begin { allocate the memory for the string and for the #0 character } GetMem(Result, Length(STRING_DATA[Language]) + 1); { the StrPCopy function copies a string to a null-terminated string } StrPCopy(Result, STRING_DATA[Language]); end; { use this routine to release the PChars allocated with GetStringData } procedure FreeStringData(Data: PChar); begin FreeMem(Data); end; exports GetStringData, FreeStringData; begin end.

Listing 21-9 shows how to use the GetStringData and the FreeStringData routines in the host application.

Listing 21-9: Using the GetStringData and the FreeStringData routines to retrieve a localized string from the DLL

procedure TMainForm.RadioGroup1Click(Sender: TObject); var DLLHandle: THandle; stringFunc: function(Language: TMyLanguage): PChar; freeProc: procedure (Data: PChar); buffer: PChar; selectedLang: TMyLanguage; begin selectedLang := TMyLanguage(RadioGroup1.ItemIndex); DLLHandle := LoadLibrary('LanguageLib.dll'); try { find both routines } stringFunc := GetProcAddress(DLLHandle, 'GetStringData'); freeProc := GetProcAddress(DLLHandle, 'FreeStringData'); if not Assigned(stringFunc) or not Assigned(freeProc) then MessageDlg('Error Loading DLL!', mtError, [mbOK], 0) else begin buffer := stringFunc(selectedLang); try MessageDlg(string(buffer), mtInformation, [mbOK], 0); finally { free the PChar's memory using the DLL's FreeStringData routine } freeProc(buffer); end; // try..finally end; // if not Assigned finally FreeLibrary(DLLHandle); end; // try..finally end;

If you really need to pass string data between the application and the DLL, you can do things the API way — allocate and deallocate PChars in the host application and design DLL functions to accept the PChar buffer and the maximum number of characters to copy. When doing things the API way, you also have to enable the developer to call the function with invalid parameters (like nil pointers) to determine how big the buffer should be.

The following listing shows the GetStringDataEx function, which expects the developer to pass both a pointer to a valid buffer and the length of the buffer. The GetStringDataEx function also allows the developer to pass nil to determine the appropriate length for the buffer.

Listing 21-10: Doing things the API way

library LanguageLib; uses SysUtils, MyTypes; const STRING_DATA: array[TMyLanguage] of string = ('Good morning.', 'Guten Morgen.', 'Dobro jutro.'); { the developer can pass nil to determine how big his/her buffer has to be } { if the developer passed a valid Buffer, StrPLCopy copies BufferLen bytes from a Delphi string to the Buffer } function GetStringDataEx(Language: TMyLanguage; Buffer: PChar; BufferLen: Integer): Integer; begin Result := Length(STRING_DATA[Language]); if Buffer <> nil then StrPLCopy(Buffer, STRING_DATA[Language], BufferLen); end; exports GetStringData, FreeStringData, GetStringDataEx; begin end.

Listing 21-11 shows how to use the GetStringDataEx function in the host application.

Listing 21-11: Calling the GetStringDataEx function

procedure TMainForm.RadioGroup2Click(Sender: TObject); var DLLHandle: THandle; selectedLang: TMyLanguage; buffLen: Integer; buffer: PChar; stringProc: function(Language: TMyLanguage; Buffer: PChar; BufferLen: Integer): Integer; begin selectedLang := TMyLanguage(RadioGroup2.ItemIndex); DLLHandle := LoadLibrary('LanguageLib.dll'); try StringProc := GetProcAddress(DLLHandle, 'GetStringDataEx'); if Assigned(StringProc) then begin { pass nil to the GetStringDataEx function to determine the appropriate size for the buffer } buffLen := StringProc(selectedLang, nil, 0); { allocate the buffer } GetMem(Buffer, buffLen + 1); try { call the GetStringDataEx function normally } StringProc(selectedLang, Buffer, buffLen); MessageDlg(string(Buffer), mtInformation, [mbOK], 0); finally { release the buffer } FreeMem(Buffer); end; // try..finally (Buffer) end else MessageDlg('Cannot load the DLL!', mtWarning, [mbOK], 0); finally FreeLibrary(DLLHandle); end; // try..finally (DLLHandle) end;

Категории