3044 How to Build and Use a Simple DLL Last update 06/12/96 KEYWORDS: DLL build use simple AREA: TI This document discusses how to create dynamically linked libraries (DLLs) using version 5.0 of the Borland C++ compiler. Topics covered include 16-bit and 32-bit DLLs, linking explicitly or linking on startup, exporting functions and classes, ordinal values, and module definition files. All code designated as 16-bit in this article will compile correctly for the 32-bit platform. Creating the Simplest DLL ========================= The steps below are all that is necessary to create a basic DLL. You may read the following explanations if you so desire. 32-Bit ====== 1. Click on the New Project toolbar button in the Borand C++ IDE. The New Target dialog appears. 2. Enter a project path and name the target "SAMPLE1". 3. Select "Dynamic Library [.dll]" as the Target Type. 4. Click OK. 5. In the Project window, delete the .RC and .DEF nodes. 6. In the .CPP file, type the following, #include <windows.h> extern "C" void __declspec(dllexport) WINAPI YourFunctionNumberOne () { MessageBox (NULL, "We are now inside your function in your DLL", "Your Message Title", NULL ); } 7. Click Build Project on the toolbar. Your "SAMPLE1.DLL" is now complete. 16-Bit ====== 1. Click on the New Project toolbar button in the Borand C++ IDE. The New Target dialog appears. 2. Enter a project path and name the target "SAMPLE1". 3. Select "Dynamic Library [.dll]" as the Target Type. 4. Select Platform as Windows 3.x (16). 4. Click OK. 5. In the Project window, delete the .RC and .DEF nodes. 6. In the .CPP file, type the following, #include <windows.h> extern "C" void __export WINAPI YourFunctionNumberOne () { MessageBox (NULL, "We are now inside your function in your DLL", "Your Message Title", NULL ); } 7. Click Build Project on the toolbar. Your "SAMPLE1.DLL" is now complete. Calling the DLL from an EXE =========================== 1. Go to the Project menu and select New Target. 2. Enter a path and name the target "SAMPLE2". 3. Set the Platform to coincide with that of the DLL. 4. Click OK. 5. In the Project window, delete the .RC and .DEF nodes. 6. In the .CPP file type the following, 32-bit ====== #include <windows.h> extern "C" void __declspec(dllexport) WINAPI YourFunctionNumberOne (); int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) { YourFunctionNumberOne (); return FALSE; } 16-bit ====== #include <windows.h> extern "C" void __export WINAPI YourFunctionNumberOne (); int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) { YourFunctionNumberOne (); return FALSE; } 7. In the Project window, right click on the "SAMPLE2.EXE" node. 8. Click on Add Node and add "SAMPLE1.LIB" to the project. 9. Make and run the project. Extern "C" ========== If you want it simple, use, extern "C" in all function prototypes. It suppresses name mangling. Omitting this modifier on a DLL function's prototype can cause much frustration. When a C++ module is compiled, function names are generated that include an encoding of the function's argument types. This is known as name mangling. The syntax for such mangling of function names is unique for each manufacturer's compiler. Using the 'extern "C"' modifier prevents name mangling thus providing an easy way to export functions from a DLL. By not mangling a function's name, other applications can call the function in the DLL using exactly the same name as it appears in the DLL source code. In the above example, the extern "C" modifier can be omitted from the function's prototype in both CPP modules with no ramifications. This is because both modules are compiled with Borland C++ version 5.0, so the function name is mangled with the same syntax in each module. Omitting the extern "C" modifier, however, would force applications not compiled with Borland C++ version 5.0 to use the mangled name in order to call the function within the DLL. Please see the sections "Module Definition Files" and "LoadLibrary and GetProcAddress" for how to export C++ functions. __declspec(dllexport) and __export ================================== Functions ========= The simplest way to make a function in a DLL visible to other applications is to use on of the following modifiers: __declspec(dllexport) in a 32-bit DLL __export in a 16-bit DLL in the function prototype. For efficiency, set the compiler options for Entry/Exit code to "Windows DLL Explicit Functions Exported" (-WDE,-WCDE). Another technique is to list the function you want to make visible to other applications in the EXPORTS section of a module definition file. If this is done and the __declspec(dllexport) or __export modifier is not used in the function prototype, then you MUST use the compiler option for Entry/Exit code "Windows DLL All Functions Exportable" (-WD,-WCD). Please don't overlook this requirement. If a function is made visible by listing it the the EXPORTS section, but does not get the proper prolog code generated, runtime crashes will occur. Also please see the section below, "Module Definition Files", for further information. Please refer to chapter six of the Borland C++ Programmer's Guide, section "Prologs, epilogs, and exports: A summary" for a complete discussion of exportable and exported functions. Data ==== 32-bit ====== In a 32-bit DLL an easy way to export data is the following: //yourdll.cpp int __declspec(dllexport) MyGlobalVariable; //yourexe.cpp extern int __declspec(dllimport) MyGlobalVariable; When building YOUREXE.EXE, link in the import library for the DLL (YOURDLL.LIB in this example). 16-bit ====== In a 16-bit DLL an easy way to export data is the following: //yourdll.cpp int __export MyGlobalVariable; //yourexe.cpp extern int __far MyGlobalVariable; //yourexe.def . . IMPORTS YOURDLL.MyGlobalVariable; If you don't want to use a DEF file and you want to load your DLL explicitly, you can use LoadLibrary and GetProcAddress as follows, //yourexe.cpp HINSTANCE hinstDLL = LoadLibrary ("YOURDLL.DLL"); int * pSomeVariable; pSomeVariable = (int *) GetProcAddress (hinstDLL, "_MyGlobalVariable"); Classes ======= The '__declspec(dllexport)' and '__export' modifiers can also be used to export a class from a DLL. As a 32-bit example, modify the DLL source file SAMPLE1.CPP so it contains only the following: #include <windows.h> class __declspec(dllexport) YourClassNumberOne { private: int MemberOne; public: static int MemberTwo; int FunctionGetMemberOne (); }; int YourClassNumberOne::MemberTwo; int YourClassNumberOne::FunctionGetMemberOne () { MemberOne = 55; return MemberOne; } Make the SAMPLE.DLL target. By using the __declspec(dllexport) modifier, all non-inline member functions and static data members of the class are exported. Now change the EXE source file SAMPLE2.CPP, to contain only the following: #include <windows.h> class __declspec(dllimport) YourClassNumberOne { private: int MemberOne; public: static int MemberTwo; int FunctionGetMemberOne (); }; int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) { YourClassNumberOne A; char szMessageString[40]; int i = A.FunctionGetMemberOne (); wvsprintf (szMessageString, "The number is %i", &i); MessageBox (NULL, szMessageString, "Your Application", NULL); A.MemberTwo = 59; wvsprintf (szMessageString, "The number is %i", &A.MemberTwo); MessageBox (NULL, szMessageString, "Your Application", NULL); return FALSE; } Make and run SAMPLE2.EXE. For 16-bit, use this same example, as above, but simply replace '__declspec(dllexport)' with '__export' and '__declspec(dllimport)' with '__import' One important caution. Classes are a C++ feature. With that feature comes name mangling. Therefore all member functions of exported classes will be mangled. To call those member functions from other applications, the mangled names must be used. In this example, since both SAMPLE1.CPP and SAMPLE2.CPP are compiled with Borland C++ version 5.0, the member function name is mangled with the same syntax. Again, please see the sections "Module Definition Files" and "LoadLibrary and GetProcAddress" for cases necessitating this precaution. WINAPI ====== In the 32-bit Windows header file the macro WINAPI is defined as follows: #define WINAPI __stdcall __stdcall is the 32-bit Windows API function calling convention. It stipulates function parameters are pushed on the stack from right to left, the called function cleans up the stack on function exit, and the function name is not modified upon compilation. In the 16-bit Windows header file, the macro WINAPI is defined as follows: #define WINAPI __far __pascal The Pascal calling convention is used for all 16-bit Windows API functions. It stipulates parameters are pushed on the stack from left to right, the called function cleans up the stack upon exit and the function name is changed to all upper case upon compilation. The '__far' modifier specifies references to the function will be made with 32-bit pointers (Segment:Offset). This is necessary since the Windows API functions will not exist in the local segment of the calling application. 16-Bit Considerations ===================== When compiled for 16-bit, Pascal function names are changed to all upper case. C is a case sensitive language, so in order to call such a function from another application, all upper case must be used in the function name. By contrast, C function names are not changed to upper case but are prefixed with an underscore when compiled. Similarly, in order to call a C function from another application, the function must be specified with an underscore added to the beginning of name. In the above example, a link to the DLL is made using an import library. This technique does not require the use of modified names for Pascal or C functions. Explicitly loading a DLL and finding a function's address at run time does require the use of modified names for Pascal and C functions. Please see the section "LoadLibrary and GetProcAddress" below. In addition, be sure to use the Pascal calling convention for callback functions in 16-bit Windows. For other functions, be sure to prototype precisely the function in the module importing a function exported from a DLL. Don't mix Pascal, __stdcall and C calling conventions. Failure to heed these precautions would result in stack corruption upon function exit manifested as corrupted data and general protection faults. Loading and Linking the DLL =========================== Below are discussed three methods of linking to functions in a DLL. Please see the above section "__declspec(dllexport) and __export" for how to link to data and classes exported from a DLL. This section includes, 1. Import Libraries, 2. the LoadLibrary/GetProcAddress functions 3. Module Definition (.DEF) files. Import Libraries ================ By including SAMPLE1.LIB in the SAMPLE2.EXE project above, the SAMPLE1.DLL is loaded into memory automatically when SAMPLE2.EXE is executed. This is the simplest method to link a DLL with another application. SAMPLE1.LIB is called an import library and can be created using the IMPLIB.EXE utility. Import libraries are created automatically in Borland C++ when compiling a DLL from the IDE. LoadLibrary and GetProcAddress ============================== DLLs can be loaded explicitly in an application using the Windows API function LoadLibrary. After the DLL is loaded, the application must call the API function GetProcAddress to find the address for each function in the DLL it wants to call. Please note this technique cannot be used for non-static member functions of classes exported from a DLL. This is due to the hidden 'this' pointer in a member function's argument list. Because of the hidden 'this' pointer, the return value from GetProcAddress, FARPROC, cannot be cast into the correct function pointer type. Using the example from section "Creating the Simplest DLL", 1. Delete SAMPLE1.LIB from the SAMPLE2.EXE project. 2. The next step depends on the target. 32-Bit ====== 3. Modify SAMPLE2.CPP so it contains only the following. #include <windows.h> int PASCAL WinMain (HINSTANCE, HINSTANCE, LPSTR, int) { HINSTANCE hinstDLL = LoadLibrary ("SAMPLE1.DLL"); FARPROC lpYFNO = GetProcAddress (hinstDLL, "YourFunctionNumberOne"); if (lpYFNO != NULL) (*lpYFNO)(); else MessageBox (NULL, "No Go", NULL, NULL); FreeLibrary (hinstDLL); return FALSE; } If the extern "C" modifier had not been used in the function prototype in the DLL, then the call to GetProcAddress would have to include the mangled name of the function. FARPROC lpYFNO = GetProcAddress (hinstDLL, "@YourFunctionNumberOne$qv"); 16-Bit ====== 3. Modify SAMPLE2.CPP so it contains only the following. #include <windows.h> int PASCAL WinMain (HINSTANCE, HINSTANCE, LPSTR, int) { HINSTANCE hinstDLL = LoadLibrary ("SAMPLE1.DLL"); FARPROC lpYFNO = GetProcAddress (hinstDLL, "YOURFUNCTIONNUMBERONE"); if (lpYFNO != NULL) (*lpYFNO)(); else MessageBox (NULL, "No Go", NULL, NULL); FreeLibrary (hinstDLL); return FALSE; } Please notice the function name had to be specified using all upper case. This is because the function was defined as PASCAL in the DLL. If PASCAL had not been used in the DLL, the function would be defined as using the C-calling convention by default In that case, the call to GetProcAddress would have to include the original function name prefixed with an underscore. FARPROC lpYFNO = GetProcAddress (hinstDLL, "_YourFunctionNumberOne"); If the extern "C" modifier had not been used in the function prototype in the DLL, then the call to GetProcAddress would have to include the mangled name of the function. For a PASCAL function, it would be: FARPROC lpYFNO = GetProcAddress (hinstDLL, "@YOURFUNCTIONNUMBERONE$QV"); And for a C function it would be, FARPROC lpYFNO = GetProcAddress (hinstDLL, "@YourFunctionNumberOne$qv"); None of this name modification according to calling convention occurs in 32-bit programs. The PASCAL constant is defined in the 32-bit Windows header as __stdcall. So even if you recompile 16-bit code with a PASCAL modifier on a function, for 32-bit, the new __stdcall calling convention will be used in the newly compiled module. Module Definition Files ======================= Every Windows program requires a module definition (DEF) file. Normally, if you don't provide one yourself, the linker will use a default one. In Borland C++ the default DEF file is BORLANDC\LIBS\DEFAULT.DEF if BORLANDC is the directory in which you installed the compiler. See the Borland C++ User's Guide for the Module Definition File Reference. (Page 136 in version 4.5) Functions can be exported from and imported into your program by linking your own DEF file and making entries in its EXPORTS and IMPORTS sections. Exporting and importing by this technique is not as simple as the others methods mentioned above. The one advantage of using a DEF file to export or import functions, however, is the ability to assign them alias names. To import functions into your application: 1. Use IMPDEF to find the exported name and/or ordinal number of the function in the DLL which is exporting the function. When functions are exported from a DLL or EXE, they can be referenced by either their names or ordinal numbers. IMPDEF is a utility that "dumps" the functions exported by a DLL or EXE, giving both their names and numbers. You can use either to refer to these functions. 2. List the function names or numbers in the IMPORTS section of the calling program's DEF file. For most cases, it is easier and safer to use name references. So, for example, you run IMPDEF on MYFUNCS.DLL with: IMPDEF MYFUNCS.DEF MYFUNCS.DLL This would generate something like the following: LIBRARY MYFUNCS DESCRIPTION 'MYFUNCS.DLL' EXPORTS WEP @1 _FunctionA @2 ; FunctionA(char) FUNCTIONB @3 ; FunctionB(char) @FunctionC$qc @4 ; FunctionC(char) So using the information provided by IMPDEF, you could add the following to your program's DEF file: IMPORTS MYFUNCS._FunctionA ; FunctionA(char) cdecl extern "C" MYFUNCS.FUNCTIONB ; FunctionB(char) PASCAL extern "C" MYFUNCS.@FunctionC$qc ; FunctionC(char) cdecl mangled C++ Or, if ordinal numbers are used, then the IMPORTS section would look like: IMPORTS MYFUNCS.2 MYFUNCS.3 MYFUNCS.4 Refering to functions by ordinal value is faster. The IMPORTS section can also designate alias names for functions to be used internally. This eases importing of mangled names. IMPORTS ImportedFunction1=MYFUNCS._FunctionA ImportedFunction2=MYFUNCS.FUNCTIONB ImportedFunction3=MYFUNCS.@FunctionC$qc 3. Add the DEF file to your project. To export functions from a DLL: 1. Keep it simple. Use the __export keyword for all functions you wish to export and the -WDE/-WCDE compiler Entry/Exit code options, "Windows DLL Explicit Functions Exported". 2. Use the EXPORTS section of your module definition file to assign alias names to your functions if desired. This is optional. EXPORTS Read1 =@__smread$qv @1 Read2 =@__smread$qcc @2 Write1 =@__smwrite$qv @3 This technique can be a nice way to export C++ functions with mangled names. Exporting functions by ordinal also reduces the memory required for the DLL to load. 3. Add the DEF file to your project. LibMain, WEP, DLLEntryPoint =========================== The Borland C++ compiler will supply default versions of these functions if none are supplied in your DLL source. LibMain and WEP are good places to allocate and free global memory for your DLL. LibMain is also good to initialize global variables in your DLL. Be sure to use GlobalAlloc with the GMEM_SHARE flag when allocating global memory for your DLL. If you are using the Borland C++ memory suballocator ("new" operator) then please see TI863 as mentioned below, about changing the _WinAllocFlag variable. DllEntryPoint replaces both LibMain and WEP in a 32-bit DLL and consists of a 4-way switch statement to handle process and thread attaching and detaching. DllEntryPoint outline ===================== BOOL WINAPI DLLEntryPoint (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: // The DLL is being mapped into the // process's address space. break; case DLL_THREAD_ATTACH: // A thread is being created. break; case DLL_THREAD_DETACH: // A thread is exiting cleanly. break; case DLL_PROCESS_DETACH: // The DLL is being unmapped from the // process's address space. break; } return(TRUE); } Win32 calls this function whenever a DLL attaches to a process and whenever a DLL detaches from a process. The hinstDLL parameter contains the instance handle of the DLL. Like the hInstance parameter to WinMain, this value identifies the virtual address of where the view of the DLL was mapped in the process's address space. Usually, you'll save this parameter in a global variable so that you can use it in calls that load resources such as DialogBox and LoadString. Please see the Win32 Online Help for further details of the DLLEntryPoint function. On the MSDN CD for October 95, there is also a good article on the DllEntryPoint function in the book "Advanced Windows NT", Chapter Seven Dynamic-LinkLibaries. Associated Borland TI Documents =============================== TI1718 - Configuring classes for use in DLL's and EXE's (16-bit issues) TI1017 - Creating Windows DLL's using v3.1 of Object Windows (16-bit and OWL 1. TN1840 - Class notes on C++ and DLLs Issues to consider when sharing a class between an .EXE and a .DLL. (An excellent discussion of how to export C++ classes from a 16-bit DLL. This document must be requested by phone from a Borland C++ Tech Support engineer.) "Three ways to link with a Dynamic Link Library (DLL)" (Another discussion of the issues in this article) TN2546 - Allocating memory with GlobalAlloc in a DLL, GMEM_SHARE TI863 - Understanding Memory Allocation Under Windows Books ===== "Windows Programmer's Guide to DLLs and Memory Management" Mike Klein, Sams Publishing, Carmel, IN, 1992 Borland C++ 4.5 "Programmer's Guide", "Writing Dynamic-Link Libraries", p. 201-206 "Module Definition Files" .ff, p. 185-190. Borland C++ 4.5 "User's Guide", p. 138-139, p. 143-146 "Advanced Windows NT", Chapter Seven Dynamic-Link Libraries. Jeffery M. Richter, Microsoft Press, Redmond WA, 1994 TI