Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics
 Using C DLLs with Delphi (and HeadConv v4.20)
See Also: Delphi Papers and Columns

This article shows a tool (HeadConv v4.20) and techniques that can will assist you and enable you to use DLLs written in foreign languages like C or C++, from within your Delphi applications.

I'm sure you must have experienced it at least once in the past few months. You've just started to work with Delphi, but wouldn't it be nice to re-use some of your old C/C++ code. Or wouldn't it be nice to use that new DLL or API that has just been released, unfortunately with a C header file. Well, we can use all this old C code, provided we put it in DLLs, and that also means we can use these foreign DLLs, provided we know what's in there. A C header file might not be much, but it's often all we have. And although you might not be able to translate it by hand, this article will show you ways to (attempt to) translate language C DLL header files to Delphi import units, thereby making it possible for your Delphi apps to call procedures and functions from this DLL!

DLLs
Dynamic Link Libraries are - for Windows - the ultimate form of reusable code. With a DLL, you can create a single library of procedures/functions or resources that can be used by an unlimited number of applications. DLLs can be called by any Windows program (or DLL), provided you know how to ask for the right stuff inside the DLL - you need an import library or import unit to access the internals.

Delphi Import Units
Using a C DLL in Delphi is most often done by writing an import Unit. All declarations of the exported routines as well as all types and constants used or returned by these routines go into the Interface part of the Unit, the Implementation part only consists of a list of external statements for the exported routines. In fact all Delphi Units referring to the Windows API (such as WinProcs, LZExpand, ShellAPI) are examples of such import units.
First of all, what is a Delphi import unit, and how do we make one?
You simply put the import units name into your projects uses clause and the DLL functions are available as if they are defined in some other Unit of your project.
There are two types of import units: implicit (static) and explicit (dynamic). It often seems that an implicit import unit is simpler to write by yourself. However, the main advantage of dynamically loading DLLs is that your application can be loaded and run without those DLLs being present, provided that it can adapt to their absence. For example, if you had implemented a 3D look in a DLL of your own and couldn't find it (loading it dynamically) then you could still run the app with the "flat" look. If the DLL in question had been statically linked, Windows would have refused to run the app (claiming that your 3D.DLL could not be found), so the user would have been out of luck.
Another advantage of explicit import units is that the app will initially load faster because the dynamically loaded DLLs are loaded afterwards on an as needed basis.

Implicit Linking
Implicit linking happens when you Use an import unit for the DLL or declare a DLL function in your code with external 'DLLNAME'. In this case the linker will place references to the DLL functions into the imported name table of the produced EXE file and the Windows loader will automatically load the DLLs (at which time their main block is executed) with the EXE file:

 procedure Foo(X: Integer); external 'BAR' index 1;
If during an implicit link (also called "static link") Windows cannot find the DLL, you will get a notification - most often a File Error MessageBox (one that does not indicate which file is missing, by the way - most helpful!). So you must be absolutely sure that the DLL will be available at all times when you want to use implicit import units.

Explicit Linking
Dynamically linking a DLL using an explicit import unit is the method VB uses and which you can also use, but "by hand". You load the DLL when you need it with LoadLibrary, obtain function pointers to the exported functions you want to use with GetProcAddress and free the DLL again with FreeLibrary when you no longer need it:

 var
   Hbar: Thandle;
   Foo: procedure (X: Integer); {$IFDEF WIN32} stdcall; {$ENDIF}
 begin
   Hbar := LoadLibrary('BAR.DLL');
   if Hbar >= 32 then { success }
   begin
     Foo := GetProcAddress(HBar, 'FOO');
     ...
     Foo(1);
     ...
     FreeLibrary(HBar);
   end
   else
     MessageDlg('Error: could not find BAR.DLL', mtError, [mbOk], 0)
 end.
Note that when we are using an explicit link (also called "dynamic link"), we can detect whether or not the LoadLibrary failed, and whether or not the DLL is actually loaded. If the DLL is not loaded, the program can continue (and just disable the functions or options that needed the DLL in the first place). This is also one way to write components that need DLLs, so your Delphi Component Library or Package will still load if the DLL is not present.

