OnAStickSoftware
About OAS
OAS Playout
OAS Audio API
Other Software
Technical Articles
Dragon Pages

Mix 'n Match DLLs - Visual 'C' & Borland 'C'

This article is a series of notes which may be useful to anyone wishing to link code built using two different development environments - in this case between Visual Studio (V6) and Borland C++ (v5 - the old version NOT Builder) although I imagine many of the techniques can be applied to newer and in fact different compilers.

Much of Playout is developed (still) under BC5 because it still serves my needs and I prefer it over the VC environment. However I have had to often link in 3rd party code which is either pre-built or has projects in the VC format so it has been necessary to cross this hurdle without porting the whole lot to VC (and lose some of the features that only BC offers).


Static Code Linkage

Compilers (and assemblers) untimately convert source into object files (.obj), these are then linked together by the linker to form the final executable. Often 3rd party packages create static libraries - which are simply a collection of .obj files which may be linked with your application. The first hurdle here is that the format of the object files differs between compilers - VC produces them in COFF format whilst BC writes them in OMF. The linkers for either environment will fail then because they cannot read the files (and by inference any static library produced by the 'other' toolset). This first problem is actually not to difficult to solve - you can obtain tools to convert between either format (COFF2OMF for example and I think versions of Borland Builder include this tool as standard).

The story doesn't end here however because this simply serves to highlight another problem - that of the underlying run time library (RTL). Unless you are converting code which doesn't make use of any of the 'C' (or langugage equivalent) RTL (calls such as 'printf', 'scanf' etc.) odds are the application still will not link (or if it does will crash in use) because of the differences between the implementation of these. The RTL is part of the development environment, created independently by each vendor so 'printf' for example under Borland will be implemented differently to VC. This includes naming conventions and the underlying data structures used by each RTL. For this reason then, it's really not worth trying to statically link code together in this manner. Fortunatly there is an alternative - using DLLs. Static libraries can easily be converted to DLLs, simply by putting them in a DLL project and exporting the necessary functions with a module definition (.def) file.


DLL Linkage

The easiest way to link code build by different toolsets is to build the code into a DLL and have the other compiler load it (either statically or dynamically). Here I'll discuss linking VC build DLLs with Borland EXEs (because this is probably more common) but will also touch on the other way around later on.

Calling Convention

