Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C++

Creating Import Library from a DLL with Header File

Rate me:
Please Sign up or sign in to vote.
4.61/5 (28 votes)
20 Jan 2011CPOL8 min read 99K   4.2K   85   19
A tutorial to guide you through the process of creating a lib from a third-party DLL

Introduction

There are two ways that one can use a DLL: implicit (or static) and explicit (or dynamic). For the former, an import library (.lib file) will be required. Normally this .lib file is shipped along with the DLL, however, this may not be the case for some third-party DLLs. So to use such a DLL implicitly, we have to create its .lib file.

If you search the web on this topic, you will get Microsoft's KB page http://support.microsoft.com/kb/131313/en-us and a previous CodeProject article libfromdll.aspx. The Microsoft's page contains a lot of useful information, but it lacks sample source to illustrate the procedure, and the latter page is to a large extent off the topic. This article will provide both the underlying theory and a detailed tutorial together with a sample source package. So I expect my article to be very useful.

The Main Principle

To tell the truth, there is no hat magic for generating a lib. We will absolutely need the names or ordinals of the exported functions. In addition to these, we may need a header file that contains the argument descriptions, type of return value, calling convention, etc. (the so-called "prototype" or "signature" information) of the exported functions to properly use them. The former information can be obtained directly from the DLL itself, while the latter needs to be provided by the vendor/author of the DLL.

In my understanding, the lib file does not itself contain the full signature information; it is only that the signature of the function may affect its name mangling, which is directly reflected in the lib. If extern "C" and __cdecl are used for the exported functions, then the signature will not affect the name mangling, and in such cases, I believe it possible to create a lib without the header file. However, a lib is useless without a header file to ensure safe use of the DLL. So it makes little (if not no) sense to talk about generating a lib without a header file.

We will use Microsoft Visual C++ (MSVC6 in my case) to make a lib. Recall that MSVC will generate a DLL and a lib from the c/CPP source and a .def (module definition) file. The .def file is not absolutely necessary if you work only with the __cdecl calling convention, but to work with __stdcall, I found it indispensable. So, roughly, we use the following major steps:

  1. Write a .def file based on the information from the DLL, using the DUMPBIN tool.
  2. Write the source.
  3. Let MSVC build the DLL and lib. And we are done.

But wait, how do we ensure that the generated lib can be used as the lib of the original DLL? In fact, the above-mentioned source is just meant as a "trivial wrapper" of the original DLL, that is, for each exported function in the original DLL, in the wrapper there is a corresponding exported function, which does nothing but jump to the entry of the original function. So the trivial wrapper has the same external behavior as the original DLL, and thus the lib we built can be used as if it were the lib of the original DLL.

The Sample DLL

To illustrate our procedure, I have included a sample DLL, which is pretended to be without a lib (although actually it is not). The source of the sample DLL is contained in DllSample.zip for download, but it is not important and can be disregarded.

The sample DLL (DllSample) exports two functions:

C++
BOOL __cdecl NumberList(int begin, int end);
void __stdcall LetterList(int begin, int end, BOOL capital);

We deliberately use different calling conventions and return types in order to illustrate our method in a manner as general as possible. When NumberList is invoked, it displays a message box listing the numbers from begin to end. When LetterList is invoked, it displays a message box listing the letters between the (begin+1)-th and the (end+1)-th; the BOOL argument capital controls whether the letters are capital.

Writing the DEF File

MSVC comes with a DUMPBIN tool to dump the contents of Windows binary files. Now if DllSample.dll is in the current directory, type "dumpbin /exports dllsample.dll", then the exported functions will be displayed as follows:

C++
ordinal hint RVA      name 
      2	   0 000010A0 LetterList                          
      1      00001000 [NONAME]

This reveals that LetterList is exported both by name and by ordinal, and NumberList (this name is not included in the DLL binary, but the header file will include its information) is exported only by ordinal. By copying the output of DUMPBIN to the clipboard, pasting it to a text file, and modifying it appropriately according to the format of a def file, we can arrive at the following def file:

C++
    LIBRARY      "DllSample"

EXPORTS
    NumberList   @1 NONAME
    LetterList   @2 

Writing the Source

This major step consists further of several steps.

Writing the header file for caller's use

