Linking 3rd-party DLLs

UPDATE: 1/24/98

This tip discusses how to link third-party DLLs written with Microsoft Visual C++ into Borland C++. Due to differences in how the compilers export functions, this can be a major problem when DLL suppliers don't follow the rules. In this technical note I will show you several ways to link to 3rd-party DLLs. I also provide some suggestions for DLL authors to avoid these problems. The reason why links to third-party DLLs fail is fully explained in the The Original Alias Method (below).

Here are some helpful links on the Borland web site:

  • http://www.borland.com/devsupport/bcppbuilder/ti/ (broken link)
  • http://www.borland.com/devsupport/bcppbuilder/faq/ (broken link)

These topics deal specifically with linking MSVC DLLs:

  • http://www.borland.com/devsupport/bcppbuilder/ti/TI2387C.html (broken link)
  • http://www.borland.com/devsupport/bcppbuilder/ti/TI2407C.html (broken link)

An Alias Solution (this works well with C++Builder)

This method is based on C++Builder FAQ 440 which they removed from their web site. I will discuss it in detail here.

The first step is to run IMPDEF on the DLL file to create a definition file. I usually create a directory to hold my new import library routines and copy the DLL files from the windows or system directory to this directory to work on them (otherwise you will need to fully specify file paths to get these commands to work or do all the work in your windows system directory, not a good idea). Once the DLL files are in the import library directory, open a console window to run the command line programs. Run IMPDEF on each DLL to create a DEF file:

IMPDEF FILE.DEF FILE.DLL

The next step is to edit the function definitions. Many DLLs can contain hundreds of functions. Editing the DEF files can be a major pain. I had several DEF files and several hundred functions to edit. To save some time I wrote a quick and dirty C++Builder program to perform these operations automatically. You can download this program by clicking on this line. The binary EXE and source code is included in this ZIP file. To use the program, follow these steps:

  1. Use the File/Open command to open a DEF file for processing. 
  2. Use the File/Fix command to change the functions to aliases. 
  3. Use the File/Save command to save the fixed file. 

We now need to go back to the console command line and use IMPLIB to convert the DEF files into import libraries:

IMPLIB FILE.LIB FILE.DEF

Add the LIB files to the project manager window of your program and you are done. You will need to reference the library include header files and set the proper path on the Options/Project directories tab to link to the functions. The DLL and DEF files can be deleted from the directory when you are happy with the results.

Dynamic Load-Time Linking (this always works but is difficult to implement)

In this method I use function pointers and dynamically load the DLL and set the pointers to the entries. This requires some mucking around with the DLL header files to convert all function definitions into pointers to functions. You then use the Win32 LoadLibrary and GetProcAddress calls to resolve all the function pointers. After this is done you can call the functions just like normal. You have the added benefit of being able to load the DLLs only as you need them. I used macros to define my functions in the header file and then modified these macros to generate the function pointers and fill them in with GetProcAddress. This made it easy to just copy the function definitions and modify the macro names for each of the operations. Some details follow.

Here is the function definition macro in the header file (used for Intel IPL library). I modified the function definition to change it to typedef of pointer to function and also added p_ to the front of the name:

// Disable the Intel format and use our own for dynamic linking.

#define IPLAPI(type,name,arg) typedef type __cdecl (*p_##name) arg;

You will need to edit all the function prototypes to look like this, making them pointers to the functions. Intel used the macro in their definitions so I only had to modify the macro definition as above.

IPLAPI(void, iplAllocateImage,(IplImage* image, int fillValue))

Next, we need to create a new unit to perform the DLL loading and pointer lookup at run-time. We will reference the original function prototype header file in our unit header file and define this macro to reference the pointers. You then need to use this macro for each function that you use:

#define EXTLINK(name) extern p_##name name

// Intel imports

EXTLINK(iplAllocateImage);

In the body of our DLL loading unit, we define the actual function pointers using another macro. The compiler will export these names automatically and make them available to our programs that use them:

#define FUNLINK(name) p_##name name

FUNLINK(iplAllocateImage);

In the OnCreate or OnShow event of your main program form you will need to load the DLL and make it available for use. You must perform the loading and function resolution before using any of the function pointers. This bit of code shows a LoadDLLs subroutine (placed in the body of our DLL loading unit) that does just that. Again, a macro is used to resolve the function pointers by looking them up by name in the DLL. I have chosen to terminate the application if anything goes wrong here. Make sure you don't try to call any of the functions in the OnCreate event of any form or they may point to NULL. If you need to call the functions to perform initialization you will need to verify they were all set properly. Once they have been loaded and resolved, you can use the pointers just like you would use the functions.

static HINSTANCE hIPL;

void TermMessage(const AnsiString s) {

  Application->MessageBox(s.c_str()

    ,"Application Fatal Error",MB_OK+MB_ICONSTOP);

  Application->Terminate();

}

