C - правильный импорт функций stdcall из неуправляемой DLL

Я пытаюсь импортировать функцию из неуправляемой DLL в проект C, создав файл .def с указанием функции, которую мне нужно использовать. Практикуюсь на функции WinAPI MessageBoxA из user32.dll. Это функция стандартного вызова, как и другие функции WinAPI. Вот как я создаю свой файл .def:

LIBRARY user32.dll
EXPORTS
_MessageBoxA@16

Затем я создаю из него .lib следующим образом: lib /def:"C:\Path\to\def\user32.def" / out:"C:\path\to\project\user32-mb.lib", который успешно создает user32-mb.lib и user32-mb.exp. Затем в моем проекте C я делаю следующее:

#pragma comment(lib, "user32-mb.lib")

#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

EXTERNC __declspec(dllexport) int __stdcall MessageBoxA(void *hWnd, char *lpText, char *lpCaption, int uType);

void main(){
    MessageBoxA(0, "MessageBox test", "MessageBox test", 0x00000030L);
}

Однако при подключении выдает следующую ошибку:

error LNK2019: unresolved external symbol _MessageBoxA@16 referenced in function _main

Однако, когда я меняю объявление в .def на это:

LIBRARY user32.dll
EXPORTS
MessageBoxA

И измените прототип функции в моем коде C на cdecl вместо stdcall:

EXTERNC __declspec(dllexport) int __cdecl MessageBoxA(void *hWnd, char *lpText, char *lpCaption, int uType); Окно сообщения действительно появляется, но сразу после закрытия выдает ошибку:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention. Что указывает на то, что вызывать его с помощью cdecl тоже плохая идея, так как для этого все-таки требуется stdcall.

Вопрос в том, что я должен изменить в файле .def или в моем проекте, чтобы избежать как ошибок, так и правильно импортировать и вызывать функцию stdcall?


person Mints97    schedule 14.08.2014    source источник


Ответы (4)


Вам нужно изменить __declspec(dllexport) на __declspec(dllimport), так как вы импортируете функции из DLL, а не экспортируете их:

EXTERNC __declspec(dllimport) int __stdcall MessageBoxA(void *hWnd, char *lpText, char *lpCaption, int uType);
                      ^^
person Drew McGowen    schedule 14.08.2014
comment
Файл DEF: LIBRARY user32.dll EXPORTS _MessageBoxA@16 и изменил строку в проекте, как вы сказали. Теперь пишет, что не может найти __imp__MessageBoxA@16 - person Mints97; 15.08.2014
comment
Вы также можете попробовать добавить этот символ в часть EXPORTS файла DEF. - person Drew McGowen; 15.08.2014
comment
def файл: LIBRARY user32.dll EXPORTS _MessageBoxA@16 __imp__MessageBoxA@16 точно такая же ошибка - person Mints97; 15.08.2014
comment
Хм. Я видел некоторые ассемблерные программы, которые используют MessageBoxA@16, т.е. без начального подчеркивания. К сожалению, у меня нет доступа к банкомату с Windows, поэтому я ничего не могу проверить. - person Drew McGowen; 15.08.2014
comment
Также не используйте dllimport, он не объявлял указатель на функцию. Просто удалите его полностью. - person Hans Passant; 15.08.2014
comment
@HansPassant: файл DEF: LIBRARY user32.dll EXPORTS _MessageBoxA@16 объявление в C: EXTERNC int __stdcall MessageBoxA(void *hWnd, char *lpText, char *lpCaption, int uType); Не удается найти _MessageBoxA@16. - person Mints97; 15.08.2014
comment
@HansPassant, ты уверен? Поскольку winuser.h использует dllimport, так почему бы и OP не использовать его? (да, это ссылка на другую библиотеку, но это кажется неуместным) - person Drew McGowen; 15.08.2014
comment
Я уверен. Его .lib неполный, он также не объявляет указатель функции __imp__MessageBoxA@16. Тот, который используется, когда вы используете dllimport. - person Hans Passant; 15.08.2014
comment
@HansPassant: но я пытался также объявить __imp__MessageBoxA@16 в файле def, см. мой комментарий выше. - person Mints97; 15.08.2014