A header file to be included at the DLL's caller side should contain the prototype of the exported functions. The vendor/author of the original DLL will usually have provided a raw header, but it may be in another language or developed in another platform, for example, in C++ Builder, Delphi, or VB, so we often need to edit it so that it can successfully compile under MSVC. In our example, the core part of the header DllSample.h should be:

C++
extern "C" __declspec(dllimport) BOOL __cdecl NumberList(int begin, int end);
extern "C" __declspec(dllimport) void __stdcall LetterList
			(int begin, int end, BOOL capital);

The inclusion of extern "C" is to instruct the compiler to use C-style name mangling, to allow programs written in C to use our lib. The inclusion of __declspec(dllimport) is optional, but it will help boost the performance slightly. For detailed discussion of this, see Matt Pietrek's article http://msdn.microsoft.com/en-us/magazine/cc301805.aspx.

Renaming DllSample.dll to __DllSample.dll

The target files of the MakeLib project are not MakeLib.dll and MakeLib.lib, but instead are DllSample.dll and DllSample.lib. To avoid name collision, the original DLL, i.e., the DllSample.dll generated in the DllSample project, is to be renamed to __DllSample.dll.

Writing the Data Part of MakeLib

The DllUtil.zip contains three projects: DynCall, MakeLib, and UseLib. DynCall is to illustrate the dynamic call of the DLL; MakeLib, as we said, is to generate a wrapper DLL and a lib; and UseLib is to demonstrate that the lib generated in MakeLib can be used the same as the unavailable lib of the original DLL. The inclusion of project DynCall in our workspace is because DynCall and MakeLib share a large portion of source code, and DynCall is useful in its own right.

The data part of DynCall and MakeLib are very similar, but they also have important differences. In both sources, there are many pointers to the imported functions, but in DynCall they must be declared as global, whereas in MakeLib they must be defined as static, since they need not be externally linked. Roughly, the core source snippet of MakeLib.cpp relevant to function pointers is as follows:

C++
#define FUNCDEF_NumberList(NumberList) \
BOOL __cdecl NumberList(int begin, int end)

#define FUNCDEF_LetterList(LetterList) \
void __stdcall LetterList(int begin, int end, BOOL capital)

// if the function pointers are to be used in a DLL wrapper setting
// they should be defined in file scope, i.e. static variables
#define STATDEF_IMPORT_FUNC(funcname) \
    typedef FUNCDEF_##funcname(IMP_##funcname); \
    static IMP_##funcname *imp_##funcname;

// Define the imported function pointers as static variables,
// since they need not go public. Please include your specific functions
STATDEF_IMPORT_FUNC(NumberList)
STATDEF_IMPORT_FUNC(LetterList)

This expands to the following:

C++
typedef BOOL __cdecl IMP_NumberList(int begin, int end);
static IMP_NumberList *imp_NumberList;

typedef void __stdcall IMP_LetterList(int begin, int end, BOOL capital);
static IMP_LetterList *imp_LetterList; 

Then it is easy to see that the imp_xxxx's are static pointers to imported functions, which are to be assigned values when the wrapper DLL is loaded.

At this point, the reader may wonder why I used so many "weird" macros. Macros are not really useful if you only have a handful of functions, but a commercial DLL may well export hundreds of functions, and in such cases using a suitably designed set of macros can save us a lot of typing work. I myself have a direct experience on this.

Writing the Code Part of MakeLib

About half of MakeLib code implements the functionality of loading the original __DllSample.dll, getting the addresses of the exported functions, and storing them in the function pointers for dynamic call. The code for this functionality is the same as in ImportDllSample.cpp, the main source for DynCall, and it should be easy to read.

Now let us look at the implementation of exported wrapper functions. As said, the wrapper is called trivial because whenever an exported wrapper function is called, it simply jumps to the entry point of the corresponding original function in __DllSample.dll. But how do we achieve this? Please see the following code snippet excerpted from MakeLib.cpp:

C++
// Defines trivial wrapper of the original function. It contains only one instruction,
// that is, jump to the entry of the original function
#define NAKED_WRAPPER(funcname) \
extern "C" __declspec(naked) FUNCDEF_##funcname(funcname) \
{ \
    __asm jmp imp_##funcname \
}

NAKED_WRAPPER(NumberList)
NAKED_WRAPPER(LetterList)  

Again, this expands to the following:

C++
extern "C" __declspec(naked) BOOL __cdecl NumberList(int begin, int end)
{
    __asm jmp imp_NumberList
}

