| |||||||||||||||||||||
|
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. Requirements
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(
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)
RTL ObjectsBeware 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 Ret.u.LowPart = Test64BitProc ( 123 ) ;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
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 | ||||||||||||||||||||