Global TMW:
Login  |  Register          Free Newsletter Subscription
Subscribe
Email
Print
Reprint
Learn RSS

Forwarding DLLs Add Functions to Existing Software

Without digging too deeply into code, you can extend the operation of existing DLLs.

John Dumais, Agilent Technologies, Santa Rosa, CA -- Test & Measurement World, 10/1/2000

Programmers often use dynamic-link libraries to perform functions common to many applications. A simple dynamic-link library (DLL) might control I/O ports, and a complex DLL could provide a complete set of control functions for a laser printer. If you need to modify the behavior of an existing DLL but you lack its source code, you can use a “forwarding” DLL to change or add new functions to the DLL.

Assume for a moment that you’ve installed an improved I/O board in your PC to run with an old, but still useful, application program. An old DLL controls only the old I/O board, so the old DLL cannot operate the new board. To make the new I/O board operate with the old application requires a forwarding DLL. The forwarding DLL implements functions with the same “signature” as those contained in the original DLL. Thus, a call from the application to the DLL function set_PortA remains the same. But instead of calling the set_PortA function in the original DLL, the application calls the set_PortA function in the forwarding DLL you write. (See “Software Resources,” p. 54.) The new DLL might provide all new I/O-port operations for the new I/O board. Or, it could perform some operations entirely on its own and forward others to the old DLL, perhaps taking advantage of already written and debugged data-analysis functions. In the process of forwarding the calls to the old DLL, the forwarding DLL sets up a new port address, byte mask, buffer pointer, or other parameter to make the old DLL’s operations compatible with the new hardware.

A forwarding DLL comes in handy if you have to fix a bug in an old DLL but you lack the DLL’s source code. You just implement the bug-free function in a new DLL. Even if you have source code, you may find it easier to use a forwarding DLL than to rewrite old DLL code.

You also could use a forwarding DLL to improve performance. Suppose your system uses a DLL that gets data from a spectrum analyzer and then looks for peaks in the data. You’ve come up with a faster algorithm to find peaks. So, you write a new DLL that forwards data-acquisition calls to the old DLL but that captures calls to the peak-detection function and runs them faster in the new DLL. You need not change the many function calls from the application to the old DLL. The only modification involves changing the code in the application to load the new DLL in place of the old one (Fig. 1).

My interest centered on using DLLs to extend the behavior of Agilent Technologies’ Visual Engineering Environment, also called Agilent VEE, so I have included some information specific to this software. Nevertheless, you can use all of the examples in this article without Agilent VEE. Get the complete program listings from this article.

TMW00_10F4ig1.gif (26532 bytes)
Figure 1. A DLL provides specific functions called from an application program. A forwarding DLL intercepts calls to the old DLL and substitutes new code for func1 and simply passes the func2 call to the old DLL.
Make Calls to Addresses
Before you learn how to implement a forwarding DLL, you first need to know how to dynamically call functions not defined in the application. One key to using a forwarding DLL is getting the computer to go to a specific address to execute an instruction. By doing so, you can point the computer to a specific function in a DLL and have the computer execute the function. But first you must define a specific type of variable to hold the function pointer.

In most cases, you’ll need a new data type—a function pointer—that represents the address of a function:

typedef void (*p_vv_fn)(void);

The typedef keyword signals the C compiler to define a new data type in your program, and the (*p_vv_fn) notation tells the compiler to specifically define a function-pointer data type called p_vv_fn. The asterisk identifies a variable used as a pointer. The p_vv_fn name lets you easily declare other variables as the same type of function pointer.

The remainder of the typedef statement specifies the data type returned by the function (void) and the data types of the arguments, also (void), sent to the function. Taken together, these characteristics form a function’s signature. The signature is important because it determines how the compiler will set up the stack prior to calling the function and how it cleans up the stack when the function returns control to the calling application.

Although you can use any character string for the variable, I use p_ to indicate a pointer. The following two characters denote the data type returned by the function and sent to the function in the argument data-type list. Thus, in my naming scheme, vv_ indicates void data types for both. The fn in the variable indicates I’m using a pointer to a function.

Listing 1
/* The test function */
void func1(void){
printf("Executing func1 ");
return;
}

