update : Pierre has fixed bugs in HPB bot template 4, and is aware of bugs in RACC template 2
version 1.0, 31.08.2004
Newest version of this document can be found on http://neuron.tuke.sk/~wagner or http://kxbot.sourceforge.net
If you are experienced enough, read document about declaration of GiveFnptrsToDll() and Server_GetBlendingInterface()
Copyright (C) 2004 Jozef Wagner, http://neuron.tuke.sk/~wagner
Permission is granted to copy and distribute this document unmodified. All modifications are prohibited without written permission from Jozef Wagner
All copyrighted material is used here as fair use of the material under United States copyright law.
THIS DOCUMENT IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This document deals with strange declaration of GiveFnptrsToDll() and various macros and function keywords used in SDK , META and most of todays bots/mods for Half-life.
I'll target mainly on windows environment and MSVC (not Borland or MingW32), because I don't have linux installed right now and making shared libraries under linux is not complicated.
Requirements :
Most of the bots/mods can be downloaded from www.bots-united.com
SDK - Half-Life 1 SDK, version 2.3. Alfred Reinolds patch applied
HPB4 - Botmans bot, version 4
HPBT3 - Botmans bot template, version 3
HPBT4 - Botmans bot template, version 4
JOE - Joebot, version 1.6.5.3
META - Metamod, version 1.17.2
POD26 - Podbot, version 2.6
RACCP - RACC Preview
RACC1 - RACC Template, version 1
RACC2 - RACC Template, version 2
REAL - RealBot, CVS snapshot, August 2004
WHIST - Whistlers bot framework, first version
VC6 - Visual C++ 6.0 headers
VC7 - Visual C++ 7.0 headers (Visual Studio .NET 2003)
HPBF3 - Botmans forum archive, 2003
HPBF4 - Botmans forum archive, 2004
BUF4 - Bots united forum, August 2004
I will try to explain these problems :
Well here is what I've found out. Even though problem is very complex, careful reader should not have problem understanding it.
GiveFnptrsToDll() declaration in (my) bots source code and in SDK is : (read more)
void DLLEXPORT GiveFnptrsToDll( ... )
Well SDK uses lots of strange macros, so first I had to determine what they mean.
in eiface.h (which is part of SDK) we can found definition for our macro :
#define DLLEXPORT __stdcall
EXPORT macro is used for all exported function in bot, except GiveFnptrsToDll() :
extern "C" EXPORT int GetNewDLLFunctions( ... ) extern "C" EXPORT int GetEntityAPI( ... ) extern "C" EXPORT int GetEntityAPI2( ... ) extern "C" EXPORT int Server_GetBlendingInterface( ... )
All functions exported with Botmans LINK_ENTITY_TO_FUNC are exported with extern "C" EXPORT too.
in cbase.h (which is part of SDK) there is this definition :
#define EXPORT _declspec( dllexport )
(Note that accorting to Microsoft, correct syntax is __declspec (two underscores), but MSVC accepts also one underscore. Valve incorrectly uses one underscore in SDK. Though in WIN16 applications only one underscore is correct, we don't need WIN16 compatible applications anymore IMO)
DllMain is declared as
BOOL WINAPI DllMain( ... )
So we have another strange macro, WINAPI
MSDN C++ Language reference reveals that WINAPI is defined as __stdcall. Also in VC6 and VC7, in file include/windef.h, WINAPI is defined as :
#ifdef _MAC #define WINAPI CDECL #elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) #define WINAPI __stdcall #else #define WINAPI #endif
(For WIN16, WINAPI is defined as FAR PASCAL. FAR is empty definition in WIN32 and PASCAL is __stdcall again :). In WIN16, it is obviously __far __pascal. Both of these calling conventions are obsolete in WIN32)
To be complete, CDECL is defined in VC6 and VC7, in file include/windef.h as :
#if defined(DOSWIN32) || defined(_MAC) #define CDECL _cdecl #else #define CDECL #endif
To summarize, we have learned that :
Now we only have to learn, what those __xxx things are, and what extern "C" means
NOTE : GiveFnptrsToDll() is exported as __stdcall and all other functions are exported as extern "C" __declspec( dllexport )
(DllMain() is not exported, because it is called by main entry point function called _DllMainCRTStartup)
NOTE : WINAPI and DLLEXPORT macros must be after functions return type, extern "C" specifier and EXPORT macros must be before functions return type
NOTE : WINAPI, DLLEXPORT and EXPORT macros are empty definitions in linux. Seems like in linux, everything is easier
BEWARE : DLLEXPORT is defined differently in META. It is not the same macro as we had above
This and this MSDN references explains what __declspec(dllexport) is.
Shortly, it is for exporting functions in DLL. Functions with __declspec(dllexport) specifier can be called by another program which loads your DLL
You can read more in tutorial on creating and using DLLs on www.flipcode.com
Also www.mindcracker.com has some tutorials for begginers
Important thing is also that __declspec(dllexport) keyword must be before function prototype. This means before functions return type. Example :
// WRONG : int __declspec(dllexport) MyFunction(int a, double b); // WRONG, function does not have return type : // ISO C++ forbids declaration of functions with no type __declspec(dllexport) MyFunction(int a, double b); // GOOD : __declspec(dllexport) int MyFunction(int a, double b);
__declspec(dllexport) is one of three ways how to export function in MSVC. The other two are .def files and /EXPORT parameter in linker options
Fortunately, there are several programs which help you view all exported functions under windows :
To summarize, we have learned that :
But there is one catch, name-decoration. Read about it in next section
NOTE : There is no such thing as __declspec(dllexport) in linux. It is all Microsoft specific thingy. In linux, functions are exported by using -share parameter in linker
Look at this list of exported functions :
...
162 A0 000661F0 ?VIP_SafetyTouch@CVIP_SafetyZone@@QAEXPA...
163 A1 00045940 ?Wait@CFuncTrain@@QAEXXZ
164 A2 000493C0 ?Wait@CGunTarget@@QAEXXZ
165 A3 000205E0 ?WaitTillLand@CGib@@QAEXXZ
166 A4 00060AF0 DelayedUse
167 A5 00014CF0 GetEntityAPI
168 A6 00014D20 GetNewDLLFunctions
1 A7 00034980 GiveFnptrsToDll
169 A8 0000E9E0 Server_GetBlendingInterface
...
Do you see all these ? and @ and QAEXXZ things ?
Well this is because internal name of function is NOT the same as we specified in source code. This is called name decoration or name mangling
Undecorated name is name we specified in our source code (GiveFnptrsToDll)
There are two name decorations (there are more, but MSVC recognizes only 2):
Programs compiled under C++ uses C++ name decoration by default
Why new name decoration ? :
Main disadvantage of C++ name decoration : Name is different on diferrent compilers, even on different versions of same compiler
As you probably guessed, extern "XXX" keyword controls which name decoration should compiler use
Theory behind extern "XXX" is called linkage specification : It is the protocol for linking functions (or procedures) written in different languages. Function calling conventions are affected by the linkage specification selected. An example of a linkage specification is extern "C". (taken from MSDN)
extern "C" keyword is called C linkage specifier : It is a declaration of a function or object as extern "C", indicating to the C++ compiler that the function name that follows the linkage specifier is an undecorated C function. C linkage allows existing C code to be used in new C++ applications. (taken from MSDN)
Read more about extern "C" here, here and here
Another article about extern "C" and name decoration on Intel fortran compiler for linux
But extern "C" does more than just change to C decoration. In combination with __declspec(dllexport), it tries to export with undecorated name. What is worse, it does not always succeed.
extern "C" :
We will talk about __cdecl and __stdcall in next section
If we insist on exporting with C++ decoration, we must call our exported function by ordinal, rather than by name. This "advanced" feature is beyond scope of this document. (We must NOT export with __declspec(dllexport). We must know at what position function will be exported, and import it by calling GetProcAddress with its position, rather than its name)
IMPORTANT : __declspec(dllexport) behave diferrently when in export "C" __cdecl mode : If the function is exported with the C calling convention (_cdecl), it strips the leading underscore (_) when the name is exported.
To summarize, we have learned that :
__stdcall and __cdecl are calling conventions : A convention that determines the order in which arguments passed to functions are pushed on the stack (the calling sequence), whether the calling or called function removes the arguments from the stack, and the name-decorating convention the compiler uses to identify individual functions. (taken from MSDN)
__cdecl is C calling convention, and __stdcall is standard calling convention
Detailed : Calling convention specify how and who clears stack, how are parameters pushed and more. See this document if you are into low level stuff
__cdecl is default calling convention in MSVC (you can set it in Project properties dialog), so you don't see it in source codes much
PERFECT articles about calling conventions can be found at The Old New Thing : 1, 2, 3, 4 and 5. Best for us is 3rd article.
Another detailed article about calling conventions.
Summarization :
Using .def file in yout bot is another way how to export functions. (Read more)
Example of bot file from SDK :
LIBRARY mp EXPORTS GiveFnptrsToDll @1 SECTIONS .data READ WRITE
Part LIBRARY defines the name of your dll (mp.dll in above example)
Part SECTIONS defines properties of sections in your dll (in our example, section .data will have read and write "access")
Most important for us is part EXPORT, which defines list of exported functions
.def file can be used together with other methods of exporting (__declspec(dllexport) and /EXPORTS)
Full syntax for EXPORTS is :
entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]
@ordinal defines position in list of exported functions. This is sometimes very important, because function can be imported not only by its name, but also by its position.
[NONAME], [PRIVATE] and [DATA] are not important for us :-)
Interesting for us is entryname[=internalname], which let us completely change name of our function
We have MyFunction declared as :
extern "C" int __stdcall MyFunction(int a, double b, char c)
which will result in internal name :
_MyFunction@16
If caller wants undecorated name, or even completelly different name, we can fix it :
EXPORTS HelloPierre=_MyFunction@16
Caller should now import function with name HelloPierre, which in reality is our MyFunction
Also notice that we havent used __declspec(dllexport), because we are already exporting through .def file. If we included __declspec(dllexport) in declaration, function would be exported twice. Once as _MyFunction@16 and once as HelloPierre
IMPORTANT : In SDK,
EXPORTS GiveFnptrsToDll @1
should not work, because internal name of GiveFnptrsToDll is _GiveFnptrsToDll@8. WHY IT WORKS IS STILL MIRACLE TO ME. Correct definition is
EXPORTS GiveFnptrsToDll=_GiveFnptrsToDll@8 @1
and extern "C" should be used in declaration, because we want C name decoration
Summary :
GiveFnptrsToDll() is defined in dlls\h_export.cpp :
#ifdef _WIN32 void DLLEXPORT GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine,globalvars_t *pGlobals) #else extern "C" void GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine,globalvars_t *pGlobals) #endif
DLLEXPORT is defined in engine\eiface.h :
#ifdef _WIN32 #define DLLEXPORT __stdcall #else #define DLLEXPORT /* */ #endif
.def file is used : dlls\mp.def :
LIBRARY mp EXPORTS GiveFnptrsToDll @1 SECTIONS .data READ WRITE
In dlls\mp.dsp, def is included to project : /def:".\mp.def"
of course SDKs approach works, but here are my thoughts, why it is not good :
Also other problems arise :
Here I will present my solution on how to export GiveFnptrsToDll :
If you are already using .def file :
Change functions definition to :
#if defined _WIN32
#pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1")
#pragma comment(linker, "/SECTION:.data,RW")
#endif
#ifndef __linux__
extern "C" void __stdcall GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals )
#else
extern "C" void GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals )
#endif
{
...
}
Advantages of my approach :
Note that this solution is for MSVC and linux. You have to include additional code for Borland or MingW32 to export it (But you had to do it also in old approach, hadn't you ?)
All tested bots are using .def files (HPB4, HPBT3, HPBT4, JOE, META, POD26, RACCP, RACC1, RACC2, REAL, WHIST)
For authors of these bots, I strongly recommend reading declaration of GiveFnptrsToDll() and Server_GetBlendingInterface() where they may find more useful stuff
Hey Pierre and Botman ! You have error in one or more of your bots. Read it and fix errors (Errors with return type of GiveFnptrsToDll() under linux and somewhat serious error in Server_GetBlendingInterface())
End of document