C DLL header files
C does not have import units like Delphi, but an optional static import library (with accompanying header and definition file). From the C DLL we can generate a static import .LIB file with IMPLIB, and the .DEF file with IMPDEF (these tools are part of any C/C++ developing environment). It is the header file that is the most interesting part, since this defines which functions and which parameters are to be linked in. Most C DLLs will therefore ship with the .DLL and .H files only (we can regenerate the rest).
To illustrate the task of header conversion, let's check out a sample C DLL header file, and do the conversion by hand. As an example that everybody will have access to, I choose the WING.H header file from the 16-bit WING.DLL that Mircosoft provides to developers:

 /*********************************************************************\
 *
 * wing.h - WinG functions, types, and definitions
 *
 *          Copyright (c) 1994 Microsoft Corp. All rights reserved.
 *
 \********************************************************************/
 
 #ifndef _INC_WING
 #define _INC_WING

 #ifndef _INC_WINDOWS
 #include     /* Include windows.h if not already included */
 #endif

 #ifdef __cplusplus
 extern "C" {            /* Assume C declarations for C++ */
 #endif

 #if defined(WIN32) || defined(_WIN32)
 #define WINGAPI WINAPI
 #else
 #define WINGAPI WINAPI _loadds
 #endif

 /***** WingDC and WinGBitmap ****************************************/

 HDC WINGAPI WinGCreateDC( void );

 BOOL WINGAPI WinGRecommendDIBFormat( BITMAPINFO FAR *pFormat );

 HBITMAP WINGAPI WinGCreateBitmap( HDC WinGDC, BITMAPINFO const FAR *pHeader,
         void FAR *FAR *ppBits );

 void FAR *WINGAPI WinGGetDIBPointer( HBITMAP WinGBitmap,
         BITMAPINFO FAR *pHeader );

 UINT WINGAPI WinGGetDIBColorTable( HDC WinGDC, UINT StartIndex,
         UINT NumberOfEntries, RGBQUAD FAR *pColors );

 UINT WINGAPI WinGSetDIBColorTable( HDC WinGDC, UINT StartIndex,
         UINT NumberOfEntries, RGBQUAD const FAR *pColors );

 /***** Halftoning ***************************************************/

 HPALETTE WINGAPI WinGCreateHalftonePalette( void );

 typedef enum WING_DITHER_TYPE
 {
     WING_DISPERSED_4x4,
     WING_DISPERSED_8x8,
     WING_CLUSTERED_4x4
 } WING_DITHER_TYPE;

 HBRUSH WINGAPI WinGCreateHalftoneBrush( HDC Context, COLORREF crColor,
         WING_DITHER_TYPE DitherType );

 /***** Blts *********************************************************/

 BOOL WINGAPI WinGBitBlt( HDC hdcDest, int nXOriginDest,
         int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc,
         int nXOriginSrc, int nYOriginSrc );

 BOOL WINGAPI WinGStretchBlt( HDC hdcDest, int nXOriginDest,
         int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc,
         int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc );

 #ifdef __cplusplus
 }                       /* End of extern "C" */
 #endif

 #endif // _INC_WING
We can conceptually split the DLL in three parts: the first part contains a lot of compiler directives, the second part contains the actual function prototypes, and the third part contains some ending compiler directives (to keep the first part in balance). Let's not worry about the compiler directives for now, but let's focus on the most important part: the function prototypes in the second part of the header file.

Type Conversion
When converting C function prototypes to Pascal equivalent declarations, it's important to substitute the C function and argument types with the correct Pascal types. I often use a Type Translation Table for this, like the following in table 1 (optional parts on the C-side are noted between square brackets):