/* Assign address of func1 to the pointer*/
p_vv_fn func_ptr = func1;

/* Transfer execution to the function address */
func_ptr();
Listing 2
/* function pointer returns a long and taking a void argument */
typedef long (*p_lv_fn)(void);

/* this function returns a long and takes a long as an argument */
long foo(long a){
return a + 1;
}

int main(int argc, char *argv[]){
p_lv_fn p_func = (p_lv_fn)foo;
long result = 0;

/* call a function that expects a long argument, but send no argument */

result = p_func();
return 0;
}

You can use a function pointer as shown in the short code segment provided in Listing 1. You’ll find the complete code that contains this segment in the fp.c file on the T&MW Web site.

Match Code Signatures
When you set up a pointer to a function, you must ensure the signature of the function pointer matches that of the function you’re calling. Thus, if the function expects two long arguments, the calling function should provide them and not some other argument type.

The function-pointer data-type definition serves to define a template. Once you have the template—p_vv_fn in the example above—you can use it to define many function pointers, all of which will have a common signature. Two additional definitions of function-pointer data types illustrate how you would set up pointer types of other function signatures:

/* function pointer returns a long and takes a void argument list*/
typedef long (*p_lv_fn)(void);
/* function pointer returns a long and takes a long argument list*/
typedef long (*p_ll_fn)(long);

So, whenever you declare a variable of type p_ll_fn, for example, the variable points to a function that returns a long value and that takes an argument list that includes a long value.

When the signature of a function pointer doesn’t match the signature of a function, you may get undefined results. The code segment in Listing 2 illustrates such mismatched signatures. The program uses the pointer p_func to call the function foo. But because the code defines the pointer as returning a long and taking a void, the pointer passes no arguments to the foo function. The foo function takes and returns a long argument. As a result, the addition operation in foo adds 1 to who knows what—you cannot predict the results. Incidentally, this code segment represents “legal” code that compiles, links, and runs, with no errors or warnings from the compiler or the linker. Be sure to examine your application carefully to determine how it calls the functions in the DLL.

A function’s signature lets the compiler properly construct a stack in memory to hold arguments that pass back and forth between a main program and a function. The operating system provides a stack pointer—another address—that locates the next open location on the stack. If the main program pushes one value onto the stack for a function, the function should pop one value off the stack. But what if the function expects two values? It doesn’t know what’s on the stack, so it simply pops the latest two values off the stack: the one pushed on by the main program, and another left from some previous operation. (Popping data “off” a stack doesn’t clear a location, it simply moves the stack pointer.) When signatures don’t match, the stack pointer gets out of step and argument transfers either leave information on the stack or extract too much information from the stack.

Even when the signatures match, the two pieces of code also must agree on which piece maintains the stack. You don’t want to call a function in an old DLL and have a conflict in how the stack gets maintained. The proper maintenance of the stack relies on calling conventions. Win32 programs rely on the three common calling conventions listed in Table 1. (Other calling conventions exist, but they aren’t as common.)

Table 1: Win32 Calling Conventions
Calling Convention Parameter Passing Direction Stack Cleanup Comments
__cdecl Pushes parameters on the stack right to left. Caller cleans up the stack The default for C and C++ programs. Because the caller cleans up the stack, this convention allows for the use of varargs.
__stdcall Pushes parameters on the stack right to left/ Called function cleans up the stack Win32 API functions are called using this convention. The generally accepted expectation is that functions implemented in DLLs will use this convention.
thiscall Pushes parameters on the stack right to left. Called function cleans up the stack The calling convention used by C++ member functions that do not use varargs. The varargs member functions are declared with __cdecl and push the pointer on the stack last.

For proper operation, the calling convention in the application and the DLLs must match. How can you tell which calling convention a DLL uses if you no longer have the source code? You can look at the DLL’s header file or inspect the DLL itself. Or, you can look in the code for the application that uses the DLL. In any case, you must ensure you don’t change the agreed-upon calling convention.

Examine the Header File
Applications that can dynamically open a DLL and call functions in it may need a header file to specify the interface to the DLL. By inspecting the contents of the header file, you can usually determine the calling function used with a DLL. (For programs written using Agilent VEE, the Import Library object calls a Definition File, basically a C-language header file.) A line of code such as this

