This article show the structure of import library file (.lib) used along with header to link against DLL
Have you ever wondered about the contents of Microsoft's import library file? This article gives a brief description of .lib file structure and provides source to create import library given the name of export module (.dll; .sys; .exe files) and list of its exported functions (functions can be cherry-picked, so it's not necessary to provide full list).
On x86, we can use the following calling conventions for C function:
__cdecl__stdcall__fastcall__vectorcallOn x64, we can use the following calling conventions for C function (__cdecl, __stdcall, __fastcall are ignored by compiler):
__vectorcallSee this link for details about name decoration.
Import library is an archive, it starts with arch signature, it is an 8-byte string, namely:
!<arch>\nAfter the signature, we have a bunch of files:

Each file starts with fixed-length header, following the arbitrary-length body (header contains length of the body):

Now let's consider symbols "hosted" by import library. Assuming that export module in question is BOOTVID.DLL, we will have three "predefined" symbols:
__IMPORT_DESCRIPTOR_BOOTVID__NULL_IMPORT_DESCRIPTOR<7Fh> BOOTVID_NULL_THUNK_DATA // string starts with 0x7FFor each imported function, we will have two additional symbols. Let's consider this C function:
C++
void __stdcall VidDisplayString(char *pString);In x86 version of import library, we will have:
_VidDisplayString@4 // decorated function name__imp__VidDisplayString@4 // the same string prefixed by __imp_In x64 version of import library, we will have:
VidDisplayString__imp_VidDisplayString // the same string prefixed by __imp_Now let's describe each file contents. First two files in archive contain all symbol names along with offsets to other files. Then we have three files dedicated to three "predefined" symbols. And finally, we have files dedicated to functions (one file for each function).
File 1 contains number of symbols field, symbol names and their corresponding offsets (offsets to another files from the beginning of archive). So File 3 is dedicated to __IMPORT_DESCRIPTOR_BOOTVID, File 4 is dedicated to __NULL_IMPORT_DESCRIPTOR, and so on. Note that only one file is dedicated to each function, despite the fact that function introduces two symbols.

File 2 contains offset table, symbol names and their corresponding indexes (indexes into offset table). It is essentially the same information given in a different form. Note that indexing starts from 1.

Files 3, 4, 5 are dedicated to __IMPORT_DESCRIPTOR_BOOTVID, __NULL_IMPORT_DESCRIPTOR, <7F> BOOTVID_NULL_THUNK_DATA. I won't picture them in detail, they are essentially the same for all import libraries. Instead, I want to focus on File 6 and picture it in detail. Let's see the structure that represents file header:
C++
struct FILE_HEADER // size = 0x3C
{
char Part1[16];
char Id[24];
char Part3[8];
char BodyLength[10];
char Part5[2];
};Id field holds random number generated by time function (in ASCII form), BodyLength field holds the length of the following file body (in ASCII form). After file header, we have symbol descriptor:
C++
struct SYMBOL_DESCRIPTOR // size = 0x14
{
WORD a;
WORD b;
WORD c;
WORD Architecture;
DWORD Id;
DWORD Length;
union
{
WORD Hint;
WORD Ordinal;
WORD Value;
}
WORD Type;
};Architecture field contains 0x14C for x86 and 0x8664 for x64. Id field is the same random number represented in binary form. Hint / Ordinal field contains function hint / ordinal, Type field specifies import type (you will find description further). Length field contains the summary length of two following strings (including their null-characters):

