правильный условный вызов метода класса

Я не могу использовать __thiscall давая мне следующую ошибку:
error C4234: nonstandard extension used : '__thiscall' keyword reserved for future use

Я вызываю функцию класса из DLL:

typedef void(*SETDIRPROC)(void*,D3DXVECTOR3&);

void ZMyCharacter_SetDirection_Rev( void )
{
if( pZMyCharacter == 0 )
{
printf( "cannot set direction now\n" );
return;
}
SETDIRPROC SetDir_Proc = (SETDIRPROC)0x004A2AF0;
D3DXVECTOR3 pos = D3DXVECTOR3(4.0f,2.0f,1.0f);
SetDir_Proc( pZMyCharacter, pos );
}

pZMyCharacter это void* указатель на реальный class ZMyCharacter на реальном приложении. Это работает, но я получаю ошибки отладки (которые можно игнорировать), предупреждающие, что соглашение о вызовах отличается. Так и есть, так как SETDIRPROC является __cdecl по умолчанию, и я не могу изменить его на __thiscall,

typedef void(__thiscall *SETDIRPROC)(void*,D3DXVECTOR3&); // ошибка C4234

как мне обойти это?

1

Решение

Я собираюсь отвезти вас по темной и ужасающей дороге в страну неопределенного поведения …

Проблема здесь в том, что вам нужно вызывать указатель на функцию-член, фактически не имея указателя на функцию-член. Вы можете использовать немного темной магии из земли UB, чтобы выполнить это. Эта темная магия позволит вам преобразовать простое целочисленное значение в полностью пригодный указатель на функцию-член. Для этого мы можем создать union

// Magical beans. Replace with your own beans if you have them.
struct ZMyCharacter {};

// Here be dark magic!
union FunctionAddress
{
typedef void (ZMyCharacter::*MEMBER_FUNC)(D3DXVECTOR3);

FunctionAddress(uintptr_t addr) : address(reinterpret_cast<void*>(addr)) {}

MEMBER_FUNC function;
private:
void* address;
};

Это волшебный union позволит вам установить указатель на функцию-член из простого целочисленного значения через его конструктор …

FunctionAddress pZMyFunction(0x004A2AF0);

Чтобы использовать это, вам нужно будет внести пару изменений в ZMyCharacter_SetDirection_Rev вызвать функцию-член напрямую, вместо того, чтобы передавать ее как указатель на другую функцию (что, как я полагаю, основано на вашем предыдущем вопросе, является встроенной сборкой) …

void ZMyCharacter_SetDirection_Rev( void )
{
if( pZMyCharacter == NULL )
{
printf( "cannot set direction now\n" );
return;
}
D3DXVECTOR3 pos = D3DXVECTOR3(4.0f,2.0f,1.0f);

// directly call using pointer to member function
(pZMyCharacter->*pZMyFunction.function)( pos );
}

Имейте в виду, что если функция виртуальная, вы обходите стороной виртуальную функцию. Если это так, вы должны быть готовы к ситуациям, когда это нарушает полиморфизм. Если вы хотите полностью поддерживать диспетчеризацию виртуальных функций, вам необходимо полностью перепроектировать макет виртуальной таблицы. Для этого вы можете сделать что-то еще проще, не используя много темной магии.

// reverse engineered virutal function layour/ordering
struct ZMyCharacter
{
virtual void func1();
virtual void func2();
virtual void func3();
virtual void target_function(D3DXVECTOR3&);
virtual void func4();
};

void ZMyCharacter_SetDirection_Rev( void )
{
if( pZMyCharacter == NULL )
{
printf( "cannot set direction now\n" );
return;
}
D3DXVECTOR3 pos = D3DXVECTOR3(4.0f,2.0f,1.0f);
pZMyCharacter->target_function( pos );
}

Я понимаю, что это меняет несколько вещей для вашей текущей кодовой базы. Вы должны быть в состоянии интегрировать код или концепции в то, что вы пытаетесь достичь.


Неопределенное поведение, о котором я упоминаю, касается доступа к неактивному члену профсоюза. Обычно это считается плохим.

2

Другие решения

Я думаю, это должно выглядеть так:

typedef void(__thiscall &ZMyCharacter::*SETDIRPROC)(D3DXVECTOR3&);
SETDIRPROC SetDir_Proc = (SETDIRPROC)0x004A2AF0;
static_cast<ZMyCharacter*>(pZMyCharacter)->SetDir_Proc( pos );

__thiscall предназначен для использования в указателе на функцию-член, а не на свободную функцию, как вы объявили. Вышесказанное должно быть ближе к тому, что нужно компилятору — тип функции-члена приведен и вызван.

2

Это не проверено, но должно дать вам начало. Обратите внимание, что он основан на деталях реализации MSVC, поэтому он не является стандартным и не будет работать в других компиляторах:

#pragma warning(push)
#pragma warning(disable: 4608)
template < typename Src, typename Dest >
Dest force_cast( Src src )
{
union _convertor { Dest d; Src s; _convertor() : d(0), s(0) {} } convertor;
convertor.s = src;
return convertor.d;
}
#pragma warning(pop)

void func(ZMyCharacter* pZMyCharacter)
{
typedef void (ZMyCharacter::*F_SetDirection)(D3DXVECTOR3&);
F_SetDirection pfSetDirection = force_cast< FARPROC, F_SetDirection >(0x004A2AF0);
D3DXVECTOR3 pos = D3DXVECTOR3(4.0f,2.0f,1.0f);
(pZMyCharacter->*pfSetDirection)(pos);
}
1

Этот маленький (неясный) фрагмент здесь не рекомендуется совсем, но, как вы можете себе представить, это работает:

class ZMyCharacterHook
{
public:
virtual void SetDir(D3DXVECTOR3&);
};

typedef void (*SETDIRPROC)(D3DXVECTOR3&);
typedef void (ZMyCharacterHook::*SETDIR_METHOD)(D3DXVECTOR3&);

SETDIRPROC SetDir_Proc = (SETDIRPROC)0x004A2AF0;

D3DXVECTOR3 pos = D3DXVECTOR3(4.0f, 2.0f, 1.0f);
((ZMyCharacterHook *)pZMyCharacter->*(SETDIR_METHOD &)SetDir_Proc)(pos);

Он никак не поддерживается и должен использоваться только в том случае, если вы действительно хотите рискнуть, так как он имеет неопределенное поведение (взгляните на обходной путь приведения ссылок).

Если вы хотите быть более дискретным, но все же против какой-либо ремонтопригодности и переносимости, вы можете использовать встроенную сборку MSVC x86 для явного задания ecx (который содержит this указатель при использовании thiscall), а затем вызвать «простую» функцию по ее адресу:

__asm
{
pusha                    // save all registers
mov  ecx, pZMyCharacter  // "this" pointer
lea  eax, pos            // eax = &pos (parameter passed by reference)
push eax                 // parameters pushed onto the stack
mov  eax, 0x004A2AF0
call eax                 // the function call itself
popa                     // restore registers, no messed up code from here on
}
1