C/C++ TypeObjectPascal Type
unsigned short [int]Word
[signed] short [int]SmallInt
unsigned [int]Cardinal { 3.25 fix }
[signed] intInteger
UINTLongInt { or Cardinal }
WORDWord
DWORDLongInt { or Cardinal }
unsigned longLongInt { or Cardinal }
unsigned long intLongInt { or Cardinal }
[signed] longLongInt
[signed] long intLongInt
charChar
signed charShortInt
unsigned charByte
char*PChar
LPSTR or PSTRPChar
LPWSTR or PWSTRPWideChar { 3.12 fix }
void*Pointer
BOOLBool
floatSingle
doubleDouble
long doubleExtended
LP,NP,PP,P prefix: if first = T then T becomes P else P prefix
HANDLETHandle
FARPROCTFarProc
ATOMTAtom
TPOINTTPoint
TRECTTRect
COLORREFTColorRef
OFSTRUCTTOFStruct
DEBUGHOOKINFOTDebugHookInfo
BITMAPTBitMap
RGBTRIPLETRGBTriple
RGBQUADTRGBQuad
BITMAPCOREHEADERTBitmapCoreHeader
BITMAPINFOHEADERTBitmapInfoHeader
BITMAPINFOTBitmapInfo
BITMAPCOREINFOTBitmapCoreInfo
BITMAPFILEHEADERTBitmapFileHeader
HANDLETABLETHandleTable
METARECORDTMetaRecord
METAHEADERTMetaHeader
METAFILEPICTTMetaFilePict
TEXTMETRICTTextMetric
NEWTEXTMETRICTNewTextMetric
LOGBRUSHTLogBrush
LOGPENTLogPen
PATTERNTPattern { TLogBrush }
PALETTEENTRYTPaletteEntry
LOGPALETTETLogPalette
LOGFONTTLogFont
ENUMLOGFONTTEnumLogFont
PANOSETPanose
KERNINGPAIRTKerningPair
OUTLINETEXTMETRICTOutlineTextMetric
FIXEDTFixed
MAT2TMat2
GLYPHMETRICSTGlyphMetrics
POINTFXTPointFX
TTPOLYCURVETTTPolyCurve
TTPOLYGONHEADERTPolygonHeader
ABCTABC
RASTERIZER_STATUSTRasterizer_Status
MOUSEHOOKSTRUCTTMouseHookStruct
CBTACTIVATESTRUCTTCBTActivateStruct
HARDWAREHOOKSTRUCTTHardwareHookStruct
EVENTMSGTEventMsg
WNDCLASSTWndClass
MSGTMsg
MINMAXINFOTMinMaxInfo
SEGINFOTSegInfo
ACCELTAccel
PAINTSTRUCTTPaintStruct
CREATESTRUCTTCreateStruct
CBT_CREATEWNDTCBT_CreateWnd
MEASUREITEMSTRUCTTMeasureItemStruct
DRAWITEMSTRUCTTDrawItemStruct
DELETEITEMSTRUCTTDeleteItemStruct
COMPAREITEMSTRUCTTCompareItemStruct
WINDOWPOSTWindowPos
WINDOWPLACEMENTTWindowPlacement
NCCALCSIZE_PARAMSTNCCalcSize_Params
SIZETSize
MENUITEMTEMPLATEHEADERTMenuItemTemplateHeader
MENUITEMTEMPLATETMenuItemTemplate
DCBTDCB
COMSTATTComStat
MDICREATESTRUCTTMDICreateStruct
CLIENTCREATESTRUCTTClientCreateStruct
MULTIKEYHELPTMultiKeyHelp
HELPWININFOTHelpWinInfo
CTLSTYLETCtlStyle
CTLtypeTCtltype
CTLINFOTCtlInfo
DDEADVISETDDEAdvise
DDEDATATDDEData
DDEPOKETDDEPoke
DDEAACKTDDEAck
DEVMODETDevMode
KANJISTRUCTTKanjiStruct

It's also nice to have access to the translation of the special WINDOWS.H types, which is already done by Borland, and can be found in WINTYPES.PAS (it's a rewarding experience to compare these two files with each other - you'll learn a lot about C and Pascal conversion issues).
Now that we've handled the standard built-in (and Windows) types, let's look at the C type definition of WING_DITHER_TYPE that is present in the WING.H file.

 typedef enum WING_DITHER_TYPE
 {
     WING_DISPERSED_4x4,
     WING_DISPERSED_8x8,
     WING_CLUSTERED_4x4
 } WING_DITHER_TYPE;
This is a so -called enumerated type, and can be translated into an ObjectPascal enumerated type very easily:
 type
   WING_DITHER_TYPE =
    (WING_DISPERSED_4x4,
     WING_DISPERSED_8x8,
     WING_CLUSTERED_4x4);
