#c #visual-c #return #visual-studio-2019 #vtable
#c #visual-c #Возврат #visual-studio-2019 #vtable
Вопрос:
Я пытаюсь понять, почему следующий код неправильно работает в MSVC под Windows. Он напрямую вызывает метод VTable, который возвращает значение по значению.
#include <iostream>
#define RETURN_BY_VALUE 1
struct Value {
int Secret;
};
class IFoo {
public:
#if RETURN_BY_VALUE
virtual Value GetValue() = 0;
#else
virtual int GetValue() = 0;
#endif
};
class Foo : public IFoo {
public:
#if RETURN_BY_VALUE
virtual Value GetValue() { return Value{42}; }
#else
virtual int GetValue() { return 42; }
#endif
};
int main() {
IFoo* f = new Foo;
void* vtable = (void *)(*(std::intptr_t *)f);
#if RETURN_BY_VALUE
std::cout << f->GetValue().Secret << std::endl;
// The pointer to the GetValue function is at 0 byte offset in the Vtable
auto method = (Value(*)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f).Secret << std::endl;
#else
std::cout << f->GetValue() << std::endl;
auto method = (int(*)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f) << std::endl;
#endif
delete f;
return 0;
}
Вывод
> test.exe
42
1820312
Если мы настроимся RETURN_BY_VALUE
на то, чтобы 0
все работало так, как ожидалось.
> test.exe
42
42
Запуск образца в godbolt указывает на то, что gcc и clang не имеют этой проблемы https://godbolt.org/z/hn4MxK .
Реальным применением этого было бы подключение GetAdapterLuid в d3d12.
Комментарии:
1. вы уверены, что это правильный макет виртуальной таблицы Visual Studio?
2. Ваш пример для godbolt кажется неправильным. Вы всегда выводите там путь по умолчанию.
3. @AlanBirtles Да, вот почему это работает, если вы установите значение RETURN_BY_VALUE равным 0
4. @Secundi Спасибо, виноват. Исправлено: godbolt.org/z/hn4MxK
5. при вызове неопределенного поведения часто кажется, что все работает
Ответ №1:
- Не забывай об
__stdcall
этом. Без__stdcall
этого соглашение о вызове для свободной функции и для методов отличается больше.GetAdapterLuid
это__stdcall
я так думаю. - В Windows ABI возвращаемые структуры передаются как скрытый параметр. Похоже, что порядок между
this
и скрытым параметром отличается в методе и свободной функции. Вы можете определить свою функцию так, чтобы она действительно отсутствовала.
Похоже, это работает для меня, хотя, конечно, это пахнет:
#include <iostream>
#define RETURN_BY_VALUE 1
struct Value {
int Secret;
};
class IFoo {
public:
#if RETURN_BY_VALUE
virtual Value __stdcall GetValue() = 0;
#else
virtual int __stdcall GetValue() = 0;
#endif
};
class Foo : public IFoo {
public:
#if RETURN_BY_VALUE
virtual Value __stdcall GetValue() { return Value{42}; }
#else
virtual int __stdcall GetValue() { return 42; }
#endif
};
int main() {
IFoo* f = new Foo;
void* vtable = (void *)(*(std::intptr_t *)f);
#if RETURN_BY_VALUE
std::cout << f->GetValue().Secret << std::endl;
// The pointer to the GetValue function is at 0 byte offset in the Vtable
Value v;
auto method = (void(__stdcall *)(IFoo*, Value*)) (*(std::intptr_t *)(vtable));
std::cout << (method(f, amp;v), v.Secret) << std::endl;
#else
std::cout << f->GetValue() << std::endl;
auto method = (int(__stdcall *)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f) << std::endl;
#endif
delete f;
return 0;
}
gcc / clang вернет вашу структуру точно как целое число, Unix-подобные системы имеют другой ABI.
Самый чистый способ реализации перехватов — реализовать методы как методы. Найдите методы в вашей виртуальной таблице hook точно так же, как в целевой виртуальной таблице, тогда вам не нужно подделывать метод с помощью free function .
Комментарии:
1. Спасибо, кажется, это работает. Для общей реализации, я думаю, нам пришлось бы выделить объект, чтобы конструктор не вызывался дважды.
2. Размещение (C 11 без ограничений)
union
в стеке может быть лучше.