We will consider three possible scenarios:
export module and export function without module definition file. Input:
Source:
C++
__declspec(dllexport) void __cdecl function1() {} __declspec(dllexport) void __stdcall function2() {} __declspec(dllexport) void __fastcall function3() {} __declspec(dllexport) void __vectorcall function4() {}
Output for x86:
Import library (SYMBOL_DESCRIPTOR):
C++
Name = _function1, Hint = 0, Type = 8 Name = _function2@0, Hint = 1, Type = 4 Name = @function3@0, Hint = 2, Type = 4 Name = function4@@0, Hint = 3, Type = 4
Import library stores Hint (starts from 0, represents index into AddressOfNames array).
Export module's export (AddressOfNames, AddressOfNameOrdinals):
C++
Name = _function1, Ordinal = index into AddressOfFunctions array Name = _function2@0, Ordinal = index into AddressOfFunctions array Name = @function3@0, Ordinal = index into AddressOfFunctions array Name = function4@@0, Ordinal = index into AddressOfFunctions array
Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).
Import module's import (IMAGE_IMPORT_BY_NAME):
C++
Name = _function1, Hint = 0 Name = _function2@0, Hint = 1 Name = @function3@0, Hint = 2 Name = function4@@0, Hint = 3
Output for x64:
Import library (SYMBOL_DESCRIPTOR):
C++
Name = function1, Hint = 0, Type = 4 Name = function2, Hint = 1, Type = 4 Name = function3, Hint = 2, Type = 4 Name = function4@@0, Hint = 3, Type = 4
Import library stores Hint (starts from 0, represents index into AddressOfNames array).
Export module's export (AddressOfNames, AddressOfNameOrdinals):
C++
Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4@@0, Ordinal = index into AddressOfFunctions array
Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).
Import module's import (IMAGE_IMPORT_BY_NAME):
C++
Name = function1, Hint = 0 Name = function2, Hint = 1 Name = function3, Hint = 2 Name = function4@@0, Hint = 3
SYMBOL_DESCRIPTOR):
C++
Name = _function1, Hint = 0, Type = 8 Name = _function2@0, Hint = 1, Type = 0xC Name = @function3@0, Hint = 2, Type = 0xC Name = function4@@0, Hint = 3, Type = 0xC
Import library stores Hint (starts from 0, represents index into AddressOfNames array).
Export module's export (AddressOfNames, AddressOfNameOrdinals):
C++
// function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array
Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).
Import module's import (IMAGE_IMPORT_BY_NAME):
C++
// function names are not decorated Name = function1, Hint = 0 Name = function2, Hint = 1 Name = function3, Hint = 2 Name = function4, Hint = 3
Output for x64:
Import library (SYMBOL_DESCRIPTOR):
C++
Name = function1, Hint = 0, Type = 4 Name = function2, Hint = 1, Type = 4 Name = function3, Hint = 2, Type = 4 Name = function4@@0, Hint = 3, Type = 0xC
Import library stores Hint (starts from 0, represents index into AddressOfNames array).
Export module's export (AddressOfNames, AddressOfNameOrdinals):
C++
// function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array
Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).
Import module's import (IMAGE_IMPORT_BY_NAME):
C++
// function names are not decorated Name = function1, Hint = 0 Name = function2, Hint = 1 Name = function3, Hint = 2 Name = function4, Hint = 3
export function using module definition file (we specify function name and ordinal). Input:
Source:
C++
void __cdecl function1() {} void __stdcall function2() {} void __fastcall function3() {} void __vectorcall function4() {}
Def file:
C++
EXPORTS function1 @1 function2 @2 function3 @3 function4 @4
Output for x86:
Import library (SYMBOL_DESCRIPTOR):
C++
Name = _function1, Ordinal = 1, Type = 0 Name = _function2@0, Ordinal = 2, Type = 0 Name = @function3@0, Ordinal = 3, Type = 0 Name = function4@@0, Ordinal = 4, Type = 0
Import library stores Ordinal (starts from 1, represents alternative function name).
Export module's export (AddressOfNames, AddressOfNameOrdinals):
C++
// function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array
Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). If we would use NONAME directive for some function, it would not have corresponding entries in AddressOfNames and AddressOfNameOrdinals arrays.
Import module's import (now we have Ordinal instead of IMAGE_IMPORT_BY_NAME):
C++
Ordinal = 1 Ordinal = 2 Ordinal = 3 Ordinal = 4
Output for x64:
Import library (SYMBOL_DESCRIPTOR):
C++
Name = function1, Ordinal = 1, Type = 0 Name = function2, Ordinal = 2, Type = 0 Name = function3, Ordinal = 3, Type = 0 Name = function4@@0, Ordinal = 4, Type = 0
Import library stores Ordinal (starts from 1, represents alternative function name).
Export module's export (AddressOfNames, AddressOfNameOrdinals):
C++
// function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array
Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). If we would use NONAME directive for some function, it would not have corresponding entries in AddressOfNames and AddressOfNameOrdinals arrays.
Import module's import (now we have Ordinal instead of IMAGE_IMPORT_BY_NAME):
C++
Ordinal = 1 Ordinal = 2 Ordinal = 3 Ordinal = 4
When we import function by Name, we obtain its address in the following way:
C++
if (strcmp(FunctionName, AddressOfNames[Hint]))
{
for (int i = 0; i < NumberOfNames; ++i)
{
if (!strcmp(FunctionName, AddressOfNames[i]))
{
Hint = i
break
}
}
}
Index = AddressOfNameOrdinals[Hint]
Address = AddressOfFunctions[Index]When we import function by Ordinal, we obtain its address in the following way:
C++
Address = AddressOfFunctions[Ordinal - Base]INTERNALNAME allows you to use another name ("internal") in your export module's code. Consider this example:
Source:
C++
void __stdcall internal_name() {}Def file:
C++
EXPORTS
external_name = internal_nameFrom import/export mechanism point of view, it is equivalent to:
Source:
C++
void __stdcall external_name() {}Def file:
C++
EXPORTS
external_namePRIVATE allows you to build import library without some symbol ("partial" import library).
We can use dumpbin tool to get information about export module's exports (for which we do not have source). Possible dumpbin outputs:

Here we see decorated function name, hint and ordinal.

The same, except function name is not decorated.

Here we see ordinal only. Since module does not store function name, hint also does not exist.
Note that export will always have ordinal, however export will not necessarily have name and hint.
Since calling convention is not stored, we can determine it by examining function's code by disassembler. On x86, it is usually __stdcall or __fastcall, on x64, it is usually default calling convention.
For each imported function, we need to specify:
Import types:
IMPORT_BY_SPECIFIED_NAMEIMPORT_BY_DECORATED_NAMEIMPORT_BY_ORDINALIMPORT_BY_SPECIFIED_NAME:
Import module will hold undecorated function name and Hint that is given by Value parameter.
Export module must store undecorated function name.
IMPORT_BY_DECORATED_NAME:
Import module will hold decorated function name and Hint that is given by Value parameter.
Export module must store decorated function name.
IMPORT_BY_ORDINAL:
Import module will hold Ordinal that is given by Value parameter.
Export module does not need to store function name.
Size of argument list in bytes parameter is needed to perform function name decoration.
The code to generate import library:
C++
int main(int argc, char* argv[])
{
char *pName;
SYMBOL_INFO *pSymbolList;
pName = "BOOTVID";
pSymbolList = CreateSymbolList(pName);
g_InfoAll.bX64 = FALSE; // specify TRUE for x64
AddFunction(pSymbolList, "VidDisplayString", 4, 4,
CALLING_CONVENTION_STDCALL, IMPORT_BY_SPECIFIED_NAME);
WriteImportLibrary(pName, ".dll", pSymbolList);
DestroySymbolList(pSymbolList);
return 0;
}Here, we specify architecture (x86), export module name (BOOTVID), export module extension (.dll) and function list.
In our import module's source, we would declare the function in the following way:
C++
__declspec(dllimport) void __stdcall VidDisplayString(char *pString); // C functionWe have not considered variables, DATA and CONSTANT directives, importing using .def file, purpose of __imp_, and C++ names. I will update the article later to cover all these topics. Thank you for reading.
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)