📄 ch19.htm
字号:
you need it, so you are making more efficient use of memory. Another advantage isthat your application will load more quickly when using dynamic loading because notall the code needed to run the program is loaded when the application initially loads.</P><P>The primary disadvantage to using the dynamic loading approach is that it is abit more work for you. First, you need to load the DLL with the Windows API functionLoadLibrary. Then, when you are done with the DLL, you unload it with FreeLibrary.Further (and this is where the work comes in), you need to use the GetProcAddressfunction to obtain a pointer to the function or procedure you want to call. To saythat this approach can be a tad confusing would be an understatement. The followingsection describes how to call procedures and functions in DLLs using both staticand dynamic loading.</P><P><H2><A NAME="Heading13"></A>Calling Functions and Procedures Located in DLLs</H2><P>The method you use to call a function or procedure in a DLL depends on whetherthe DLL was statically or dynamically loaded.</P><P><H3><A NAME="Heading14"></A>Calling Using Static Loading</H3><PRE>Calling functions and procedures from DLLs that are statically loaded is simple. First the calling application must contain a declaration for the function or procedure. After that, you call the function or procedure as you do a regular function or procedure. To import a function or procedure contained in a DLL, use the external modifier in the function or procedure declaration. For example, given the SayHello procedure shown earlier, the declaration in the calling application would look like this:</PRE><PRE>procedure SayHello(AForm : TForm); external `testdll.dll';</PRE><P>The external keyword tells the compiler that the procedure can be found in a DLL(TESTDLL.DLL in this case). The actual procedure call looks no different than anyother:</P><P><PRE>SayHello(Self);</PRE><P>After you have properly imported the function or procedure, you call it like anyother function or procedure. This step presumes, of course, that the procedure hasbeen exported from the DLL as described earlier.</P><BLOCKQUOTE> <P><HR><strong>CAUTION:</strong> When you declare functions and procedures contained in a DLL, be sure you get spelling <I>and</I> capitalization right. This is one place in Object Pascal programming where capitalization counts! If you misspell or improperly capitalize a function or procedure name, you will get an exception at runtime and the application will refuse to load. <HR></P> <P> <BR><HR><B>USING THE </B>external<B> KEYWORD</B> </P> <P>The external keyword has three flavors. Using external you can import a procedure or function in one of three ways:<BR> </P> <UL> <LI>By actual name<BR> <LI>By ordinal value<BR> <LI>By renaming </UL> <P></P> <P>The first way of importing, by actual name, is the method you have seen used up to this point. You simply declare the name of the function or procedure exactly as it appears in the DLL--for example, <PRE>procedure SayHello(AForm : TForm); external `testdll.dll';</PRE></BLOCKQUOTE><PRE></PRE><BLOCKQUOTE> <P>The second way of importing, by ordinal value, requires you to specify the ordinal value of the function or procedure as exported from the DLL: <PRE>procedure SomeOrdinalProcedure; external `testdll.dll' index 99;</PRE></BLOCKQUOTE><PRE></PRE><BLOCKQUOTE> <P>In this case, I am importing the procedure that is exported from the DLL as index 99. I can name the procedure anything I want in the calling application as long as the signature matches that of the procedure in the DLL. I can name the procedure anything I want because the procedure is exported by ordinal value and not by the procedure name.<BR> The third method, by renaming, enables me to import the procedure by it original name, but give the procedure a new name in the calling application. It looks like this: <PRE>procedure CoolProcedure; external `testdll.dll' name `DoSomethingReallyCool';</PRE></BLOCKQUOTE><PRE></PRE><BLOCKQUOTE> <P>Here I am importing a procedure called DoSomethingReallyCool and renaming it to CoolProcedure.<BR> </P> <P>Of these three methods, the first, importing by name, is the most commonly used. <HR></BLOCKQUOTE><P>The trick in writing and using DLLs, then, is in getting the imports and exportsright. Otherwise, there's nothing to it. Unless you need the flexibility that dynamicloading provides, you should almost always opt for static loading.</P><P><H3><A NAME="Heading15"></A>Calling Functions and Procedures Using Dynamic Loading</H3><P>Calling functions and procedures in DLLs that are dynamically loaded isn't a lotof fun. It requires that you declare a pointer to the function or procedure in theDLL, and pointers to functions can be confusing. To illustrate, let's say you havea procedure in a DLL called SayHello (the SayHello procedure gets a workout in thischapter). It would look like this in the DLL's source code:</P><P><PRE>procedure SayHello(AForm : TForm);begin MessageBox(AForm.Handle, `Hello From a DLL!', `DLL Message Box', MB_OK or MB_ICONEXCLAMATION);end;</PRE><P>To call this procedure from your program, you first have to declare a type thatdescribes the procedure:</P><P><PRE>type TSayHello = procedure(AForm : TForm);</PRE><P>Now that you've done that, you must load the DLL, use GetProcAddress to get apointer to the procedure, call the procedure, and, finally, unload the DLL. Here'show the whole operation looks:</P><P><PRE>var DLLInstance : THandle; SayHello : TSayHello;begin { Load the DLL. } DLLInstance := LoadLibrary(`testdll.dll'); { Get the address of the procedure. } @SayHello := GetProcAddress(DLLInstance, `SayHello'); { Call the procedure. } SayHello(Self); { Unload the DLL. } FreeLibrary(DLLInstance);end;</PRE><P>As I said, loading a DLL dynamically is a bit more work. Still, when you needto load a DLL at runtime, that's the way you have to do it. Note that this code ispared down a bit for clarity. You will almost always add some error-checking codeto ensure that the DLL loads correctly and that GetProcAddress returns a good address.Here's how the code looks with error checking code in place:</P><P><PRE>procedure TForm1.DynamicLoadBtnClick(Sender: TObject);type TSayHello = procedure(AForm : TForm);var DLLInstance : THandle; SayHello : TSayHello;begin DLLInstance := LoadLibrary(`testdll.dll'); if DLLInstance = 0 then begin MessageDlg(`Unable to load DLL.', mtError, [mbOK], 0); Exit; end; @SayHello := GetProcAddress(DLLInstance, `SayHello'); if @SayHello <> nil then SayHello(Self) else MessageDlg(`Unable to locate procedure.', mtError, [mbOK], 0); FreeLibrary(DLLInstance);end;</PRE><P>As you can see, you probably won't use dynamic loading of DLLs unless you absolutelyhave to.</P><P><H2><A NAME="Heading16"></A>Creating a DLL Project with the Object Repository</H2><P>Creating a DLL in Delphi is accomplished through the Object Repository. (The ObjectRepository is covered on Day 8, "Creating Applications in Delphi."). Tocreate a DLL project, follow these steps:</P><DL> <DT></DT> <DD><B>1. </B>Choose File|New to display the Object Repository. <P> <DT></DT> <DD><B>2. </B>Double-click the DLL icon. <P></DL><P>Were you expecting it to be more complicated than that? Delphi creates the DLLproject and displays the Code Editor. The file in the edit window looks like this:</P><P><PRE>library Project2;{ 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 SysUtils, Classes;beginend.</PRE><P>Now you can begin adding code to the DLL. Be sure to add any exported procedureor function names to the exports section. Write any standalone functions or proceduresthat you need in your DLL. When you are finished adding code, you can choose Compileor Build from the Project menu to build the DLL.</P><BLOCKQUOTE> <P><HR><B>COMMENTING THE DLL UNIT</B> </P> <P>I want to take a moment to explain the large comment block at the top of a DLL unit. This message is telling you that if your DLL has exported functions and procedures that take a long string type as a parameter or if your exported functions return a long string, you must do the following: <BR> </P> <UL> <LI>Put ShareMem at the beginning of the uses list in both the DLL source and the calling application's main unit. Be sure that ShareMem comes before other units in the uses list.<BR> <LI>Ship the Borlndmm.dll file with your DLL. Notice that I said Borlndmm.dll and not Delphimm.dll, as the comments in the DLL code indicate. Borland changed the name of the memory manager DLL but neglected to change the comments generated when you create a new DLL unit. The comments are not completely misleading, however, because both Delphimm.dll and Borlndmm.dll are included with Delphi 4. </UL> <P></P> <P>To avoid this requirement, just be sure that your DLL functions and procedures don't take any long string parameters and that your DLL functions don't return a long string. Instead of using a long string, you can use a PChar or a short string. For example, rather than use <PRE>procedure MyProcedure(var S : string);begin { Procedure code here. }end;</PRE></BLOCKQUOTE><PRE></PRE><BLOCKQUOTE> <P>use this: <PRE>procedure MyFunction(S : PChar);begin</PRE> <PRE>{ Procedure code here. }<B></B></PRE> <P> <PRE>end;</PRE></BLOCKQUOTE><PRE></PRE><BLOCKQUOTE> <P><BR> This situation is easy enough to work around, so you should never have the need for Borlndmm.dll. You just need to be aware of the restrictions placed on using the long string in DLL functions and procedures. Note that you can use long strings in functions and procedures used within the DLL itself without the need for Borlndmm.dll. This applies only to exported functions and procedures. <HR></P> <P><HR><strong>NOTE:</strong> I always remove the comments about Borlndmm.dll when I create a new DLL. You can certainly leave them in the DLLs source code if you want. After you understand what the comments are saying, you won't need them anymore, so you might as well remove them. <HR></BLOCKQUOTE><P>The next three listings contain code that illustrates the concepts discussed thusfar. Listing 19.3 contains a DLL that will be called statically from a calling application.Listing 19.4 contains a DLL that is called dynamically from the calling applicationand implements a DLLProc. Finally, Listing 19.5 contains a program that calls thetwo DLLs. The program has a form with four buttons that call various procedures inthe two DLLs. The book's code (available at http://www.mcp.com/info) contains thesample programs for the projects in these three listings.</P><P><H4>LISTING 19.3. TestDll.dpr.</H4><PRE>library TestDLL;uses SysUtils, Classes, Forms, Windows;procedure SayHello(AForm : TForm);begin MessageBox(AForm.Handle, `Hello From a DLL!', `DLL Message Box', MB_OK or MB_ICONEXCLAMATION);end;procedure DoSomething;begin MessageBox(0, `This procedure was exported by ordinal.', `DLL Message Box', MB_OK or MB_ICONEXCLAMATION);end;procedure DoSomethingReallyCool;begin MessageBox(0, `Something really cool.', `DLL Message Box', MB_OK or MB_ICONEXCLAMATION);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -