Pitfalls of converting
Rudy Velthuis
Rudy Velthuis
[SHOWTOGROUPS=4,20]
Notes
или Зарегистрируйся on the JEDI site. You can also start on the Для просмотра ссылки Войди или Зарегистрируйся page. This article assumes you have sufficient knowledge of the Delphi language, and some (reading) knowledge of C and/or C++. You should also have read the documents on conversion, formerly on the JEDI pages, and the Для просмотра ссылки Войди или Зарегистрируйся, if you want your conversion to be compliant with Borland or JEDI standards (this is not required though).
I have found Для просмотра ссылки Войдиили Зарегистрируйся for the translation of C and C++ (not only headers) to Delphi. I didn’t read it in full, so I don’t know how accurate it is. But it seems to discuss every construct.
In this article I often use the word “function”, since that is how they are called in C and C++. In Delphi, you have both functions and procedures, but you might say that procedures are just a special case of functions, without a return value. So if I write “function”, I mean both “function” and “procedure”. Sometimes I use the word “routine” as well, with the same meaning.
When the conversion must also be usable in C++Builder, you must take care not to forget to use the {$EXTERNALSYM SomeSymbol} and perhaps the {$NODEFINE SomeSymbol} directives where appropriate. More on this later on. In the sources displayed in the article, I purposely omitted most of them, because they greatly decrease the signal-to-noise ratio.
C and C++ use a lot of pointers, in structures, in parameters, etc. If you have problems understanding or using pointers, you should perhaps read my article "Addressing pointers".
To help me with some of the most annoying tasks of header conversion, I wrote a simple IDE expert for myself. This is also available for Для просмотра ссылки Войдиили Зарегистрируйся.
Not for .NET
This article is (currently) entirely meant for the Win32 (and Win64) versions of Delphi. The principles explained here don’t fully apply to the Microsoft .NET versions, using PInvoke, although some parts do. People using .NET might be interested in using the .NET translations of Win32 functions on Для просмотра ссылки Войдиили Зарегистрируйся. Since this site produces C# translations, this can be combined (if you want, since you can also use the C# functions directly) with Embarcadero’s Для просмотра ссылки Войди или Зарегистрируйся webservice.
Calling conventions
To call a function, you must often pass parameters to it. There are plenty ways how this can be done. In old BASIC programs, you would just load some global variables with values and use these in your subroutine. Fortunately, things have become a bit more sophisticated, and we know two major ways of doing it (on Windows). You either pass the parameters on the calling stack (the place the processor also places the temporary pointer to the code following the call, so it knows where to continue after the call was done), or you pass some of them in registers. Floating point values can also be passed on the stack of the coprocessor.
Calling conventions rule how parameters will be passed (stack only or registers), in which order they will be passed (from left to right, i.e. in the same order as they appear in source code, or the other way around), and which code will clean the stack after use, if necessary. There are a lot of possible combinations, but Delphi currently knows 5 (or well, 6, if you count winapi) of them:
There are a few ways to try to determine the calling convention. First, C/C++ either uses __cdecl (by default), but can be instructed to use __stdcall or __pascal, or even __fastcall (register). The latter is however not completely compatible with Delphi’s register (different registers are being used). To make things even more confusing, C++ Builder also has a __fastcall, which is compatible with Delphi, but also knows the __msfastcall convention, which is compatible with Microsoft’s __fastcall. We may see this in a newer version of Delphi too. But if you see these keywords, you know which convention is being used.
Note: the usual calling convention for Windows DLLs is stdcall. Most of the Windows APIs use this convention. In Linux, there is no stdcall, and the default is cdecl there.
Macros
But the convention can be hidden in a macro (often an all capital word, like PASCAL). These are often #defined in another header file, to which you many not even have access. If you do, follow all includes until you have found one of the keywords mentioned above (i.e. _pascal or __stdcall, don’t mind the number of underscores, that’s a C compiler thing). Be aware that the values of the macros can be different for different platforms, so make sure the type you found is the definition for Win32, and not for the Mac or the Alpha.
One case that is really annoying is FAR PASCAL. You might expect this to be translated to far; pascal; but this is far from the truth. In 32-bit Windows, PASCAL seems to be #defined as __stdcall, and FAR as an empty macro (far is something that was necessary for segmented 16-bit DOS/Windows 3.x code, not for Windows 32-bit code). So to find out the correct calling convention may require some detective work. A good searching tool, like grep, can help you with this.
The most common macros are these:
Other macros are either defined in terms of these macros, or directly as __stdcall, __cdecl, etc.
A trick
If you can’t find the place where the macro was defined, there is one trick left. Normally, under 32-bit Windows, you can expect stdcall. However, some headers still use cdecl. To find out which one it is, you should do a test. Declare only one of the functions (but one with parameters) in the header as stdcall. Now call this function in code, and put a breakpoint on it. Debug the code, and as soon as the breakpoint is reached, open the CPU window of the IDE. Note the value of the ESP register. Now step over the function with F8, and note the ESP value again. If it was changed, you were right, and it was a stdcall function (the function cleaned the stack, changing the stack pointer as an effect). If it didn’t change, it was a cdecl function. If you are familiar with assembler, you might even directly see the cleaning of the stack (something like ADD ESP,xxx).
Win64
In 64 bit Windows (for AMD or Intel), there is only one public calling convention. It is described Для просмотра ссылки Войдиили Зарегистрируйся. DLLs will and should not use anything else. Delphi can use and produce it.
If you want to share your translations between Win32 and Win64, there is no need to remove declared calling conventions like stdcall or cdecl. The Win64 compiler will simply ignore them.
[/SHOWTOGROUPS]
This article is meant for everyone who needs to translate C/C++ headers to Delphi. I want to share some of the pitfalls you can encounter when converting from C or C++. This article is not a tutorial, just a discussion of frequently encountered problem cases. It is meant for the beginner as well as for the more experienced translator of C and C++.Translation is the art of failure. — Umberto Eco
Notes
This is not a tutorial on header conversion. You can find a Для просмотра ссылки ВойдиI never use notes, they interfere with me. — Ken Blanchard
I have found Для просмотра ссылки Войди
In this article I often use the word “function”, since that is how they are called in C and C++. In Delphi, you have both functions and procedures, but you might say that procedures are just a special case of functions, without a return value. So if I write “function”, I mean both “function” and “procedure”. Sometimes I use the word “routine” as well, with the same meaning.
When the conversion must also be usable in C++Builder, you must take care not to forget to use the {$EXTERNALSYM SomeSymbol} and perhaps the {$NODEFINE SomeSymbol} directives where appropriate. More on this later on. In the sources displayed in the article, I purposely omitted most of them, because they greatly decrease the signal-to-noise ratio.
C and C++ use a lot of pointers, in structures, in parameters, etc. If you have problems understanding or using pointers, you should perhaps read my article "Addressing pointers".
To help me with some of the most annoying tasks of header conversion, I wrote a simple IDE expert for myself. This is also available for Для просмотра ссылки Войди
Not for .NET
This article is (currently) entirely meant for the Win32 (and Win64) versions of Delphi. The principles explained here don’t fully apply to the Microsoft .NET versions, using PInvoke, although some parts do. People using .NET might be interested in using the .NET translations of Win32 functions on Для просмотра ссылки Войди
Calling conventions
Probably the easiest, and most often made mistake is using the wrong calling convention. Now you may say: “My code is unconventional, so I don’t need them anyway”, but it’s not that simple. So what is a calling convention?True art selects and paraphrases, but seldom gives a verbatim translation. — Thomas Bailey Aldrich
To call a function, you must often pass parameters to it. There are plenty ways how this can be done. In old BASIC programs, you would just load some global variables with values and use these in your subroutine. Fortunately, things have become a bit more sophisticated, and we know two major ways of doing it (on Windows). You either pass the parameters on the calling stack (the place the processor also places the temporary pointer to the code following the call, so it knows where to continue after the call was done), or you pass some of them in registers. Floating point values can also be passed on the stack of the coprocessor.
Calling conventions rule how parameters will be passed (stack only or registers), in which order they will be passed (from left to right, i.e. in the same order as they appear in source code, or the other way around), and which code will clean the stack after use, if necessary. There are a lot of possible combinations, but Delphi currently knows 5 (or well, 6, if you count winapi) of them:
- pascal, the original but mostly obsolete calling convention for old Pascal programs;
- register, the current default calling convention in Delphi (__fastcall in C++Builder);
- cdecl, the standard calling convention for C and C++ code;
- stdcall, the default cross-language calling convention on 32-bit Windows;
- winapi, an undocumented but supported alias for stdcall; and
- safecall, a special case of stdcall, which can be ignored for now.
There are a few ways to try to determine the calling convention. First, C/C++ either uses __cdecl (by default), but can be instructed to use __stdcall or __pascal, or even __fastcall (register). The latter is however not completely compatible with Delphi’s register (different registers are being used). To make things even more confusing, C++ Builder also has a __fastcall, which is compatible with Delphi, but also knows the __msfastcall convention, which is compatible with Microsoft’s __fastcall. We may see this in a newer version of Delphi too. But if you see these keywords, you know which convention is being used.
Note: the usual calling convention for Windows DLLs is stdcall. Most of the Windows APIs use this convention. In Linux, there is no stdcall, and the default is cdecl there.
Macros
But the convention can be hidden in a macro (often an all capital word, like PASCAL). These are often #defined in another header file, to which you many not even have access. If you do, follow all includes until you have found one of the keywords mentioned above (i.e. _pascal or __stdcall, don’t mind the number of underscores, that’s a C compiler thing). Be aware that the values of the macros can be different for different platforms, so make sure the type you found is the definition for Win32, and not for the Mac or the Alpha.
One case that is really annoying is FAR PASCAL. You might expect this to be translated to far; pascal; but this is far from the truth. In 32-bit Windows, PASCAL seems to be #defined as __stdcall, and FAR as an empty macro (far is something that was necessary for segmented 16-bit DOS/Windows 3.x code, not for Windows 32-bit code). So to find out the correct calling convention may require some detective work. A good searching tool, like grep, can help you with this.
The most common macros are these:
Macro | C++ definition | Delphi definition |
---|---|---|
CALLBACK | __stdcall | stdcall |
WINAPI | __stdcall | stdcall |
WINAPIV | __cdecl | cdecl |
APIENTRY | WINAPI | stdcall |
APIPRIVATE | __stdcall | stdcall |
PASCAL | __stdcall | stdcall |
FAR | (nothing) | (nothing) |
A trick
If you can’t find the place where the macro was defined, there is one trick left. Normally, under 32-bit Windows, you can expect stdcall. However, some headers still use cdecl. To find out which one it is, you should do a test. Declare only one of the functions (but one with parameters) in the header as stdcall. Now call this function in code, and put a breakpoint on it. Debug the code, and as soon as the breakpoint is reached, open the CPU window of the IDE. Note the value of the ESP register. Now step over the function with F8, and note the ESP value again. If it was changed, you were right, and it was a stdcall function (the function cleaned the stack, changing the stack pointer as an effect). If it didn’t change, it was a cdecl function. If you are familiar with assembler, you might even directly see the cleaning of the stack (something like ADD ESP,xxx).
Win64
In 64 bit Windows (for AMD or Intel), there is only one public calling convention. It is described Для просмотра ссылки Войди
If you want to share your translations between Win32 and Win64, there is no need to remove declared calling conventions like stdcall or cdecl. The Win64 compiler will simply ignore them.
[/SHOWTOGROUPS]