2015-08-11 6 views
1

У меня есть сборка программы с msvc, которая динамически загружает dll. DLL предоставляет функцию, которая является вызовом из основной программы. Если оба они строятся с помощью msvc или gcc, все в порядке, но когда я компилирую, например. main с msvc и dll с gcc что-то не так.Вызов соглашения между msvc и gcc

# ifdef __GNUC__ 
# define CDECL __attribute__ ((__cdecl__)) 
# else 
# define CDECL __cdecl 
# endif 

struct EXP result { 
    uint32_t code; 
}; 

#define SUCCESS result{0}; 

virtual result CDECL foo(char const* const*& target) const { 
    target = (char const* const*)0xAFFE; 
    return SUCCESS; 
} 

Проблема заключается в том, после того, как целевой вызов является нулевым вместо 0xAFFE. Основная программа скомпилирована с использованием cdecl в качестве соглашения о вызове. Структура упакована (без выравнивания), но я также попытался выровняться по разным размерам (1, 2, 4, 8, 16). Я также попытался использовать __declspec/__atribute__(dllexport) и различные комбинации обоих вариантов.

Если я взглянуть в код ассемблера, я могу видеть две большие разницы:

; msvc       | gcc 
;===============================|================================ 
; befor calling     | 
;-------------------------------|-------------------------------- 
           | sub  dword ptr [esp+4],8 
           | 
; foo();      | 
;-------------------------------|-------------------------------- 
push ebp      | push ebp 
mov  ebp,esp     | mov  ebp,esp 
mov  eax,dword ptr [target] | 
mov  dword ptr [eax],0AFFEh | 
mov  eax,dword ptr [ebp+0Ch] | mov  eax,dword ptr [ebp+0Ch] 
mov  dword ptr [eax],0  | mov  dword ptr [eax],0AFFEh 
           | mov  eax,0 
pop  ebp      | pop  ebp 
ret        | ret 

Почему это, даже если я использую те же соглашения о вызовах как на компилятор? И как мне это исправить?

+0

не является DLL вызовов должен быть STDCALL? –

+0

@CemKalyoncu: Это не является обязательным требованием. Существует множество DLL-файлов, которые вместо этого используют '__cdecl'. Это абсолютно безопасно, если оба приложения и DLL согласны использовать '__cdecl'. Это не проблема. –

+1

Функция возвращает структуру. Скорее всего, msvc и gcc не согласны с тем, как передать эту структуру вокруг, будь то в стеке вызовов или в регистрах и т. Д. Возврат структуры из функции не переносится. Вызывающая конвенция определяет, как передаются параметры, но не определяет, как передаются нетривиальные возвращаемые значения. Переносимое решение состоит в том, чтобы либо самостоятельно вернуть либо 'uint32_t', либо передать структуру в функцию в качестве параметра указателя, и позволить функции заполнять ее по мере необходимости. –

ответ

0

Даже если мне потребовался один год, чтобы ответить на мой собственный вопрос (я очень ленивый человек;), я хотел бы прояснить это. Как сказал Щенок в своем answer, ABI между компиляторами отличается. Но это почти половина прав. Поскольку Microsoft изобрела COM еще в 1992 году, они создали интерфейс, который можно отобразить в интерфейсы C++ vtable. Поэтому другие крупные производители компиляторов реализовали отображение между COM and C++ vtables:

[...] Так как компилятор для Windows, которые не могут использовать COM довольно ограничен, другие производители компиляторов соблюдение соответствия между COM и виртуальными таблицами виртуальных таблицами C++. [...]

Кроме того, как Реми Лебо упомянул в своем комментарии выше:

Функция возвращает структуру. Скорее всего, msvc и gcc не согласны с тем, как передать эту структуру вокруг, будь то в стеке вызовов или в регистрах и т. Д. Возврат структуры из функции не переносится. Вызывающая конвенция определяет, как передаются параметры, но не определяет, как передаются нетривиальные возвращаемые значения. Портативное решение состоит в том, чтобы либо возвращать только uint32_t самостоятельно, либо передать структуру в функцию в качестве параметра указателя и позволить функции заполнять ее по мере необходимости.

[...], возвращая только указатель сам по себе, будет охватываться правилами вызывающего соглашения. Раскрываются тривиальные встроенные типы (целые числа, поплавки/парные, указатели и т. Д.). Пользовательские типы (классы/структуры) находятся на усмотрении каждого компилятора, как правило, из-за различий в том, как они оптимизируют свой код.

Так что проблема была: Вы не можете вернуть структуру с помощью виртуальных функций между различным компилятором но только POD.

Mentionable Показания:

6

Вызывающая конвенция в этом случае довольно бессмысленна. Проблема - это такие вещи, как vtable layout. MSVC ABI и Itanium не согласны со многими вещами. Вы не можете скомпилировать C++-интерфейс и смешивать и сопоставлять между компиляторами, если он явно не поддерживается. Clang и G ++ должны взаимодействовать, если вы установите правильные настройки, а Clang и MSVC могут взаимодействовать в зависимости от того, какие именно функции вы используете.

Вообще говоря, не смешивайте и не сопоставляйте компиляторы C++ для интерфейсов C++. Это не сработает.