While most C DLL header files contain only constant definitions (of the form #define XYZ value) and function prototypes, type definitions are also found, and are therefore important to be able to convert.

C Functions
Now that we've seen C to Pascal type conversion, we can start to convert a few of the functions that are defined in the C header file, like the WinGCreateDC function:

 HDC WINGAPI WinGCreateDC( void );
Clearly, it is a function and not a procedure, since it has a non-void return type. The function has no arguments, and hence the implicit ObjectPascal equivalent declaration is as follows:
 function WinGCreateDC: HDC; {$IFDEF WIN32} stdcall; {$ENDIF}
          external 'WING' index 1001;
The index number can be obtained from the .DEF file, by the way. If you do not have access to the .DEF file, and IMPDEF is not available to generate one from the .DLL, then you can just omit the index part. The function will still link implicitly, but will load somewhat slower (since the loader will have to search for it by name instead of by number).
The equivalent explicit declaration is as follows:
 var WinGCreateDC: function: HDC; {$IFDEF WIN32} stdcall; {$ENDIF}
Where we have to make sure to do a GetProcAddress of WinGCreateDC after the LoadLibrary was successful (see also the earlier explicit linking example).
Other WinG functions that do have arguments can be translated accordingly, for example the WinGRecommendedDIBFormat, WinGCreateBitmap and WinGCreateHalftoneBrush. Note that the last one actually uses a translated custom type WING_DITHER_TYPE that we generated in the C to Pascal Type Conversion section.
 function WinGRecommendDIBFormat(pFormat: PBitmapInfo): Bool; {$IFDEF WIN32} stdcall; {$ENDIF}
          external 'WING' index 1002;

 function WinGCreateBitmap(WinGDC: HDC;
                           Const pHeader: PBitmapInfo;
                           ppBits: PPointer): HBITMAP; {$IFDEF WIN32} stdcall; {$ENDIF}
          external 'WING' index 1003;

 function WinGCreateHalftoneBrush(Context: HDC;
                                  crColor: TColorRef;
                                  DitherType: WING_DITHER_TYPE): HBRUSH; {$IFDEF WIN32} stdcall; {$ENDIF}
          external 'WING' index 1008;
Please also note that these last three function conversions are for use in an implicit import unit, not an explicit one.

Compiler Directives
As I've said, compiler directives are the last and often most difficult task to convert. It's often important to keep in mind what the target of the converted unit will be - win16 or win32 for example, so you can eliminate a lot of conversions will you're scanning compiler directives (you won't need to convert everything in the #ifdef WIN32 section if you are working on a 16-bit import unit only, of course.

Tool Support - HeadConv v4.20
So far, we have seen that part of the conversion process is easy, part of it is hard, and a lot of it is simple 'monkey' work, that is neither complex nor particularly rewarding to do. In order to support myself in the monkey-work, I've written a little program to help me:
HeadConv (latest version is v4.20). Targeted at the serious Delphi developer, the HeadConv Expert will assist in the task of converting the C DLL header files to Delphi import units, thereby giving all Delphi users access to the huge world of third-party C DLLs.
The latest version of HeadConv v4.20 is available as command-line and Wizard edition:

HeadConv Main Page

As an example, the converted WING.H header file is used. The new file WING.PAS did only require two additional little changes in the source code (at two compiler directives) and instantly recompiled. Please note that with other (more complex) header files, some extra modifications might be necessary to get the same results.

HeadConv Type Page

HeadConv has full support for functions and procedures, argument and return types (it has a Type Translation Table of up to 128 custom type conversions - see figure 2) and generates implicit Delphi import units. The expert is integrated in the Delphi IDE, making the conversion very easy as the converted file is opened up in the IDE automatically. HeadConv is not targeted to do the conversion 100% on its own, rather it will assist in converting the C DLL header files!

Ever since the Delphi Jedi Project started, the use of HeadConv has been free for everyone! It will soon be made available as Open Source project (which also means all bug reports, comments and wishes for new features should be sent to that project, and no longer to me).


This webpage © 2000-2017 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.