If you have access to the sources of the code you wish to use, its worth declaring all the functions to be exported to use the WINAPI (or __stdcall) convention. This is the Windows 'standard' and ensures code built under any toolset will be callable from most Windows applications (this includes things like Visual Basic [VB] used under MS-Office etc.). If you've ever built GPL or Open Source software from the likes of Sourceforge you'll find many of the builds include VC projects which create DLLs and explicitly export the functions (normally with a #define like DLLEXPORT or similar) in this format for Windows platforms:

__declspec(dllexport) WINAPI void FuncA ( int A, int B ) ;


Static (DLL) Linkage

When the DLL is built, accompanying it will be an import library. This is then included in the project which will use the DLL (top level .EXE) to inform the linker where the unresolved symbols are located. However as with static libraries VC and BC write their import libraries in differing formats making them unreadable by the other toolset. As before, it is possible to convert the library however it is more straightforward with BC to simply create the library from the DLL using the IMPLIB tool.

This is a command line tool with the syntax:


IMPLIB <name of lib> <name of dll>

so is invoked:

IMPLIB mydll.lib mydll.dll

This creates the import library (mydll.lib) from the DLL (mydll.dll) which was built under VC. Include this library into the BC project and all should be well. Make sure though that the declarations for these functions (normally in header files) as seen by the Borland compiler also has the WINAPI declaration in front of them.


'C' Calling Convention

Code not explicitly exported as WINAPI will default to the usual 'C' calling convention. Also, if you need to export functions with variable argument lists, the 'C' convention needs to be used as WINAPI does not support this.

Historically, 'C' calls internally have an underscore prefixed to each function name (presumably to differ them from say Pascal symbols with the same name). Thus:

void FuncA ( int A ) ;

is exported as:

_FuncA

BC adopts this approach however VC does not add the underscore.

This then causes a problem because the BC linker will be looking for a function named (_FuncA) in the import library whereas the library will only have (FuncA) listed in it. So the link fails.

There are two solutions to this - the first is to adopt dynamic linking using the LoadLibrary/GetProcAddress approach. Here you can supply the VC exported function name in the parameter passed to GetProcAddress.

The second approach is to drop the import library and use a little known feature of module definition files - the IMPORTS directive. Oddly, VC has dropped support for this which is strange because it is a useful feature.

The IMPORTS directive allows you to explicitly import functions from DLLs using the following syntax:

<internal name> = <dllname>.<dllfunc>

Thus you can include lines as follows:

_FuncA = mydll.FuncA

This effectivaly maps 'FuncA' to '_FuncA'. Remember, you don't need an import library (.lib) for this approach.

This approach can also be used to gain access to new Win32 API calls that were not available when the toolset was released. For example, the API call 'GetDiskFreeSpaceEx' is not available in the earlier releases of BC5. However you can still call it if you know the syntax and library to use:


From MSDN - GetDiskFreeSpaceEx

Requirements

Client Requires Windows Vista, Windows XP, or Windows 2000 Professional.
Server Requires Windows Server 2008, Windows Server 2003, or Windows 2000 Server.
Header

Declared in WinBase.h; include Windows.h.

Library

Use Kernel32.lib.

DLL

Requires Kernel32.dll.

Unicode

Implemented as GetDiskFreeSpaceExW (Unicode) and GetDiskFreeSpaceExA (ANSI).


The key aspects here are the DLL it's implemented in (kernel32) and the "implemented as" line. So in the DEF file:

GetDiskFreeSpaceEx = kernel32.GetDiskFreeSpaceExA


and in the 'C' code:

BOOL WINAPI GetDiskFreeSpaceEx(
LPCTSTR lpDirectoryName,
PULARGE_INTEGER lpFreeBytesAvailable,
PULARGE_INTEGER lpTotalNumberOfBytes,
PULARGE_INTEGER lpTotalNumberOfFreeBytes
);

Makes this call available.

Parameter Passing

In general there shouldn't be any issues with this, either using WINAPI or 'C' calling convention however here are a few things to be aware of.

Structures

Don't pass structures by value, the convention used by each toolset differs so it simply doesn't work. It's not very efficient anyway so always pass by reference.

By default VC aligns structure members on a quadword (8 byte boundary), BC packs to 1 byte boundaries. You can either adopt to change the alignment for either compiler or alter it for specific code modules which use these structures. For example, to force BC to temporarily use 8 byte alignment:

#include <pshpack8.h>    // force quadword struct alignments (VC default)
#include <windows.h>
#include "metadata.h"
#include <poppack.h>     // restore packing
RTL Objects

Beware when using Run Time Library generated objects between DLLs - for example the stdio FILE* object.  Consider the following:

DLL Code:

__declspec(dllexport) FILE *FuncA ( char *Filename )
{
  return fopen ( Filename, "rb" ) ;
}

Exe Code:

FILE *Fi ;

Fi = FuncA ( "myfile.txt" ) ;
fread ( Buf, Count, 1, Fi ) ;
fclose ( Fi ) ;

This will almost certainly crash and stems back to the discussions on the RTL earlier. In this instance, the handle created by 'fopen' in the DLL is valid ONLY in the context of the RTL built into the DLL. When referred to by the top level EXE, it will attempt to use the handle in the context of the RTL built against the EXE and will fail. One of the clever things I *do* like about VC is, this problem can be avoided by linking against a common RTL library (the infamous MSVCRT.DLL) all the RTL calls (and associated data) are then managed, processwide in this DLL. Of course this only works when linking (appropriately built) VC built executables and DLLs.

You can of course provide DLL functions to perform the read/write calls (wrappers around fread & fclose in this case). The alternative is to use Windows objects (a HANDLE returned from CreateFile), these will be global across the entire process.

A similar issue arises with memory blocks allocated by 'malloc' (or one of the other 'allocs). You can have the DLL malloc the memory and have it pass back the pointer to the executable which may then read/write freely from it but it must be free'd up by a call to 'free' inside the DLL NOT the top level executable.


64-bit Integers

64 bit integers are only available as a base type starting with Borland C v5.02 (underlying type __int64) however there are some workarounds for the earlier versions discussed later. Parameter passing of this type though appears to work without issue however there is an incompatibility on function returns for 64 bit ints. VC returns the value in the EAX:EDX register pair whilst BC allocates space on the stack for the return. This is a bit tricky to handle, the simplest approach I've found is as follows:

1. Declare the function inside BC as returning a 32 bit value
2. Retrieve the upper word from the EDX register.

Thus:

// really a 64 bit return
unsigned long Test64BitProc ( int Val ) ;

void Proc ( void )
{
LARGE_INTEGER Ret ;
  Ret.u.LowPart = Test64BitProc ( 123 ) ;
Ret.u.HighPart = _EDX ;
  printf ( "%08x\n", Ret.u.LowPart ) ;
  printf ( "%08x\n", Ret.u.HighPart ) ;
}
This example populates the Win32 64 bit structure type 'Ret' with the return of the function 'Test64BitProc'. Its a bit risky since you cant assume that EDX won't be altered by the compiler between the two lines of 'C'. Trial and error....

BC versions without 64 bit integers

BC v5.02 introduced new underlying base types __int16, __int32 and notably __int64 in line with VC 6. Earlier versions did not have these declarations and you may find software with BC compiler switches which assumes you have these. For the lower sizes, it is fairly easy to create typedefs to match these ie. __int32 = int, __int16 = short etc. The 64 bit type though is a little bit more difficult. I've found making use of the Win32 LARGE_INTEGER type works quite well - for example when using the FLAC dll, I had to alter a header file to read:

#ifdef  __BORLANDC__  // jrb: for Borland v5.01 w/out 64 bit ints
typedef short FLAC__int16;
typedef int FLAC__int32;
typedef LARGE_INTEGER FLAC__int64;
typedef unsigned short FLAC__uint16;
typedef unsigned int FLAC__uint32;
typedef LARGE_INTEGER FLAC__uint64;
This seems to work in most instances and supports passing by value as well as by reference. You can even handle the returning of 64 bit values using the code example given earlier.

Processing the 64 bit numbers themselves may prove a little more tricky and depending on the requirements you can either invest in a dedicated set of 64 bit integer math routines, adopt the brute force approach (convert to/from doubles) or upgrade to a compiler which supports them properly.


Calling BC built DLLs from VC

A final word on 'going the other way' (as it were)....

Calling Conventions

The usual rules discussed earlier apply (but in reverse obviously). For static linking you'll need to create a VC compatible import library. The easiest way to achieve this is to use the included tool LIB.EXE to convert a module definition file (listing the exports) to an import library. If you have access to BC itself, you can use the IMPDEF tool to create the definition file (if one doesn't exist):

impdef <name of def> <name of dll>

so:

impdef mydll.def mydll.dll
lib /def:mydll.def


This will give you the import library mydll.lib which can be included in the top level VC project. Failing that, construct the def file manually using information from the VC dependency viewer or DUMPBIN.

For some reason, BC functions exported using the WINAPI calling convention still don't link properly under VC (despite them working quite happily from VB & MS-Access) I remain unsure why this should be the case. Functions exported using the 'C' convention do link after you deal with the naming convention issue. This can be addressed in two ways, the first is to simply append the leading underscore to the functions in the code - such that:

 a = FuncA ( 123 ) ;

becomes:

 a = _FuncA ( 123 ) ;

The 2nd approach requires you have access to the source and BC toolset. Once again it makes use of the flexibility of module definition files, this time when exporting functions. It is possible to perform a similar operation to that described earlier on importing functions - that of mapping function names.

This approach requires you do not export your functions using the __declspec directive but instead declare them under an EXPORTS statement in the DEF file. Here you then perform 2 exports of the same function:

EXPORTS
 _FuncA
 FuncA = _FuncA


This generates 2 names both pointing to the same function. The first satisfies linkage with BC applications and the second with VC apps. This is how I produce the OAS Audio core library which remains compatible with both toolsets.

Oddly enough you can't adopt this approach when building a DLL under VC, the resultant DLL always ignores one of the names and even more odder this only happens when you are attempting to create names with leading underscores so:

EXPORTS
 FuncA
  _FuncA = FuncA


will just export a single name (of '_FuncA') however:

EXPORTS
 FuncA
 FuncB = FuncA

will give two names - 'FuncA' and 'FuncB' both pointing at FuncA.

I think its to do with this. And therefore by design....

jrb, Dec '07

 

©2019 OnAStickSoftware, Comments to: webmaster@onasticksoftware.co.uk