Вам нужно использовать dllimport, а не dllexport, но в этом случае вы должны полностью удалить __declspec(...).

И вам нужно указать правильное имя функции, которое MessageBoxA.

LIBRARY USER32.dll
EXPORTS
  MessageBoxA

Также было бы упущением с моей стороны не указать, что правильным основным объявлением является

int main(void)
person David Heffernan    schedule 15.08.2014
comment
@vaxquis это необходимо, чтобы избежать нежелательного украшения имени - person David Heffernan; 14.04.2016
comment
Я перечитал вопрос ОП пару раз... и я все еще думаю, что вопрос паршивый, и я до сих пор не вижу здесь никакой необходимости в DEF - ОП мог бы либо просто использовать прямое связывание DLL MinGW, не утруждая себя DEF вообще или используйте __declspec(dllimport) (или любую его замену), что позволяет импортировать внешние файлы без файлов DEF. Тем не менее, я заметил, что он использует инструменты M$, поэтому я отказываюсь от своего утверждения о том, что DEF не совсем необходим, поскольку он слишком сильный - DEF можно опустить, я думаю, что OP использовал неправильный подход, но если он настаивал< /i> при их использовании ваш ответ на 100% ОК. - person ; 14.04.2016

Я до сих пор не совсем понимаю, почему, но удаление _ с добавлением порядкового номера к имени функции в моем файле .def все исправило. Мое решение:

LIBRARY USER32.dll
EXPORTS
MessageBoxA@16 @2093

Определение функции:

#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

typedef void *PVOID;
typedef PVOID HANDLE;
typedef HANDLE HWND;
typedef const char *LPCSTR;
typedef unsigned int UINT;

EXTERNC __declspec(dllimport)
int
__stdcall
MessageBoxA(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType);
person Mints97    schedule 15.08.2014
comment
Это правильный ответ, но я смогу принять его только через 2 дня. Между тем, кто-нибудь знает, почему объявление порядкового номера было так важно? - person Mints97; 15.08.2014
comment
Это действительно правильный ответ? Если вы не понимаете, возможно, это не так. - person David Heffernan; 17.08.2014
comment
@DavidHeffernan: это правильный ответ, потому что он работает. Почему это работает - совсем другой вопрос ;) - person Mints97; 17.08.2014
comment
@ Mints97 это работает, потому что использование DEF изменяет схему искажения - person ; 14.04.2016

Эта страница указывает что winuser.h является заголовком. Отсюда видно, что используются некоторые макросы, в том числе WINUSERAPI и WINAPI. WINUSERAPI условно #define-d в начале этого заголовка. WINAPI можно найти в заголовке winbase.h, где видно, что он привязан к соглашению о вызовах, в зависимости от платформы.

Но лучший вопрос: почему вы используете dllexport, а не dllimport?

person Shao    schedule 14.08.2014
comment
WINAPI это просто stdcall: #define WINAPI __stdcall. WINUSERAPI это DECLSPEC_IMPORT, который, в свою очередь, __declspec(dllimport). Итак, то, что идет перед именем функции в определении, в основном __declspec(dllimport) int __stdcall. Я изменил dllexport на dllimport, но он все еще не работает (см. другой ответ). Однако я не уверен, что _In_opt_ и _In_ в переменных могут вызывать какие-либо проблемы... - person Mints97; 15.08.2014
comment
@Mints97 _In_opt_ и _In_ — это пустые макросы (они используются для аннотаций SAL) - person Drew McGowen; 15.08.2014
comment
+0 - хотя вы здесь совершенно правы, Шао, это не ответ сам по себе, поскольку он не касается вопроса напрямую. Вероятно, это должен быть комментарий к OP IMO. - person ; 13.04.2016