1

How do I get the memory address of a class member function, I'm using a library statically that was previously setup dynamically, as the platform I ported the game to does not support dynamic libraries. It works well except for one issue.

When restoring a saved game NPCs become static as they don't continue to run the functions that were active when the game was saved. It did this by looking up the function address to get the name in the global offset table and symbol table when saving, when restoring it got the address using the name from the GOT and ST.

As this will not work with static linking I'm trying to figure out how to get the address of the exported functions to store it in a std::map with the address and a unique name when the application first starts.

The following doesn't seems to work for me for some reason. Any help would be much appreciated :).

Storing the address for the save game file in the base class using the DEFINE_FIELD macro:

// Global Savedata for Delay
TYPEDESCRIPTION CBaseEntity::m_SaveData[] = 
{
    DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION )
};

Derived class export from when it was using dynamic linking:

extern "C" _declspec( dllexport ) void CMultiManager( entvars_t *pev );

Derived class where ManagerThink() was exported previously when it used dynamic linking:

class CMultiManager : public CBaseEntity
{
public:
    CMultiManager();

    void /*EXPORT*/ ManagerThink ( void );
}

My new class constructor in the derived class to try and get the address of the member function (so I can store it in std:map with a name). Is it possible to this in the global scope rather than a constructor?

CMultiManager::CMultiManager()
{
    //Try the ManagerThink function directly    

    void (CBaseEntity ::*ptrTemp)(void);
    ptrTemp = static_cast <void (CBaseEntity::*)(void)> (ManagerThink);
    void* iMemoryAddressFunc = &ptrTemp;

    ALERT( at_error, "__TEST__ FUCNTION __ EXPORT ADDRESS: CMultiManager::ManagerThink (%08lx)\n", (unsigned long)iMemoryAddressFunc );

//-------------------------------------------------------------------------------

    //Try the inherited m_pfnThink variable ManagerThink() is stored in as well.

    void* iMemoryAddressVar = &m_pfnThink;

    ALERT( at_error, "__TEST__ M_PFNTHINK __ EXPORT ADDRESS: 
    CMultiManager::ManagerThink (%08lx)\n", (unsigned long)iMemoryAddressVar );
}

Function check method called before restoring the game, it's using the function address stored in the save game data in the base class (function above).

void FunctionCheck( void *pFunction, char *name )
{ 
    if (pFunction && !NAME_FOR_FUNCTION((unsigned long)(pFunction)) )
        ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (unsigned long)pFunction );
}

Debug log when running the game:

Obviously it's not going to find the export as I haven't setup anything yet as I need the address to store first, but why do the addresses not all match up? ManagerThink() has been assigned to m_pfnThink as well, so why are the first two in the log not at the same address at least?

From CMultiManager():
__TEST__ FUCNTION __ EXPORT ADDRESS: CMultiManager::ManagerThink (d008759c)
__TEST__ M_PFNTHINK __ EXPORT ADDRESS: CMultiManager::ManagerThink (01eb0e08)
From FunctionCheck();
No EXPORT: multi_manager:ManagerThink (00226c0a)

The ManagerThink() Function:

// Designers were using this to fire targets that may or may not exist -- 
// so I changed it to use the standard target fire code, made it a little simpler.
void CMultiManager :: ManagerThink ( void )
{
    float   time;

    time = gpGlobals->time - m_startTime;
    while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time )
    {
        FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 );
        m_index++;
    }

    if ( m_index >= m_cTargets )// have we fired all targets?
    {
        SetThink( NULL );
        if ( IsClone() )
        {
            UTIL_Remove( this );
            return;
        }
        SetUse ( ManagerUse );// allow manager re-use 
    }
    else
        pev->nextthink = m_startTime + m_flTargetDelay[ m_index ];
}

I'm also confused how it knew when setup dynamically which EXPORT ManagerThink ( void ); function to call for each instance of the object created, because it would have just stored the one EXPORT address in the GOT/symbol table right?

Any help/suggestions/advise would be great. Thank you :)

EDIT:

Thanks for the replies. I've managed to find a work around the issue.

I looked at a newer version of game where they updated the code to not use the GOT and ST as they needed the game on platforms that don't support dlls and were faced with the same issue. They solved it using the following macros to declare a structure and store defined pointers to member functions in the structure.

//-----------------------------------------------------------------------------
//
// Macros used to implement datadescs
//

#define DECLARE_SIMPLE_DATADESC() \
    static datamap_t m_DataMap; \
    static datamap_t *GetBaseMap(); \
    template <typename T> friend void DataMapAccess(T *, datamap_t **p); \
    template <typename T> friend datamap_t *DataMapInit(T *);

#define BEGIN_SIMPLE_DATADESC( className ) \
    datamap_t className::m_DataMap = { 0, 0, #className, NULL }; \
    datamap_t *className::GetBaseMap() { return NULL; } \
    BEGIN_DATADESC_GUTS( className )

#define BEGIN_SIMPLE_DATADESC_( className, BaseClass ) \
    datamap_t className::m_DataMap = { 0, 0, #className, NULL }; \
    datamap_t *className::GetBaseMap() { datamap_t *pResult; DataMapAccess((BaseClass *)NULL, &pResult); return pResult; } \
    BEGIN_DATADESC_GUTS( className )

#define DECLARE_DATADESC() \
    DECLARE_SIMPLE_DATADESC() \
    virtual datamap_t *GetDataDescMap( void );

#define BEGIN_DATADESC_NO_BASE( className ) \
    datamap_t className::m_DataMap = { 0, 0, #className, NULL }; \
    datamap_t *className::GetDataDescMap( void ) { return &m_DataMap; } \
    datamap_t *className::GetBaseMap() { return NULL; } \
    BEGIN_DATADESC_GUTS( className )

#define BEGIN_DATADESC( className ) \
    datamap_t className::m_DataMap = { 0, 0, #className, NULL }; \
    datamap_t *className::GetDataDescMap( void ) { return &m_DataMap; } \
    datamap_t *className::GetBaseMap() { datamap_t *pResult; DataMapAccess((CBaseEntity *)NULL, &pResult); return pResult; } \
    BEGIN_DATADESC_GUTS( className )

#define BEGIN_DATADESC_GUTS( className ) \
    template <typename T> datamap_t *DataMapInit(T *); \
    template <> datamap_t *DataMapInit<className>( className * ); \
    namespace className##_DataDescInit \
    { \
        datamap_t *g_DataMapHolder = DataMapInit( (className *)NULL ); /* This can/will be used for some clean up duties later */ \
    } \
    \
    template <> datamap_t *DataMapInit<className>( className * ) \
    { \
        typedef className classNameTypedef; \
        static CDatadescGeneratedNameHolder nameHolder(#className); \
        className::m_DataMap.baseMap = className::GetBaseMap(); \
        static typedescription_t dataDesc[] = \
        { \
        { FIELD_VOID, 0, 0, 0, 0 }, /* so you can define "empty" tables */

#define END_DATADESC() \
        }; \
        \
        if ( sizeof( dataDesc ) > sizeof( dataDesc[0] ) ) \
        { \
            classNameTypedef::m_DataMap.dataNumFields = SIZE_OF_ARRAY( dataDesc ) - 1; \
            classNameTypedef::m_DataMap.dataDesc      = &dataDesc[1]; \
        } \
        else \
        { \
            classNameTypedef::m_DataMap.dataNumFields = 1; \
            classNameTypedef::m_DataMap.dataDesc      = dataDesc; \
        } \
        return &classNameTypedef::m_DataMap; \
    }

// replaces EXPORT table for portability and non-DLL based systems (xbox)
#define DEFINE_FUNCTION_RAW( function, func_type )          { FIELD_VOID, nameHolder.GenerateName(#function), /*{ NULL, NULL },*/ 1, FTYPEDESC_FUNCTIONTABLE, /*NULL, NULL,*/ (inputfunc_t)((func_type)(&classNameTypedef::function))},

//------------------------------------------------------------------------------
  • TL;DR. Non-static member functions are not the same as static member functions, global or "free" functions. [More information](https://isocpp.org/wiki/faq/pointers-to-members#fnptr-vs-memfnptr-types) – PaulMcKenzie Jul 04 '17 at 05:30
  • Which platform are you targeting that does not allow dynamic libraries? With regards to your confusion about _which...function to call for each instance...created_ .. C++ is not C, long story short, [there's basically a hidden `this` pointer](https://stackoverflow.com/questions/41667653/). You can get the address of member functions and invoke them like so: `void (A::*fnptr)() = &A::function;`, to invoke, you need a valid object: `A a; (a.*fnptr)();` .. Also, does `static_cast(CMultiManager::ManagerThink);` compile? – txtechhelp Jul 04 '17 at 06:06
  • Thank you for the response :), the platform is the OG XBox. So that means the exported symbol table has one entry per exported member function when dynamic linking, but each instance created in the C code the library is used with knows what instance it's used with using the hidden this pointer, correct? I will try using the code you posted above, the code static_cast-ing compiles and when used with the code I posted seems to output an address from somewhere. Thank you – Brent de Carteret Jul 04 '17 at 06:24
  • @BrentdeCarteret -- To call a non-static member function via pointer, the syntax used must have the instance specified. Either `(yourObjectPtr->*function)(args)` or `(yourObject.*function)(args)`. So given this, how are you going to construct your program to make a call that looks like that? It isn't `C`, where you merely do things like this: `(*function)(args)` or even `function(args)`; – PaulMcKenzie Jul 04 '17 at 08:44
  • @BrentdeCarteret: I'm trying to understand your question fully, but I'm failed. It seems that you have several problems to solve. Is this one of them? You have a class member function pointer (`ptr`), and you have an `object`, and you want to know the address that `object->*ptr` points to? If yes, I can help you in this matter. – geza Jul 04 '17 at 09:30
  • Thank you for the replies, my problem is I needed to store the address of member functions. I looked at a newer version of the software I was porting where they moved away from using the global offset table and symbol table, they did it with some macros that I have pasted into an edit above. Thanks again – Brent de Carteret Jul 06 '17 at 12:08

0 Answers0