Different ways to invoke the same DLL export function.
Introduction
This article describes the design and implementation of a DLL loader class. An instance of the described class enables explicit loading and unloading of a DLL file to and from memory as well as providing means to access any EXPORTED functions of a DLL file.
The key focus of this design is to enable a neat and clean syntax at sopurce level for executing routines that reside in a DLL. The primary design objective of this class is to provide an easy way to relocate different functions of an existing application into various dynamic-link-libraries (DLLs) so that different implementations of the same functions can be loaded at runtime.
In particular, the described class was originally designed to address the following scenario. Assume, an application is developed and compiled into a self contained exe file. The product is launched with great success and now a software architect (oh my God!) wants to factor out various common (maybe extensible) functions of this application into a DLL module. The idea is to load these functions at runtime from a DLL and invoke functions (now residing in the DLL) as if they are defined locally within the app. A DLL file utilizes the so-called PE format. Since there is an existing codebase to maintain, obviously, the programmer prefers as little changes to existing source code as possible.
#ifdef marco
#undef marco
#define marco macro
#endif
According to this scenario, the tasks to be accomplished are:
- relocate functions into a DLL and export them,
- load DLL during application runtime,
- provide access to EXPORTED DLL functions and
- unload DLL whenever appropriate.
As shown in the image above (top of page), the code required for loading an exported function is quite simple. More importantly, the sample code there demonstrated our DLL loader design can adapt itself to provide a C or C++ interface for accessing an EXPORTED DLL function. Notice also that in contrast to other known implementations, the use of macros is stripped to a bear minimum. The current implementation provides substantially cleaner syntax than other existing techniques known to the author at the time of writing.
I would like to point out that the original concept was to enable a program to load and discover all exported functions from a given DLL during runtime. This is still not achievable.
As a side note, I always believe that the reusability of your code can be measured by the number of lines required to bring out a particular pattern. The lesser lines required, the more reusable your code is. Also, reusability means natural C++ syntax. Obscure macro based implementation reduces reusability since fellow programmers must look at the docs to find out what that marco does (i.e.: MFC macro METHOD_PROLOGUE(xx,yy)
).
Design issues
The first idea that came to mind was to overload operator FunctionPointerType ()
of a class, where FunctionPointerType
is a function pointer to a function, so that during compile time, the compiler would rank all candidate functions and automatically type-cast a variable to a function pointer type. For example, consider the following code segment:
1 class DllFunction {
2 typedef int(*FuncPtrType)(int);
3 FuncPtrType fp;
4 public:
5
6 DllFunction(FuncPtrType t) { fp =t;}
7
8
9 operator FuncPtrType () {
10 return fp;
11 }
12 };
13
14 int test(int i) { cout << i << endl; }
15
16 int main() {
17 DllFunction a(test);
18
19 int bb = test(10);
20 int cc = a(10);
21 return 0;
22 }
23
This is in fact my first attempt to implement a DLL loader. Unfortunately, this code segment does not compile with the MSVC line of compilers (tested: MSVC6, VS.NET). After poking further than I should, I realized that operator FuncPtrType ()
was never considered to be a candidate function during the compilation stage. This is specific to MSVC line of compilers. The exact error is at line 20, "error C2064: term does not evaluate to a function"
. The above code segment compiles perfectly with g++. Utilizing operator overloading has one distinct advantage: it enables one to delay the explicit loading of a DLL into memory until the actual EXPORTED function is invoked. I.e.: implement some logic inside operator FuncPtrType()
to load a given DLL.
First Workaround
The author has dreamt up many different ideas to get round this limitation of the MSVC compiler. For the moment, two specific implementations are worth considering.
The compilation error occurs because the variable a
(on line 20 above) cannot be automatically typecast to a function pointer (due to limitation of the compiler). Some of the questions that I asked myself are: Is there a way to get around that limit? Keeping in mind that we have a large codebase to maintain, what would be the easiest way (least effort on my part) to enable a(10)
to actually invoke a function call?
The answer to these questions represents my first and second solutions which will be addressed promptly. For the first solution, the key is to use a function pointer variable that is defined either locally or globally within the application. In other words, we declare a
as int(* a)(int) = test;
. This eliminates the need to do an explicit typecast because the actual variable is a function pointer already. The disadvantage though is that the delay load behavior can no longer be supported neatly in comparison with using a class to encapsulate a DLL function. The following code segment shows how this can be done. I wonder why the code looks awfully like standard C code?
1
2 int _stdcall test(int i) { cout << i << endl; }
3
4 int main() {
5 int (_stdcall *a) (int);
6 a=test;
7
8 int bb = test(10);
9 int cc = a(10);
10 return 0;
11 }
12
Second Workaround
I am a bit reluctant to reveal my second implementation because it is not source level compatible with my existing codebase. Thus, it requires a lot of search and replace to rectify my existing code to work with this design. It was in the spirit of industry non-compliance and bad programming practice (thanks to one infamous software company) that I decided to present my second implementation. Just to re-iterate, what I am about to show you is a bad design since it is:
- not readily compatible with my existing codebase,
- it does not reflect the true power of
operator()()
and
- if the programmer decided not to use my design, the developed code must be re-edited manually before it will compile again.
Having said that, everything you will see is C++.
In order to appreciate the underlying reason for developing this implementation, let's go back to a question that was raised previously. If variable a
cannot be automatically typecast to a function pointer (due to limitation of a compiler), is it possible that we typecast it manually so that a(10)
will indeed invoke a function. I.e.:
typedef (int)(_stdcall* FuncPtrType)(int);
(FuncPtrType a)(10);
Of course, this is possible. There are various ways that this can be done. I have chosen to utilize operator()()
to return a Function Pointer Type object so that we can write code such as a()(10)
. The following code segment illustrates how this is done:
1 class DllFunction {
2 typedef int(*FuncPtrType)(int);
3 FuncPtrType fp;
4 public:
5
6 DllFunction(FuncPtrType t) { fp =t;}
7
8
9 operator FuncPtrType () {
10 return fp;
11 }
12
13
14 FuncPtrType operator ()() {
15 return fp;
16 }
17
18 };
19
20 int test(int i) { cout << i << endl; }
21
22 int main() {
23 DllFunction a(test);
24
25 int bb = test(10);
26 int cc = a()(10);
27
28 return 0;
29 }
30
Since this design uses objects to encapsulate a DLL EXPORTED function, it is now possible to implement a delayed load behavior. This can be done quite simply by introducing some logics into operator()()
. Refer to Zip file for an example of this.
Other possibilities
Downloadable Zip file
The attached downloadable zip file contains two sample implementations. The samples should be regarded as proof of concept implementations only. They are not production-ready code!!!
Please refer to the MSVC help files for more information regarding DLL mapping and loading of a function from a DLL.
Concluding remarks
The described implementations of a DLL loader enable clean and simple syntax at source level for accessing DLL functions. Four lines are required to load and execute an export function from a DLL file.
The author would like to re-iterate that the original concept was to enable a program to load and discover all exported functions from a given DLL during runtime. This is still not achievable currently, though there has been significant progress in that direction.
Finally, may I please point out that there are existing macro based implementations that work very well for general DLL usage. For example, the implementation by Yao Zhifeng A class to wrap DLL functions dated:26 Jun 2002.
History
version 1.0: 15Oct2002 2.50a - dklt
version 1.1: 12Nov2002 6.02a - dklt
version 1.2: 28Mar2003 5.22p - dklt
(submitted with 10 embedded typos) version 1.3: 04Oct2003 11.41p - dklt