extern "C" __declspec(naked) 
	void __stdcall LetterList(int begin, int end, BOOL capital)
{
    __asm jmp imp_LetterList
} 

The key to the above code is the keyword __declspec(naked). There is a detailed explanation of it in MSDN.

Verifying the Correctness

Now we launch UseLib.exe to test the correctness. The calling chain now is UseLib -> (new) DllSample.dll -> (original) DllSample.dll. So if you wish to check that DllSample.lib can be used directly as the lib of the original DllSample.dll. You need to rename __DllSample.dll back to DllSample.dll, and then run UseLib.exe.

Conclusion

We can now see that the so-created DllSample.lib can work perfectly as the import library of the original DllSample.dll. To sum up, the above-described procedure for import library creation has two merits:

  1. MakeLib.cpp has a very generic skeleton, and the use of macros helps to keep typing minimal.
  2. The wrapper DLL can sometimes be used for investigation or hacking purposes. The implementation of the wrapper functions can easily be modified to be "non-trivial", to allow us to do some hacking before jumping to the original function. However, if you wish to do the hacking after calling the original function, then the naked implementation is probably no longer suitable.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
China China
Yiping Cheng is both a software developer and a researcher. He has been programming in C/C++ for over 15 years. His favorite platform is Windows and he loves using MFC to build full-fledged Windows applications. When he is not programming or researching, he enjoys listening to traditional opera and music.

Comments and Discussions

 
QuestionExcellent! Pin
MrBjorn25-Sep-18 23:39
MrBjorn25-Sep-18 23:39 
Questionmy vote of 5 Pin
Member 1140506926-Jan-15 22:40
Member 1140506926-Jan-15 22:40 
Generalmy vote of 5 Pin
Southmountain23-Oct-14 16:05
Southmountain23-Oct-14 16:05 
GeneralMy vote of 5 Pin
aydinsahin12-Nov-12 22:31
aydinsahin12-Nov-12 22:31 
GeneralMy vote of 5 Pin
gndnet26-Jun-12 8:44
gndnet26-Jun-12 8:44 
Suggestionpexports + dlltool/lib Pin
Feiyun Wang31-Jul-11 2:53
Feiyun Wang31-Jul-11 2:53 
GeneralMy Vote of 5 Pin
Midnight48924-Jan-11 2:30
Midnight48924-Jan-11 2:30 
GeneralNo subject Pin
xComaWhitex17-Jan-11 7:03
xComaWhitex17-Jan-11 7:03 
GeneralRe: No subject Pin
Yiping Cheng17-Jan-11 14:30
Yiping Cheng17-Jan-11 14:30 
GeneralRe: No subject Pin
Yiping Cheng17-Jan-11 14:46
Yiping Cheng17-Jan-11 14:46 
GeneralI rate it 3 because.. Pin
Member 170172917-Jan-11 4:11
Member 170172917-Jan-11 4:11 
GeneralRe: I rate it 3 because.. Pin
Yiping Cheng17-Jan-11 5:23
Yiping Cheng17-Jan-11 5:23 
GeneralRe: I rate it 3 because.. Pin
cmk6-May-13 10:42
cmk6-May-13 10:42 
Generallike it Pin
Pranay Rana17-Jan-11 1:21
professionalPranay Rana17-Jan-11 1:21 
GeneralMy vote of 3 Pin
Ajay Vijayvargiya16-Jan-11 3:41
Ajay Vijayvargiya16-Jan-11 3:41 
GeneralRe: My vote of 3 Pin
Yiping Cheng16-Jan-11 3:52
Yiping Cheng16-Jan-11 3:52 
GeneralRe: My vote of 3 Pin
Anna-Jayne Metcalfe17-Jan-11 10:08
Anna-Jayne Metcalfe17-Jan-11 10:08 
Just a thought - adding brief details of the background to why you needed it would actually improve the flow of the article.
Anna Rose | [Rose]

Tech Blog | Visual Lint

"Why would anyone prefer to wield a weapon that takes both hands at once, when they could use a lighter (and obviously superior) weapon that allows you to wield multiple ones at a time, and thus supports multi-paradigm carnage?"

GeneralMy vote of 5 Pin
Tage Lejon16-Jan-11 2:18
Tage Lejon16-Jan-11 2:18 
GeneralRe: My vote of 5 Pin
Yiping Cheng16-Jan-11 3:24
Yiping Cheng16-Jan-11 3:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.