#define RESLINK(h,name) \

  if (((name=(p_##name)GetProcAddress(h,#name)))==NULL) \

  TermMessage(#name)

void LoadDLLs() {

  if (((hIPL=LoadLibrary("IPL"))==NULL))

  TermMessage("Could not find IPL.DLL");

  RESLINK(hIPL,iplAllocateImage);

}

And don't forget to unload the library when your application terminates (call this in the OnDestroy or OnClose of your main form):

void UnloadDLLs() {

  if (hIPL!=NULL) FreeLibrary(hIPL);

}

The Original Alias Method (buggy with ILINK32)

Update 1/7/98: Just as soon as I added a second DLL library using this method the link program would not find entries into it. It appears that there is a bug in the linker and it won't recognize a second DLL in the IMPORTS definition file. This bug was reported to the Borland news groups and we will see if they fix it. This bug appears to be only in the incremental linker, ILINK32. The standard linker, TLINK32, appears to work.

Using a DLL supplied by a third party can be one of the most difficult procedures you may encounter while programming. While the ANSI C++ standard has made the features of the language standard, it appears they didn't want to go into specifications for the external object linking and executable systems. This area is open to interpretation and each compiler seems to process object and executable files differently. Because many of the DLL tools are written using Microsoft Visual C++, they can prove difficult to use with other language products such as Borland Delphi or C++ Builder. However, DLLs are a very desirable format for software vendors to provide their products in. LIB and OBJ files will use different formats for each compiler but a DLL can be interfaced by almost any compiler.

DLLs can be used in two ways: they can be statically linked or dynamically loaded. Static linking is the desired mode because Windows does much of the work of loading and making the DLL available to your program. The problem is that you must link an import LIB file with your program to perform the static linking. This LIB file may not be in the proper format for your compiler. If you can't get a proper LIB file for the DLL there are two directions to go.

You can always dynamically load a DLL and use pointers to get into the functions provided by it. This method is discussed in the Microsoft SDK and MSDN documentation so I won't go into details. Basically, you modify the header file to make the function definitions into typedefs of pointers to functions. You then make pointers to the functions using these typedefs, load the DLL with LoadLibrary, use GetProcAddress to get the addresses of the functions and then call them as you normally would. When your application terminates, you call FreeLibrary to unload the DLL. If the DLL was created with name mangling you may still find it difficult to find the function names with GetProcAddress. The IMPDEF tool provided by Borland can be used with any DLL to find out the names used in the DLL. We will show an example of using it later.

The other way of using a DLL is to link the LIB file and let Windows load it statically when your program runs. If the LIB file is provided in a format your compiler can read you stand a good chance of making this work. Even if a LIB file isn't provided, you can use the IMPLIB tool to make one for almost any DLL. The problems that you will encounter are then name mangling and differences in the way Microsoft and Borland treat external function names. If any function uses the __cdecl calling method you will not be able link into the functions. In this case, the only solution is to create a DEF file and use an alias for the function name to link it. This is not covered very well in any of the documentation and is the whole purpose of this discussion.

It can be expected that the name decoration and mangling will be different from different compiler suppliers because this is not explicitly covered in the standards. What I am talking about is the exported names created with the extern "C" specifier which shuts off all export name modifications. It appears that certain software suppliers don't know about the extern "C" specifier although they should by now. We will discuss some guidelines for creating portable DLLs in the following example.

I will now discuss the problem I had linking into one DLL. It took several days of searching the Microsoft MSDN and Borland technical notes to solve this problem. Hopefully, this will save you some time.

One of the best kept secrets of the Internet is the Intel Developer Support site. You will find a great set of DLL based libraries there for signal and image processing. The fact that they are optimized for MMX and can be used freely in commercial projects (please read the license agreement and make your own determination) makes the deal even better. I am doing some image processing work and obtained a copy of their Image Processing Library for use. It doesn't provide some of the more fancy functions available in other commercial libraries, but you can do quite a lot with what is provided. When I tried to link it into a Borland C++ Builder project I had started I couldn't link to any of the functions, even though Intel provided a "Borland compatible" link library. Here is a code snip of one of the functions in their library:

#ifdef __cplusplus

extern "C" {

#endif

#if !defined IPLAPI

#  if defined(IPL_W32DLL)

#   ifndef __BORLANDC__

#    define IPLAPI(type,name,arg) \

                         extern __declspec(dllexport) type __cdecl name arg;

#   else

#    define IPLAPI(type,name,arg)      extern type _import __cdecl name arg;

#   endif

#  else

#    define IPLAPI(type,name,arg)              extern type __cdecl name arg;

#  endif

#endif

IPLAPI(void, iplDeallocate,(IplImage* image, int flag))

#ifdef __cplusplus

}

#endif

In case you wonder, the IPLAPI macro is used to provide slightly different function definitions in the prototype header file. The first definition is used to create the DLL using MSVC. The second one appears to be used to generate the DLL with Borland C. The last definition is used to link to the DLL using both MSVC and Borland C. When I tried to link to this function using Borland C++ Builder I got this linker error:

[Linker Error] Unresolved external '_iplDeallocate' referenced from...

They make use of the extern "C" statement so name mangling is not the problem. I used IMPDEF to dump out the function name provided in the DLL (the DEF file will have all the names, I only show the one in question):

Command Line: 

IMPDEF IPL.DEF IPL.DLL

Output for iplDeallocate:

LIBRARY     IPL.DLL

EXPORTS

    iplDeallocate                  @30   

As you can see, the function dumped out and the prototype are the same. Another library I am using from National Instruments was linking without any problems. Here is a typical function and DEF dump of it:

extern i16 WINAPI AI_Check (

        i16        slot,

        i16        FAR * status,

        i16        FAR * value);

Windows defines WINAPI as follows:

#define WINAPI      __stdcall

IMPDEF NIDAQ32.DEF NIDAQ32.DLL

LIBRARY     NIDAQ32.DLL

EXPORTS

AI_Check                       @19  

The AI_Check function is defined and named the same and it works. So why doesn't the Intel function work? Well, the whole problem is caused by underscores and how Microsoft and Borland process them. When the extern "C" statement is used by Microsoft, they remove the underscore and all other name decoration from the function name. When this statement is used in the Borland compiler, the underscore is kept only for the __cdecl functions and the name decoration is dropped. You can link to any function provided it doesn't use the __cdecl calling convention.

The solution to this problem was quite simple once I found out how to do it. My program name for my image processing program is ImageWhiz. I created an empty text file and named it ImageWhiz.DEF. I entered the following statement into it and added it to the project manager window:

IMPORTS

_iplDeallocate = IPL.iplDeallocate

This was all that was needed to make the function work with Borland C++ Builder. This type of statement is called a function alias. It maps the name used by the Borland compiler, _iplDeallocate, to the name in the image processing library, IPL.DLL. The basic format is:

<Borland name> = <DLL file name>.<DLL export name>

You only need one IMPORTS line at the start of the DEF file. It can be followed by a line for each function you use in the DLL. If you don't call a certain DLL function you don't need to add it to the DEF file.

It is still necessary to use a LIB file in Borland format (actually Intel OBJ format, the format previously used by Microsoft in their products) to provide the link to the DLL file. You must place this LIB file in the project manager window to provide the DLL export names you need to alias. Supposing the DLL supplier doesn't provide a LIB file in Borland format, you can create one using these two commands:

IMPDEF NAME.DEF NAME.DLL

IMPLIB NAME.LIB NAME.DEF

The following links are broken. You will need to search the Borland web site for the original information.

More information on this subject can be found in the Borland technical note at this link:

http://www.borland.com/devsupport/borlandcpp/ti_list/TI3242.html

This technical note shows how to use a DLL with Borland C++ 5.0 but it doesn't discuss the __cdecl underscore problem. It also shows a slightly different form of the alias statement using the function ordinal number instead of the export name. The C++ Builder documentation says that ordinal numbers don't work with the linker they provide so this form may not work.

You will find their description for this fix in C++Builder FAQ 61.

Suggestions for Portable DLLs

Well, now that we've had a good look at the problem, here are a few suggestions for creating portable DLLs:

  1. Be sure you use the extern "C" statement in your function prototype header file. This turns off name mangling and makes it easier to link to your function names. 
  2. Add a unique string to the start of all your exported function names. Intel uses ipl on the front of all their function names in the IPL library. There is less of a chance that your function name will conflict with some other vendor if you do this. In the event that you must deal with a name conflict, one of the conflicting functions will need to be renamed in the prototype header file and accessed through an alias. Now that we know how to use an alias, this is rather easy. 
  3. Avoid using the __cdecl function calling method. Use __stdcall instead. The only reason you will need to use __cdecl is when the function has a variable number of parameters, such as the standard C function printf. The __cdecl method has another disadvantage. It generates more code in the program and will cause it to run slower. 
  4. The DLL should always export the names used for the functions. By creating a special DEF export file, it is possible to export only the ordinal numbers and not the names. This can make it very difficult for someone using a different tool set to use your DLL. 

Linking to DLLs with Delphi

Using third-party DLLs with Borland Delphi can be either a pain or pleasure. Usually, a Delphi import unit is not provided for the DLL and you must create your own. In this case you must start with the C++ function prototype header file or Visual Basic interface file if one is provided. I generally use the VB file if it is available because it is closer to the format needed by Delphi. All data structures and function definitions must then be converted to Object Pascal.

Delphi allows linking to external DLL functions using three methods. It is quite easy to map the DLL internal name to a Delphi name with these functions, or you can use the function ordinal numbers. The VB import file will usually provide the internal DLL function names if you don't have access to IMPDEF to get them. You can also use the Windows Explorer to get the names. Just right click on the DLL file and select QuickView.

Here is a short sample from Delphi's help file that shows how to interface DLLs:

const

 TestLib = 'testlib.dll';

 Ordinal = 5;

procedure ImportByName;    external TestLib;

procedure ImportByNewName; external TestLib name 'RealName';

procedure ImportByOrdinal; external TestLib index Ordinal;

There are additional keywords for dealing with the different calling conventions used in C based DLLs. Just have a look at the Borland RTL source files used to interface the Windows functions for a sample of how to link to standard Windows DLLs.

© James S. Gibbons 1987-2015