Guide : Exporting functions in Half-life mods

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

Valid XHTML 1.1! Valid CSS! Dogma W4

Disclaimer :

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 :

Table of Contents :

Acronyms

1. Introduction

2. Problems with macros

3. __declspec(dllexport)

4. Name decoration, extern "C"

5. __stdcall and __cdecl

6. .def file

7. GiveFnptrsToDll()

8. Solution

9. Other bots, metamod, SDK

References

Acronyms :

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

1. Introduction

I will try to explain these problems :

If you are not interested in details, read at least sections with red text

Well here is what I've found out. Even though problem is very complex, careful reader should not have problem understanding it.

2. Problems with macros

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

3. __declspec(dllexport)

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

How to know what functions are exported ?

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

4. Name decoration, extern "C"

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

extern "C"

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 :

5. __stdcall and __cdecl

__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 :

6. .def file

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

Example

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 :

7. GiveFnptrsToDll()

How export of GiveFnptrsToDll() is implemented in SDK :

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 :

8. Solution

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 ?)

9. Other bots, metamod, SDK

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())

References

SDK :

HPB4, HPBT3, HPBT4 :

JOE :

META :

POD26 :

RACCP, RACC1, RACC2 :

REAL :

WHIST :

Visual C++ 6.0, Visual C++ 7.0 (Visual Studio .NET 2003) :

Other :

End of document