Beside this introductory example, you can do a few extra things with dynamic libraries in Delphi. You can use some new compiler directives to affect the name of the library, you can call a DLL at run time, and you can place entire Delphi forms inside a dynamic library. These are the topics of the following sections.
For a library, as for a standard application, you end up with a library name matching a Delphi project filename. Following a technique similar to that introduced in Kylix for compatibility with standard Linux naming conventions for shared object libraries (the Linux equivalent of Windows DLLs), Delphi 6 introduced special compiler directives you can use in libraries to determine their executable filename. Some of these directives make more sense in the Linux world than on Windows, but they've all been added anyway:
These directives can be set in the IDE from the Application page of the Project Options dialog box, as you can see in Figure 10.3. As an example, consider the following directives, which generate a library called MarcoNameTest60.dll:
Up to now, you've referenced in your code the functions exported by the libraries, so the DLLs were loaded along with the program. I mentioned earlier that you can also delay the loading of a DLL until the moment it is needed, so you can use the rest of the program in case the DLL is not available.
Dynamic loading of a DLL in Windows is accomplished by calling the LoadLibrary API function, which searches for the DLL in the program folder, in the folders on the path, and in some system folders. If the DLL is not found, Windows will show an error message, something you can skip by calling Delphi's SafeLoadLibrary function. This function has the same effect as the API it encapsulates, but it suppresses the standard Windows error message and should be the preferred way to load libraries dynamically in Delphi.
If the library is found and loaded (something you know by checking the return value of LoadLibrary or SafeLoadLibrary), a program can call the GetProcAddress API function, which searches the DLL's exports table, looking for the name of the function passed as a parameter. If GetProcAddress finds a match, it returns a pointer to the requested procedure. Now you can cast this function pointer to the proper data type and call it.
Whichever loading functions you've used, don't forget to call FreeLibrary at the end, so that the DLL can be properly released from memory. In fact, the system uses a reference-counting technique for libraries, releasing them when each loading request has been followed by a freeing request.
The example I've built to show dynamic DLL loading is named DynaCall. It uses the FirstDLL library built earlier in this chapter (to make the program work, you have to copy the DLL from its source folder into the folder as the DynaCall example). Instead of declaring the Double and Triple functions and using them directly, this example obtains the same effect with somewhat more complex code. The advantage, however, is that the program will run even without the DLL. Also, if new compatible functions are added to the DLL, you won't have to revise the program's source code and recompile it to access those new functions. Here is the core code of the program:
type TIntFunction = function (I: Integer): Integer; stdcall; const DllName = 'Firstdll.dll'; procedure TForm1.Button1Click(Sender: TObject); var HInst: THandle; FPointer: TFarProc; MyFunct: TIntFunction; begin HInst := SafeLoadLibrary (DllName); if HInst > 0 then try FPointer := GetProcAddress (HInst, PChar (Edit1.Text)); if FPointer <> nil then begin MyFunct := TIntFunction (FPointer); SpinEdit1.Value := MyFunct (SpinEdit1.Value); end else ShowMessage (Edit1.Text + ' DLL function not found'); finally FreeLibrary (HInst); end else ShowMessage (DllName + ' library not found'); end;
How do you call a procedure in Delphi, once you have a pointer to it? One solution is to convert the pointer to a procedural type and then call the procedure using the procedural-type variable, as in the previous listing. Notice that the procedural type you define must be compatible with the definition of the procedure in the DLL. This is the Achilles' heel of this method—there is no actual check of the parameter types.
What is the advantage of this approach? In theory, you can use it to access any function of any DLL at any time. In practice, it is useful when you have different DLLs with compatible functions or a single DLL with several compatible functions, as in this case. You can call the Double and Triple methods by entering their names in the edit box. Now, if someone gives you a DLL with a new function receiving an integer as a parameter and returning an integer, you can call it by entering its name in the edit box. You don't even need to recompile the application.
With this code, the compiler and the linker ignore the existence of the DLL. When the program is loaded, the DLL is not loaded immediately. You might make the program even more flexible and let the user enter the name of the DLL to use. In some cases, this is a great advantage. A program may switch DLLs at run time, something the direct approach does not allow. Note that this approach to loading DLL functions is common in macro languages and is used by many visual programming environments.
Only a system based on a compiler and a linker, such as Delphi, can use the direct approach, which is generally more reliable and also a little faster. In my opinion, the indirect loading approach of the DynaCall example is useful only in special cases, but it can be extremely powerful. On the other hand, I see a lot of value in using dynamic loading for packages including forms, as you'll see toward the end of this chapter.
Besides writing a library with functions and procedures, you can place a complete form built with Delphi into a dynamic library. This can be a dialog box or any other kind of form, and it can be used not only by other Delphi programs, but also by other development environments or macro languages with the ability to use dynamic link libraries. Once you've created a new library project, all you need to do is add one or more forms to the project and then write exported functions that will create and use those forms.
function GetColor (Col: LongInt): LongInt; cdecl; var FormScroll: TFormScroll; begin // default value Result := Col; try FormScroll := TFormScroll.Create (Application); try // initialize the data FormScroll.SelectedColor := Col; // show the form if FormScroll.ShowModal = mrOK then Result := FormScroll.SelectedColor; finally FormScroll.Free; end; except on E: Exception do MessageDlg ('Error in library: ' + E.Message, mtError, [mbOK], 0); end; end;
What makes this different from the code you generally write in a program is the use of exception handling:
By checking the return value of the ShowModal method, the program determines the result of the function. I've set the default value before entering the try block to ensure that it will always be executed (and also to avoid the compiler warning indicating that the result of the function might be undefined).
You can find this code snippet in the FormDLL and UseCol projects, available in the FormDLL folder. (There's also a WORDCALL.TXT file showing how to call the routine from a Word macro.). The example also shows that you can add a modeless form to the DLL, but doing so causes far too much trouble. The modeless form and the main form are not synchronized, because the DLL has its own global Application object in its own copy of the VCL. This situation can be partially fixed by copying the Handle of the application's Application object to the Handle of the library's Application object. Not all of the problems are solved with the code that you can find in the example. A better solution might be to compile the program and the library to use Delphi packages, so that the VCL code and data won't be duplicated. But this approach still causes a few troubles: it's generally advised that you don't use Delphi DLLs and packages together. So what is the best suggestion I can give you? For making the forms of a library available to other Delphi programs, use packages instead of plain DLLs!
|Copyright © 2004-2022 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide||