Jedi
Conversion Tutorial
Draft 4
Draft 4
27 JANUARY,
1998
by Andreas Prucha
by Andreas Prucha
Contents
1. Anatomy of a C Header
2. Conversion Basics
1. Naming
2. Unit dependencies
3. #defines as constants
1. Hexadecimal values
3. Datatypes
1. Basic Data Types
2. Windows API Common Types
3. Arrays
4. Strings
5. Enumerations
6. Structures, Records
1. Simple Structures
2. Unions in Structures
3. Packed Records
4. Macros
5. Conditionals
6. Functions
1. Basics
2. Calling Conventions
7. Linking
1. Static Linking
2. Dynamic Linking
8. Jedi Common Support Unit
Back to contents
## to do
Back to contents
Back to contents
The naming in converted header files should follow Borland's style as
far as possible. This means, keep the original names, but make them
more Delphi-like.
How should the C names be translated into Delphi?
The C-programmer usually uses upper case letters for type
identifiers, e.g. MY_TYPE. In Delphi, a type identifier has a
T-prefix followed by the name of the type in mixed (proper) case.
Underscores are not used. The Delphi-like translation of the C type
identifier MY_TYPE is TMyType.
In C older header files the pointer-type is named LPMY_TYPE. In
translation to Delphi it should be PMyType to conform with Borland's
style.
Constants are usually named identically to the original name,
including upper case letters and underscores.
A few examples:
C
|
Delphi-Translation
|
typedef
struct _IMAGE_FILE_HEADER {
WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; |
type
PImageFileHeader
= ^TImageFileHeader;
TImageFileHeader = packed record Machine: Word; NumberOfSections: Word; TimeDateStamp: DWORD; PointerToSymbolTable: DWORD; NumberOfSymbols: DWORD; SizeOfOptionalHeader: Word; Characteristics: Word; end; |
#define LANG_NEUTRAL 0x00 #define LANG_AFRIKAANS 0x36 #define LANG_ALBANIAN 0x1c #define LANG_ARABIC 0x01 #define LANG_BASQUE 0x2d #define LANG_BELARUSIAN 0x23 #define LANG_BULGARIAN 0x02 #define LANG_CATALAN 0x03 #define LANG_CHINESE 0x04 |
CONST
LANG_NEUTRAL = $00; LANG_AFRIKAANS = $36; LANG_ALBANIAN = $1c; LANG_ARABIC = $01; LANG_BASQUE = $2d; LANG_BELARUSIAN = $23; LANG_BULGARIAN = $02; LANG_CATALAN = $03; LANG_CHINESE = $04; |
Back to contents
Back to contents
C and C++ use #include to include header files in another
header file or a source file. Delphi refers to units (in the USES
clause) instead of header files.
For example
D3D.H includes D3DTYPES.H, D3DCAPS.H
D3DTYPES.H includes DDRAW.H
One unit contains one translation of a header file, so incorporation
of D3DTYPES.H and D3DCAPS.H is required when translating D3D.H.
Thus the translation of D3D.H (D3D.PAS) needs D3DTYPES.PAS and
D3DCAPS.PAS in uses.
In Windows.pas Borland has already done a lot of work for us.
Borland's windows unit contains most of the basic Windows datatypes,
so any translated unit needing Windows datatypes needs the Windows
unit in its uses clause.
Note: In Delphi 1.0, substitute WinProcs.pas and WinTypes.pas for
Windows.pas, since these two units include for Delphi 16-bit what
Delphi 32-bit defines in Windows.pas. In Delphi 2 these two files
are also aliased to Windows.pas in Delphi 32-bit versions for
backward compatibility.
Some header files contain source which is not directly translatable
into Pascal, but the header file's impact on compilation must be
incorporated into the translated code. For example, PSHPACK?.H
contains compiler directives that are not directly translatable to
Delphi; PSHPACK4.H directs the compiler to use 4-byte packing.
More about this later.
Back to contents
Back to contents
C and C++ use #defines in several ways. In a C header file #define
can be used
-
for declaring a constant
-
for declaring a symbol for conditional compilation
-
for macros
This chapter describes the translation of #define into Delphi
constants.
The format for declaring constants in C is:
#define
NameOfConstant Value
For example:
#define
TIME_ZONE_ID_UNKNOWN 0
#define TIME_ZONE_ID_STANDARD 1
#define TIME_ZONE_ID_DAYLIGHT 2
#define TIME_ZONE_ID_STANDARD 1
#define TIME_ZONE_ID_DAYLIGHT 2
The translation into Delphi is:
CONST
TIME_ZONE_ID_UNKNOWN
= 0;
TIME_ZONE_ID_STANDARD
= 1;
TIME_ZONE_ID_DAYLIGHT
= 2;
Back to contents
2.3.1. Hexadecimal values
Back to contents
C uses the prefix 0x to specify a hexadecimal value. For example, the
C declaration
#define
MY_CONSTANT 0xFF
translates to Delphi as
CONST
MY_CONSTANT
= $FF;
Back to contents
Back to contents
Here is a list of C data types and the equivalents in Delphi:
C
|
Delphi
|
UNSIGNED LONG
|
DWORD (Longint)
|
UNSIGNED SHORT
|
WORD
|
INT
|
Integer
|
UNSIGNED CHAR
|
Byte, Char
(Depending on usage)
|
Back to contents
Back to contents
The Windows API defines some common types for API usage. It is
recommended that the same names be used in translations as far as
possible. Windows.pas declares most of these types, some of which are
listed below:
API Type
declaration
|
Type used in
Delphi Translation
|
Type
Specification
|
ULONG
|
ULong
|
DWord
|
PULONG
|
PULong
|
^DWord
|
USHORT
|
UShort
|
SmallInt
|
PUSHORT
|
PUShort
|
^SmallInt
|
UCHAR
|
UChar
|
Byte
|
PUCHAR
|
PUChar
|
^Byte
|
DWORD
|
DWord
|
DWord
|
PDWORD, LPDWORD
|
PDWord
|
^DWord
|
BOOL
|
Bool
|
Bool
|
PBOOL, LPBOOL
|
PBool
|
^Bool
|
BYTE
|
Byte
|
Byte
|
PBYTE, LPBYTE
|
PByte
|
^Byte
|
WORD
|
Word
|
Word
|
PWORD, LPWORD
|
Pword
|
^Word
|
INT
|
Integer
|
Integer
|
PINT, LPINT
|
PInteger
|
^Integer
|
LPVOID
|
Pointer
|
Untyped Pointer
|
UINT
|
UInt
|
Integer
|
PUINT, LPUINT
|
PUInt
|
^Integer
|
WCHAR
|
WChar
|
WideChar
|
PWCHAR, LPWCHAR,
PCWCH, LPCWCH, NWPSTR,
|
PWChar
|
^WideChar
|
PWSTR, LPWSTR
|
LPWStr
|
^WideChar
|
PCWSTR, LPCWSTR
|
LPCWStr
|
^WideChar
|
PCH, LPCH
|
PChar
|
^Char
|
PSTR, LPSTR
|
LPStr
|
^Char
|
PCSTR, LPCSTR
|
LPCStr
|
^Char
|
HANDLE
|
THandle
|
DWord
|
PHANDLE, LPHANDLE
|
PHandle
|
^DWord
|
Back to contents
Back to contents
Method 1:
In C the first index of an array (or vector) is 0 and the
declaration specifies the number of elements (not the last
index), e.g.
DWORD
MyType[4]
Delphi declares arrays as the range of elements, so the same
declaration would be
MyType
= Array [0..3] of DWORD;
Method 2:
Alternatively, the number of elements may be specified by a constant,
e.g
#define
NumberOfElements 5
//...
typedef struct _MyRec {
DWORD MyArray[NumberOfElements]
} MyRec, *PMyRec
//...
typedef struct _MyRec {
DWORD MyArray[NumberOfElements]
} MyRec, *PMyRec
The same declaration in Delphi is
Const
NumberOfElements
= 5
//...
Type
TmyArray
= Record
MyArray
: Array [0..NumberOfElements-1] of DWORD;
end;
Remember, the range of the array is
0..NumberOfElements-1, but not 0..NumberOfElements.
Back to contents
Back to contents
In C, as in Delphi, a string is an array of char types. Often, a
string declaration is used in combination with a constant declaration
specifying the maximum string length, as the following example shows:
#define
RAS_MaxEntryName 256
#define RASENTRYNAMEA struct tagRASENTRYNAMEA
RASENTRYNAMEA
{
DWORD dwSize;
CHAR szEntryName[ RAS_MaxEntryName + 1 ];
};
#define RASENTRYNAMEA struct tagRASENTRYNAMEA
RASENTRYNAMEA
{
DWORD dwSize;
CHAR szEntryName[ RAS_MaxEntryName + 1 ];
};
The first line declares a constant RAS_MaxEntryName with the value
256 specifying the maximum length of the string. The lines after it
declare a struct (record) which contains a null-terminated string:
CHAR
szEntryName[ RAS_MaxEntryName + 1 ];
The Delphi translation:
szEntryName
: Array [0..RAS_MaxEntryName] of Char
Why not Array [0..RAS_MaxEntryName+1] of Char? Recall that a C
array starts with 0 and the declaration specifies the number of
elements. Thus, Array
[0..RAS_MaxEntryName+1] in Delphi is equivalent to
[RAS_MaxEntryName+2]
in C.
Back to contents
Back to contents
Enumerated types can be translated in two ways.
Method 1:
typedef
enum _FINDEX_INFO_LEVELS {
FindExInfoStandard,
FindExInfoMaxInfoLevel
} FINDEX_INFO_LEVELS;
FindExInfoStandard,
FindExInfoMaxInfoLevel
} FINDEX_INFO_LEVELS;
This part of a C-header file translates easily to Delphi:
Type
TFindExInfoLevels
= (FindExInfoStandard,
FindExInfoMaxInfoLevel);
The ordinal Value of the item FindExInfoStandard is 0. In Delphi each
enumeration starts with 0.
Method 2:
The following C-declaration is more problematic:
typedef
enum _ACL_INFORMATION_CLASS {
AclRevisionInformation = 1,
AclSizeInformation
} ACL_INFORMATION_CLASS;
AclRevisionInformation = 1,
AclSizeInformation
} ACL_INFORMATION_CLASS;
The "= 1" in the declaration forces C to start with the
ordinal value 1. This is not possible in Delphi.
There are two ways to solve the problem.
There are two ways to solve the problem.
Solution a): Declare the enumeration as
TACLInformationClass
= (_Fill0,
AclRevisionInformation,
AclSizeInformation);
Solution b): Translate the enumeration as
CONST
AclRevisionInformation
= 1;
AclSizeInformation
= 2;
TYPE
TACLInformationClass
= DWORD;
That's no problem for this example. But in C it's possible to specify
the ordinal value of each item of the enumeration, for example,
Typedef
enum _ENUMEXAMPLE {
Item1 = 5,
Item2 = 10,
} ENUMEXAMPLE;
Item1 = 5,
Item2 = 10,
} ENUMEXAMPLE;
Using enumeration in Delphi the declaration would have to be done
like this:
TEnumExample
= (_Fill0,
_Fill1,
_Fill2,
_Fill3,
_Fill4,
Item1,
_Fill6,
_Fill7,
_Fill8,
_Fill9,
Item2);
This is hard to read and clumsy to maintain.
This works better:
CONST
Item1
= 5;
Item2
= 10;
TYPE
TEnumExample
= DWORD;
Back to contents
Here is a real-world example from the winnt.h, which is not possible
to translate to a native enumerated type:
//
//
Start Type
//
#define
SERVICE_BOOT_START 0x00000000
#define
SERVICE_SYSTEM_START 0x00000001
#define
SERVICE_AUTO_START 0x00000002
#define
SERVICE_DEMAND_START 0x00000003
#define
SERVICE_DISABLED 0x00000004
//
//
//
typedef
enum _CM_SERVICE_LOAD_TYPE {
BootLoad
= SERVICE_BOOT_START,
SystemLoad
= SERVICE_SYSTEM_START,
AutoLoad
= SERVICE_AUTO_START,
DemandLoad
= SERVICE_DEMAND_START,
DisableLoad
= SERVICE_DISABLED
}
SERVICE_LOAD_TYPE;
The ordinal values of the items in the enumeration SERVICE_LOAD_TYPE
depend on the constants declared above it. This is not possible in
Delphi. The only way to translate it is:
//
//
Start Type
//
CONST
SERVICE_BOOT_START
= $00000000;
SERVICE_SYSTEM_START
= $00000001;
SERVICE_AUTO_START
= $00000002;
SERVICE_DEMAND_START
= $00000003;
SERVICE_DISABLED
= $00000004;
//
//
//
CONST
BootLoad
= SERVICE_BOOT_START;
SystemLoad
= SERVICE_SYSTEM_START;
AutoLoad
= SERVICE_AUTO_START;
DemandLoad
= SERVICE_DEMAND_START;
DisableLoad
= SERVICE_DISABLED;
TYPE
TServiceLoadType
= DWORD;
Back to contents
Back to contents
C structures are similar to records in Delphi. Structures are usually
defined with the typedef
keyword, but it's also possible to do it with #define.
The format of a structure declaration is
*** Andreas, Alan says the structure name is
missing from this example ***
{Struct|Union} [OptIdentifier] [TagName]
{ FieldDefinitions [; ...]}
[Name [...]]
{ FieldDefinitions [; ...]}
[Name [...]]
You can ignore the TagName. It's used in C for subsequent references
to the structure.
This is how the fields within a structure are defined:
#define
RASENTRYNAMEA struct tagRASENTRYNAMEA
RASENTRYNAMEA
{
DWORD
dwSize;
CHAR
szEntryName[ RAS_MaxEntryName + 1 ];
};
This C declaration defines a record (structure) named RASENTRYNAMEA.
The Delphi-style name would be TRASENTRYNAMEA. This structure
contains two fields: the first is named dwSize and has the
type DWORD. The second field is an array of char with
RAS_MaxEntryName + 1 elements.
The Delphi translation:
TYPE
PRASEntryName
= ^TRASEntryName
TRASEntryName
= Record
dwSize
: DWORD;
szEntryName
: Array [0..RAS_MaxEntryName] of Char
end
Remember, you may not declare the array of char with a range
from 0 to RAS_MaxEntryName+1. The reason is, that in C the number
of elements is specified, but in Delphi the range of elements.
Read more in the chapters about Arrays
and Strings.
Back to contents
C unions in structures are comparable to variant parts of records in
Delphi. Blocks declared in a union structure are not consecutive but
overlaid.
typedef
struct _PROCESS_HEAP_ENTRY {
PVOID
lpData;
DWORD
cbData;
BYTE
cbOverhead;
BYTE
iRegionIndex;
WORD
wFlags;
union
{
struct
{
HANDLE
hMem;
DWORD
dwReserved[ 3 ];
}
Block;
struct
{
DWORD
dwCommittedSize;
DWORD
dwUnCommittedSize;
LPVOID
lpFirstBlock;
LPVOID
lpLastBlock;
}
Region;
};
}
PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY, *PPROCESS_HEAP_ENTRY;
This is the translation into Delphi:
type
PProcessHeapEntry
= ^TProcessHeapEntry;
TProcessHeapEntry
= Record
lpData:
Pointer;
cbData:
DWORD;
cbOverhead:
Byte;
iRegionIndex:
Byte;
wFlags:
Word;
case
Integer of
0:
(Block: Record
hMem:
Thandle
Reserved
: Array [0..2] of DWORD;
end);
1:
(Region: Record
dwCommittedSize:
DWORD;
dwUnCommittedSize:
DWORD;
lpFirstBlock:
Pointer;
lpLastBlock:
Pointer
end);
end;
Case Integer of starts the variant part of the record. Each
variant is identified by an ordinal value. This value has no meaning
when the type is being used, but is required for declaration.
Please note the difference between a variant (case-) record and a
record without case-declaration.
The following translation is wrong:
type
PProcessHeapEntry
= ^TProcessHeapEntry;
TProcessHeapEntry
= Record
lpData:
Pointer;
cbData:
DWORD;
cbOverhead:
Byte;
iRegionIndex:
Byte;
wFlags:
Word;
Block:
Record
hMem:
Thandle
Reserved
: Array [0..2] of DWORD;
end;
Region:
Record
dwCommittedSize:
DWORD;
dwUnCommittedSize:
DWORD;
lpFirstBlock:
Pointer;
lpLastBlock:
Pointer
end);
end;
This wrong translation would cause Block and Region to be consecutive
in memory, and not overlaid, as follows:
LpData,
cbData, cbOverhead, iRegionIndex, wFlags
|
HMem,
dwReserved
|
DwCommittedSize,
dwUnCommittedSize, lpFirstBlock, lplastBlock
|
Back to contents
If a record is packed (aligned) then the starting byte of a field
(and consequently also of the record) is aligned with the first byte
of a word (16-bit or 2-byte align) or of a DWord (32-bit or 4-byte
alignment). This significantly speeds CPU access to the fields, but
consumes more memory.
The older APIs (from the 16-bit world) usually use packed structures
(one or two byte alignment). It is the default in 16-bit Delphi:
there is no difference under Delphi 1 if you write record or
packed record.
Four-byte alignment is the default under Delphi32, although some APIs
do use packed records under Win32.
It's important to know which type of alignment is used in an API,
because the size of a record depends on the alignment.
The following example explains the difference between the coding of
2-byte and 4-byte alignment:
TmyRecNotPacked
= Record
FieldA
: Word;
FieldB:
LongInt;
FieldC:
Byte;
End;
TmyRecPacked
= packed Record
FieldA
: Word;
FieldB:
LongInt;
FieldC:
Byte;
End;
Offset
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
10
|
11
|
TMyRecUnPacked
|
A
|
A
|
.
|
.
|
B
|
B
|
B
|
B
|
C
|
.
|
.
|
.
|
.
|
TMyRecPacked
|
A
|
A
|
B
|
B
|
B
|
B
|
C
|
Now, how to determine whether packing is required or not. In the
Microsoft APIs, two methods are used. One is clear and simple:
#pragma pack(n)
Use the packed keyword if n is 1 or 2, but don't use
packed if n is 4.
But Microsoft uses #include to control packing by
putting the packing pragma in the include file. The following include
files are used to control the packing method:
#include
|
Means
|
Packed required
|
pshpack1.h
|
#pragma pack (1)
|
Yes
|
pshpack2.h
|
#pragma pack (2)
|
Yes
|
pshpack3.h
|
#pragma pack (3)
|
Yes
|
pshpack4.h
|
#pragma pack (4)
|
No
|
Poppack.h
|
Back to previous
packing
|
?
|
If a header file contains the following line
#include
you can interpret it as #pragma pack (1) and use the packed keywords
in records from now until you come to another include of pshpack?.h
or to an include of poppack.h.
Four-byte alignment (non-packed records) is the default under
Delphi32. But what if the user changes the compiler options and
switches off 4-byte alignment? If you need to ensure that 4-byte
alignment is enabled you must insert the following line at the top of
the unit:
{$ALIGN
ON}
A unit header must look at least like this:
Unit
MyUnit;
{$ALIGN
ON}
You can use the{$ALIGN} directive instead of explicitly using the
packed keyword, too, but I prefer explicit packing because it
overrides {$ALIGN ON} and makes things quite specific.
NOTE: A field whose length is an odd number of bytes presents
a problem if you encounter #pragma pack(2) and use the packed
keyword for translating it. The field will be close packed on a byte
boundary when it should be packed on a word boundary. A solution is
being sought for handling this situation.
Back to contents
Back to contents
In C it's possible to define macros. Macros are not available in
Delphi, so functions must be used to translate C-macros. In
most cases it's easier to translate a macro based on the information
about it from the documentation than trying to translate the code
directly.
One example of a macro:
#define
PRIMARYLANGID(lgid) ((WORD )(lgid) & 0x3ff)
Here, quite clearly, the macro accepts integers. A valid translation
of this macro is
Function
PRIMARYLANGID (lgid: DWORD): DWORD;
Begin
Result
:= lgid and $3FF
End;
The following macro accepts any datatype as parameter:
#define
max(a,b) (((a) > (b)) ? (a) : (b))
It compares the values of parameter a with parameter b and returns
the higher value. So that our function can pass any datatype, we can
use variants.
Function
Max (A, B: Variant): Variant;
Begin
If
A > B then
Result
:= A
else
Result
:= B;
End;
I do recommend implementing the Delphi translation on the basis of
the macro's documentation.
Back to contents
###ToDo
Back to contents
Let's use the following C-declaration as an example of declaring a
function:
WINADVAPI
BOOL
WINAPI
ControlService(
SC_HANDLE
hService,
DWORD
dwControl,
LPSERVICE_STATUS
lpServiceStatus
);
[Options] ReturnValueType [Options] FunctionName
( [ParameterList] )
ReturnValueType:
|
Specifies the type
of the return value of the function. In the example above the type
of the return value is BOOL. If the function does not
return a value the keyword VOID is used and the translation
in Delphi is a Procedure. Please note, that the type
identifier can also be "hidden" in an identifier defined
using #define.
|
Options:
|
Options can be a token specifying the calling convention and/or
other keywords telling the compiler how to handle the function in
question. The most important item is the calling convention. In
the Windows-API header files, the calling convention is usually
"hidden" in an identifier declared using #define,
so you have to look what is defined. The example above uses the
WINAPI symbol which is declared as __stdcall. This
is necessary for the translation, too.
Every Options item must be assessed for impact and discarded or
implemented.
|
FunctionName:
|
Specifies the name
of the function
|
ParameterList:
|
List of parameters
passed to the function separated with ",".
Parameters are declared via a type identifier or via a type
identifier + parameter name combination.
|
Back to contents
Referring to the example:
WINADVAPI
BOOL
WINAPI
ControlService(
SC_HANDLE
hService,
DWORD
dwControl,
LPSERVICE_STATUS
lpServiceStatus
);
The type of the return value is BOOL. Now let's have a look at
the definition of WINAPI. WINAPI is defined in windef.h
the following way:
#define
WINAPI __stdcall
The __stdcall keyword tells the C-compiler to use the
standard-call calling convention for the function, so we have to
declare the function using stdcall, too, since the default calling
convention is Register in Delphi.
The function accepts three parameters. The first is a parameter with
type SC_HANDLE,
the second is a DWORD
and the third is a pointer to a PServiceStatus
structure.
Here is a Delphi translation of the example above:
Function
ControlService (hService: SC_Handle;
dwControl:
DWORD;
lpServiceStatus:
PServiceStatus): Bool; stdcall;
Notes:
1. SC_Handle is defined in WinSvc.pas.
2. The C variable hService appears to conflict somewhat with the
Delphi convention that employs H as the initial letter for a handle
type. Whilst it seems desirable to use an alternative
(hndService:HControlService, for example), Delphi will accept
duplicate names in a parameter list (e.g. hwnd: HWND). Since Borland
does it and it works, I would recommend staying with that convention.
Let's have a look at another function declaration:
ULONG
(FAR PASCAL MAPISENDDOCUMENTS)(
ULONG
ulUIParam,
LPSTR
lpszDelimChar,
LPSTR
lpszFilePaths,
LPSTR
lpszFileNames,
ULONG
ulReserved
);
This declaration contains a trap. The keyword PASCAL has been
used to specify the Pascal-calling convention, which was usually used
in the 16bit-Windows. But not under Win32. Look at the following line
in the windef.h file:
#define
PASCAL __stdcall
The windef.h header declares PASCAL as __stdcall, which specifes the
stdcall calling convention, so you have to use stdcall
calling convention in this case, too.
Notes
-
It is very important to trace through all the #include files for any #defines which may affect the translation.
-
The FAR keyword can be ignored in Delphi.
Back to contents
There are two ways to link a DLL and import a function. Static
linking is very easy and the recommended way if the DLL is certain to
be available on the client's machine. If the DLL is optional it is
better to use dynamic linking (runtime-linking) to make sure that the
application does not fail just because of a missing (possibly
unimportant) DLL.
Static linking encodes the calling of the library DLL into the code
as a direct call with no checking for inability to connect to the
library. Dynamic linking calls the library DLL during the
application's run-time and has the capability to handle problems with
connection to the library. Dynamic linking can occur at startup, or
during the program run-time as the library is needed by the user.
Borland usually uses static linking, but a few API translations use
dynamic linking. One example is the translation of SMAPI (MAPI.pas).
Let's look at the two ways of linking a DLL and importing a function.
If using static linking, simply declare the function prototype in the
interface section and the implementation section the following way:
INTERFACE
{Function|Procedure} FunctionName
[FunctionDeclaration;]
IMPLEMENTATION
{Function|Procedure} FunctionName external
DLLName [name 'FunctionExportName'];
The translation of a function prototype was in the previous section.
What about importing the function? It's not obligatory to include the
parameter part of the function in the implementation section, but you
can if you want. As an example to explain how to import a function,
let's use the OpenEvent function from the kernel32.dll.
There are two implementations of the function, one with
unicode-support (16bit-WideChar) and one with ansi-character (8bit
char) support.
The C-declaration is
WINBASEAPI
HANDLE
WINAPI
OpenEventA(
DWORD
dwDesiredAccess,
BOOL
bInheritHandle,
LPCSTR
lpName
);
WINBASEAPI
HANDLE
WINAPI
OpenEventW(
DWORD
dwDesiredAccess,
BOOL
bInheritHandle,
LPCWSTR
lpName
);
#ifdef
UNICODE
#define
OpenEvent OpenEventW
#else
#define
OpenEvent OpenEventA
#endif
// !UNICODE
Three function names are declared: OpenEventA for the
ansi-character version of the function, OpenEventW for the
wide-character (unicode) version, and OpenEvent. OpenEvent
uses the widechar version of the function (OpenEventW ) if the
UNICODE symbol is declared or the ansichar-version OpenEventA
if the UNICODE symbol is not declared.
INTERFACE
{...}
function
OpenEventA (dwDesiredAccess: DWORD;
bInheritHandle:
BOOL;
lpName:
PAnsiChar): THandle; stdcall;
function
OpenEventW (dwDesiredAccess: DWORD;
bInheritHandle:
BOOL;
lpName:
PWideChar): THandle; stdcall;
{$IFDEF
UNICODE}
function
OpenEvent (dwDesiredAccess: DWORD;
bInheritHandle:
BOOL;
lpName:
PWideChar): THandle; stdcall;
{$ELSE}
function
OpenEvent (dwDesiredAccess: DWORD;
bInheritHandle:
BOOL;
lpName:
PChar): THandle; stdcall;
{$ENDIF}
{...}
IMPLEMENTATION
Const
kernel32
= 'kernel32.dll';
{...}
function
OpenEventA; external kernel32 name 'OpenEventA';
function
OpenEventW; external kernel32 name 'OpenEventW';
{$IFDEF
UNICODE}
function
OpenEvent; external kernel32 name 'OpenEventW';
{$ELSE}
function
OpenEvent; external kernel32 name 'OpenEventA';
{$ENDIF}
{...}
Back to contents
Back to contents
Dynamic Linking is used to link the DLL at runtime.
Handling Static and Dynamic Linking in the Jedi Environment
Because both static and dynamic linking have their strengths, we must
be prepared to support both techniques in the Jedi units. However,
static linking is the default.
To support multiple methods compiler symbols are used. The two
compiler symbols that symbolize dynamic linking are as follows:
If Xxxx_DYNLINK is defined the DLL must be linked dynamically
at startup (in the initialization section).
If Xxxx_LINKONREQUEST is defined the DLL is linked
dynamically, not at startup, but as needed by the user. Xxxx
is the name of the API, e.g. MAPI_DYNLINK or
MAPI_LINKONREQUEST.
If neither symbol is defined, static linking is used.
Each import unit should implement the following functions:
Function
XxxxInitAPI: Boolean;
Procedure XxxxFreeAPI;
Function XxxxCheckAPI: Boolean;
Procedure XxxxFreeAPI;
Function XxxxCheckAPI: Boolean;
The functions XxxxInitAPI and XxxxFreeAPI are available for the user
if the symbol Xxxx_LINKONREQUEST is defined. The User can call this
function to load or free the DLL. XxxxInitAPI returns TRUE, if the
DLL has been loaded successfully.
If Xxxx_DYNLINK is defined, but not Xxxx_LINKONREQUEST, these
functions are used internally to load or unload the DLL in the
initialization section, but they are not available for the user.
The function XxxxCheckAPI returnes TRUE if the API is available, so
the return value is TRUE if the DLL has previously been loaded
successfully using XxxxInitAPI. When static linking is used, the
return value is always TRUE.
Here is an example of how to handle the symbols in a translation:
unit
apisample;
interface
USES
Windows,
JediUtil;
//
There are two conditional defines. One to use dynamic
//
run time linking with linking on startup, the other
//
to link upon request. In fact the only difference is
//
that the library is not loaded in the initialization
//
section if the upon-request symbol is defined.
//
Both symbols start with the name of the api, e.g. TAPI.
//
In this example it is APISAMPLE.
//
APISAMPLE_DYNLINK signals that the API should be linked
//
via LoadLibrary and GetProcAddress at startup.
//
APISAMPLE_LINKONREQUEST signals that the libary should
//
not be linked at startup, but via an initialization procedure.
//
Since most of the stuff is the same for both we use
//
APISAMPLE_DYNLINK as common symbol.
{$IFDEF
APISAMPLE_LINKONREQUEST}
{$DEFINE
APISAMPLE_DYNLINK}
{$ENDIF}
{$IFDEF
APISAMPLE_DYNLINK}
//
Define function types and variables for dynamic linking
TYPE
TApiSampleFunc1
= Function (lParam: LongInt): LongInt; stdcall;
TApiSampleFunc2
= Function (wParam: Word): LongInt; stdcall;
VAR
ApiSampleFunc1
: TApiSampleFunc1 = NIL;
ApiSampleFunc2
: TApiSampleFunc2 = NIL;
{$ELSE}
//
We don't use dynamic linking so we implement static linking
Function
ApiSampleFunc1 (lParam: LongInt): LongInt; stdcall;
Function
ApiSampleFunc2 (wParam: Word): LongInt; stdcall;
{$ENDIF}
//
//
Linking Control functions
//
//
The XxxxInitAPI function follows InitAPI
//
naming convention and is only visible if
//
Xxxxx_LINKONREQUEST is defined. The same is true
//
for the XxxxxFreeAPI function which frees the
//
library
{$IFDEF
APISAMPLE_LINKONREQUEST}
Function
ApiSampleInitAPI: Boolean;
Procedure
ApiSampleFreeAPI;
{$ENDIF}
//
The XxxxxCheckAPI function returns true if
//
the API is available. With static linking
//
the function always returns TRUE
Function
ApiSampleCheckAPI: Boolean;
implementation
CONST
APISampleDLL = 'APISAMPLE.DLL'; // Name of the DLL
{$IFDEF
APISAMPLE_DYNLINK}
VAR
hDLL : THandle = 0; // Handle to the lib. Only req. for
dyn.link.
Function
ApiSampleInitAPI: Boolean;
begin
Result
:= FALSE;
//
Load library if necessary
If
hDLL = 0 then hDLL := LoadLibrary (APISampleDLL);
If
JediCheckInstanceHandle (hDLL) then
begin
//
Set pointers to functions
@ApiSampleFunc1
:= GetProcAddress (hDLL, 'ApiSampleFunc1');
@ApiSampleFunc2
:= GetProcAddress (hDLL, 'ApiSampleFunc2');
//
Everything ok, return true
Result
:= TRUE;
end
end;
Procedure
ApiSampleFreeAPI;
begin
If
hDLL <> 0 then
FreeLibrary
(hDLL);
hDLL
:= 0;
end;
{$ELSE}
Function
ApiSampleFunc1; external APISampleDLL name 'ApiSampleFunc1';
Function
ApiSampleFunc2; external APISampleDLL name 'ApiSampleFunc1';
{$ENDIF}
Function
ApiSampleCheckAPI: Boolean;
begin
{$IFDEF
APISAMPLE_DYNLINK}
Result
:= hDLL <> 0;
{$ELSE}
Result
:= TRUE;
{$ENDIF}
end;
initialization
begin
{$IFDEF
APISAMPLE_DYNLINK}
{$IFNDEF
APISAMPLE_LINKONREQUEST}
//
Call Init if dynamic linking and not link on request
ApiSampleInitAPI
{$ENDIF}
{$ENDIF}
end;
finalization
begin
{$IFDEF
APISAMPLE_DYNLINK}
ApiSampleInitAPI;
// Call free if dynamic linking
{$ENDIF}
end;
end.
You can use the the JediCheckInstanceHandle function from the common
Jedi-support unit to check an instance handle if necessary.
Back to contents
unit
JediUtil;
{==========================================================}
{
Jedi Common Support Unit }
{==========================================================}
interface
USES
WinTypes,
WinProcs;
{----------------------------------------------------------}
{
Function JediCheckInstanceHandle }
{
}
{
Parameter: }
{
}
{
hInst: Instance Handle }
{
}
{
Returns-Value: }
{
TRUE if the handle is valid, FALSE if not }
{----------------------------------------------------------}
Function
JediCheckInstanceHandle (hInst: THandle): Boolean;
implementation
Function
JediCheckInstanceHandle (hInst: THandle): Boolean;
begin
{$IFNDEF
WIN32}
Result
:= hInst > HINSTANCE_ERROR;
{$ELSE}
Result
:= hInst <> 0;
{$ENDIF}
end;
end.