для вызова функции из внешнего модуля нам нужен адрес этой функции.
если мы пометим функцию как virtual - внутри объекта существует указатель на таблицу (так называемый vftable *)1006 *) где хранятся указатели на все виртуальные функции для этого объекта.и компилятор генерирует соответствующий код для вызова функции через этот указатель.
, поэтому, когда вы пишете
class log
{
public:
virtual void Write(const char* format, ...);
};
, компилятор создает скрытую структуру vftable внутри объекта
class log
{
public:
virtual void Write(const char* format, ...);
struct vftable {
void (* Write)(const char* format, ...);
};
};
и вызов
Output->Write("Hello Game");
действительно реализован как
(*(log::vftable**)Output)->Write("Hello Game");
, поэтому здесь у нас есть указатель объекта ( Output ), внутри него существует указатель на log :: vftable , и внутри этой таблицы существует указатель на Запись функции.обратите внимание, что в этом случае нам не нужно помечать класс как dllexport или dllimport
, поэтому работа с виртуальными функциями - все, что вам нужновызвать виртуальную функцию - указатель на объект.
в случае, если функция не виртуальная - и помечена как __ declspec (dllimport) компилятор объявить скрытую переменную, который является указателем на функцию.и функция будет вызываться через эту переменную.поэтому, когда мы пишем:
class __declspec(dllimport) log
{
public:
virtual void Write(const char* format, ...);
};
void demo(log* Output)
{
Output->Write("Hello Game");
}
компилятор фактически делает следующее:
extern void (* __imp_?Write@log@@QEAAXPEBDZZ)(log* This, const char* format, ...);
void demo(log* Output)
{
__imp_?Write@log@@QEAAXPEBDZZ(Output, "Hello Game");
}
обратите внимание, что указатель на функцию __ imp_? Write @ log @@ QEAAXPEBDZZ только объявлен (с extern ), но не реализовано.если вы соберете без соответствующего lib файла (где реализовано __ imp_? Write @ log @@ QEAAXPEBDZZ symbol), вы получите ошибку компоновщика: неразрешенный внешний символ __imp_? Write @ log @@QEAAXPEBDZZ
, поэтому, если функция-член объявляется без virtual и класса, объявленного как __declspec (dllimport) , необходимо использовать соответствующие lib .загрузчик, при загрузке экспортируемого PE ? Write @ log @@ QEAAXPEBDZZ адреса функции из Engine.dll и записи этого адреса в __ imp_? Write@ log @@ QEAAXPEBDZZ
Конечно, существует еще один вариант - реализовать все это самостоятельно.взять на себя работу грузчика.как-то так
#pragma comment(linker, "/alternatename:__imp_?Write@log@@QEAAXPEBDZZ=?__imp__Write_log__QEAAXPEBDZZ@@3PEAXEA")
void* __imp__Write_log__QEAAXPEBDZZ = 0;
BOOL LoadEngine()
{
if (HMODULE hmod = LoadLibraryW(L"Engine.dll"))
{
if (__imp__Write_log__QEAAXPEBDZZ = GetProcAddress(hmod, "?Write@log@@QEAAXPEBDZZ"))
{
return TRUE;
}
}
return FALSE;
}
после этого мы уже можем вызывать Output->Write("Hello Game");
, конечно, в c ++ мы не можем напрямую объявить имя __ imp_? Write @log @@ QEAAXPEBDZZ , поэтому нужно использовать трюк с опцией компоновщика / alternatename .или мы можем объявить это имя точно в отдельном asm файле