long __cdecl initialize(void);

in the header file that defines a function prototype indicates the DLL’s initialize function was compiled to use C calling conventions. (This type of declaration takes two underscores in front of the declaration type.)

But suppose your development software doesn’t require a header file. A little detective work may help you discern the calling convention for a function. You can use a utility called dumpbin that comes with Microsoft Visual C++, or you can use equivalent programs (see “Software Resources,” p. 54). Each program extracts information from a DLL and tells you about the DLL’s internal operations.

Dump of file new.dll
File Type: DLL
Section contains the following exports for new.dll
0 characteristics

37819CC4 time date stamp Mon Jul 05 23:05:56 1999
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name
1 0 00001019 initialize
   .
    .
    .
Figure 2. Using a program such as dumpbin lets you examine information in a DLL’s header. In this case, the dumpbin program identified initialize as an exported function in new.dll.

Assume the file new.dll contains an initialize function declared as shown above. Running the dumpbin software with the -exports switch set would produce the display for the new.dll file, as shown in Figure 2. The information shows a single exported function: initialize. DLLs may contain any number of data and function identifiers, often called symbols. Only exported symbols are accessible from outside the DLL.

Suppose the DLL had been compiled with the following declaration:

long __stdcall initialize(void);

Now, running the dumpbin program would produce the following information for the initialize function:

.
.
.

ordinal hint RVA name
1 0 00001014 _initialize@0
.
.
.

The exported function’s symbol now includes extra characters—a leading underscore (_) and a trailing @0—often called decorations. The zero indicates the number of bytes required to hold the function arguments. If your DLL contains functions with decorations such as those above, the DLL was compiled using the __stdcall calling convention.

In one situation, the exported symbol may not give you enough detail to determine the function’s calling convention. If the DLL was linked using a linker definition file (usually a file with the name of the DLL and a .def extension), the linker might have been directed to export an undecorated symbol even though the function had been declared using __stdcall. Assume you have an initialize function declared as

long __stdcall initialize(void);

The programmer must include a definition file that contains the following information:

EXPORTS
DllMain
initialize
cleanup
func1
func2
 

This EXPORTS information tells the linker to place an external reference to a function in the DLL’s symbol table. The exported definition will look exactly like the name given in the linker definition (.def) file, with no additional decorations.

   .
    .
    .

0.00 version
1 ordinal base
5 number of functions
5 number of names
ordinal hint RVA name
1 0 00001005 DllMain
2 1 00001019 cleanup
3 2 0000100A func1
4 3 0000100F func2
5 4 00001014 initialize
   .
    .
    .
Figure 3. A look at the header information in a DLL may help you resolve signature and calling-convention problems by showing exactly which conventions a programmer used to compile a DLL. The dumpbin information also shows relative addresses for the DLL’s functions.

After running dumpbin on the compiled DLL, the display provides the information shown in Figure 3. Note that the symbols for the __stdcall DLL functions are exported without decorations. The EXPORTS section of the linker definition file has final say in determining how a function symbol is exported to the other software. The initialize function was still compiled as a __stdcall-type function, though.

The preceding examples rely on function pointers that call functions that reside in the same executable module. But suppose the DLL you need isn’t in the same executable module. Then you have to do more programming. Because a linker can’t know in advance where the functions will actually exist, the application must dynamically load a DLL, or explicitly call a DLL, into its address space and then locate the addresses of the functions it needs.

Where’s That DLL?
How can a program call functions in a DLL it hasn’t seen? It can use the DLL’s export table, which lists the functions and data available for other programs to use. The export table also furnishes the address for each function. The information (Fig. 3) for new.dll shows that a function to be exported—func1—exists at address 0x100A. All addresses are relative to the start of the library. Thus, when a program dynamically loads new.dll, it obtains the address for func1 from the export table and sums it with the DLL’s starting address.

Exporting a function from a DLL can take place in one of two ways. First, in the DLL source code the programmer can specifically declare a symbol as one to export. The second technique, which I prefer, minimizes the amount of DLL source code not specifically related to the DLL’s functions. I use an EXPORTS statement in a linker definition file. When I link the object code that results from compiling the DLL source using a directive that includes a linker definition file, the linker will export the symbols named in the exports entry.

Listing 3
#include <WINDOWS.H>
#include <STDIO.H>

typedef long (*p_lv_fn)(void);

int main(int argc, char *argv[]){
HANDLE dll_handle = 0;
p_lv_fn func1_ptr = 0;

printf("Loading old.dll ");

dll_handle = LoadLibrary("old.dll");
if(!dll_handle){
fprintf(stderr, "Failure loading old.dll ");
return -1;
}
func1_ptr = (p_lv_fn)GetProcAddress(dll_handle, "func1");
if(!func1_ptr){
FreeLibrary(dll_handle);
dll_handle = 0;
    fprintf(stderr, "Failure trying to find func1 in old.dll ");
return -1;
}
printf("Results of func1: %ld ", func1_ptr());
func1_ptr = 0;
FreeLibrary(dll_handle);
dll_handle = 0;
return 0;
}

A simple application (Listing 3) shows how a program uses a function in a DLL; in this case, old.dll. The program defines a function-pointer data type and then defines a HANDLE variable, dll_handle, as zero and a function pointer, func1_ptr, as zero. The compiler’s header files define the HANDLE data type.

Setting dll_handle to zero indicates that at this point in the program the handle is not yet a valid address. Likewise, setting func1_ptr to zero indicates it is not yet a valid pointer.

The LoadLibrary function (a Win32 API call) dynamically loads the old.dll file and assigns its address to dll_handle. The if statement prints an error message if the old.dll file does not load properly.

After loading the old.dll file, the program uses the GetProcAddress API call to get the address of the func1 function and assign it the address to the function pointer, func1_ptr. The if statement checks for a nonzero return value indicating that the func1 function exists in the DLL identified by the handle variable and that the func1 function has been exported for use outside the DLL.

Now that the program contains a pointer to function func1, it can use that function as needed. In the sample program, the func1 function gets called by func1_ptr in the printf statement.

When the application no longer needs any of the DLL’s functions, it sets the pointer to zero. Then it “releases” the DLL—thus freeing memory—by calling FreeLibrary with the DLL’s handle. After releasing the DLL, the program sets the handle to zero. With the DLL gone, the pointer and handle are no longer valid or useful.

For a more complete example, download the files old.c and new.c from the T&MW Web site. The old.c file contains the source for a “lost” DLL (old.dll) that provides code for two functions, func1 and func2. By using dumpbin—or another program—I determined the signatures of each function. I wrote old.c and new.c to use with Agilent VEE, so they contain some Agilent VEE-specific API calls, but you can understand the flow of the program without knowing exactly what the Agilent VEE calls do.

The new.c file contains the source for a DLL (new.dll) that will work with the old.dll software. The code in new.c illustrates the concepts of a forwarding DLL. The new.dll software forwards application calls to the func1 function in old.dll and modifies the returned data before passing the result back to the application program. The new.dll software passes calls from the application program to the func2 function in the old DLL and returns the results to the application program. It does not alter the func2 results in any way.

Note that in this example, the application must load the new DLL, new.dll. The application still calls func1 and func2 as it always did, but the function calls go to new.dll instead of to old.dll. As far as the application is concerned, old.dll doesn’t exist.

Software Resources

• You can find out more about writing DLLs at the following Web sites:
www2.ari.net/tobywan/DllIntro.html
www.elementkjournals.com/cpb/

9809/cpb9891.htm

• You can obtain the free PEBrowsePRO program at www.apnpc.com.au/swlib/

Computer_Programming/
Programmers_Utilities/000WQA.html
.
   This program produces a graphical and textual display of decoded information in a DLL. The software also disassembles DLLs and produces a hexadecimal and mnemonic display of assembly-language code.

• The shareware program ShowDep helps you track DLL files linked to
32-bit software. The program provides a text display of build information and import and export modules. Cost is $10. You can download the software from www.rocketdownload.com/details/

file/3412.htm
.

Store Pointers as Statics
The new.c code declares the old.dll handle as a static global. Every time the application program calls function func1 or func2, the new code checks this handle value to see if old.dll has been loaded. If it hasn’t, the forwarding function calls a helper function that loads the DLL. Each forwarding function also stores a static pointer to its counterpart in the old DLL. When the forwarding function runs, it checks to see if it has a valid pointer to its equivalent function in old.dll. If it doesn’t, the forwarding function produces a valid pointer.

Storing pointers as statics has several advantages. First, statics are always initialized to zero, unless you specifically declare them otherwise. Second, statics are persistent across separate calls to functions. Thus, each forwarding initializes a pointer value only on its first use. The first time a forwarding function runs, however, it takes slightly longer to execute due to the time it takes to locate and produce a pointer for the equivalent function in old.dll. If you weren’t using a static (that is, if you were using an automatic variable), you would need to rebind the function pointers every time you called the function.

Third, declaring the global variable as a static also means that software outside the compilation unit in which the variable was declared cannot access it. By limiting access to a specific name, the name can be used elsewhere, in another compilation unit. This is important in big projects, because it helps prevent conflicts, or “collisions,” between like-named variables.

When you use a forwarding DLL, the application requires no changes, other than to reference the new DLL rather than the old one. But the forwarding-DLL method has a disadvantage: Because the application now doesn’t know about the old DLL, the old DLL can stay loaded in memory until the application exits. Even if the application unloads the new DLL, it may not unload the old DLL. You can avoid this situation by using a clean-up function called from the application. But first you must ensure you can make such a change to the application software. You need to have the application’s source code to modify it.

Now if you face an application that requires changes to an old DLL for which you have little or no documentation and lack the source code, you have some techniques to work around the problem. T&MW

FOR FURTHER READING
1. Alcock, Donald, “Illustrating C, Revised Edition,” Cambridge University Press, New York, NY, 1992.

2. Traister, Robert J., “Mastering C Pointers,” Academic Press, San Diego, CA, 1990.

John Dumais works as a software engineer on the Agilent EESof circuit simulation tools. He received a B.S.E.E. from Texas A&M University. He was a member of the Agilent VEE research and development team. E-mail c/o T&MW: tmw@cahners.com.

Author’s Note
I used Microsoft Visual C++ 6.0 to build the software shown in this article. I didn’t use the Visual C++ integrated development environment, though. Instead, I handcrafted a make file, where it was appropriate to use an automated tool.

I chose this approach, because I think it gives a better understanding of what is really taking place when you build DLLs and applications that call them. For small examples, where an automated build is not necessary, I have included instructions to build the associated modules in comments in the example’s source.

I have tested the resulting applications and DLLs on PCs using Windows NT 4.0 and Windows 98. Listings in this article have been shortened to highlight key points. Get the complete listings.

Email
Print
Reprint
Learn RSS

Talkback

We would love your feedback!

Post a comment

» VIEW ALL TALKBACK THREADS

Related Content

Related Content

 

By This Author

There are no other articles written by this author.

Sponsored Links



 
Advertisement
SPONSORED LINKS

More Content

  • Blogs
  • Podcasts

Blogs

  • Martin Rowe
    Rowe's and Columns

    August 29, 2008
    LEDs, Tubes, and Clay
    The Champlain Valley (Vermont) Exhibition, which runs until August 31, has many of the usual things ...
    More
  • Martin Rowe
    Rowe's and Columns

    August 11, 2008
    Grachanen wins NCSLI award
    At last week's NCSL International Workshop and Symposium, Chris Grachanen was awarded the NCSLI Educ...
    More
  • » VIEW ALL BLOGS RSS

Podcasts

Advertisements





NEWSLETTERS

Click on a title below to learn more.

Test Industry News (3 Times Per Month)
Machine-Vision & Inspection (Monthly)
Communications Test (Monthly)
Design, Test & Yield (Monthly)
Automotive, Aerospace & Defense (Monthly)
Instrumentation (Monthly)
Resource Center E-Alert (Monthly)
©2008 Reed Business Information, a division of Reed Elsevier Inc. All rights reserved.
Use of this Web site is subject to its Terms of Use | Privacy Policy
Please visit these other Reed Business sites