Существует ли стандартная процедура для передачи объектов класса по значению?
Существует ли стандартная процедура для передачи классов по значению? Другими словами, если я сделаю это:
struct Test
{
int a;
double b;
}
void DoSomething(Test t)
{
std::cout << t.a << std::endl;
std::cout << t.b << std::endl;
}
//...
Test myObj;
myObj.a = 5;
myObj.b = 10.5;
DoSomething(myObj);
При условии стандартной упаковки и компоновки, предоставляет ли стандарт какие-либо гарантии того, что класс будет отправляться и получаться согласованным образом независимо от компилятора?
Потому что я ожидаю вопросов в духе "почему ты хочешь это сделать?" или "это похоже на проблему XY", вот (длинный) контекст. Я пытаюсь передать объект класса туда и обратно между EXE и DLL, скомпилированными с разными компиляторами, и кажется, что объект класса не читается с правильного начального адреса. Однако эта проблема испаряется, если я передаю объект по ссылке. (Дополнительный вопрос - почему передача по ссылке работает, а передача по значению не будет? У меня сложилось впечатление, что передача по значению будет копировать объект и передавать ссылку на копию. Очевидно, я что-то здесь неправильно понимаю.)
1 ответ
В общем, ABI между различными компиляторами C++ может отличаться по своему усмотрению. Стандарт C++ не предписывает данный ABI.
Однако C ABI чрезвычайно стабильны. Одним из способов решения этой проблемы является наличие функций только для заголовков, которые переводят ваш код в extern "C"
функции, которые экспортируются из вашей DLL. Внутри вашей DLL extern "C"
Затем функции вызывают более обычный интерфейс C++.
Для конкретного примера
struct Test;
// DLL exported:
extern "C" void Private_DoSomething_Exported( Test* );
// Interface:
namespace Interface {
inline void DoSomething( Test t ) { return Private_DoSomething_Exported(&t); }
};
// implementation. Using Test&& to make it clear that the reference is not an export parameter:
namespace Implementation {
void DoSomething( Test&&t ) {
std::cout << t.a << std::endl;
std::cout << t.b << std::endl;
}
}
void Private_DoSomething_Exported( Test* t ) {
Assert(t);
Implementation::DoSomething(std::move(*t));
}
Это помещает "наиболее совместимый" ABI (чистый "C" ABI) в точку, где вы экспортируете функции из DLL. Код клиента вызывает Interface::DoSomething
, который inline
в коде клиента вызывает "C"
ABI (который даже не знает расположение объекта), который затем вызывает C++ Implementation::DoSomething
,
Это все еще не является доказательством в отношении каждой проблемы, потому что расположение даже структур POD может варьироваться в зависимости от компиляторов (в качестве практического примера некоторые компиляторы рассматривают long
как 32 бит на 64 битных машинах, другие лечат long
как 64 бит на 64 битных машинах). Упаковка также может варьироваться.
Чтобы уменьшить это влияние, сначала вы захотите использовать только типы фиксированного размера из заголовочных файлов C. Вы также захотите изучить документы по упаковке обоих компиляторов.