Как связать код C, содержащий WinAPI?

Как я могу связать код C, который содержит вызовы WinAPI? При подключении получаю следующую ошибку:

[Ошибка dcc32] Project1.dpr(16): E2065 Неудовлетворительное предварительное или внешнее объявление: '__imp__GetCurrentThreadId@0'

Рассмотрим следующий пример.

Дельфи:

program Project1;

uses
  Windows;

{$L C:\Source.obj}

function Test: DWORD; cdecl; external name '_Test';

begin
  WriteLn(Test);
end.

C:

#include <Windows.h>

DWORD Test(void)
{
   return GetCurrentThreadId();
}

person user15124    schedule 22.10.2015    source источник


Ответы (1)


Это происходит потому, что заголовочные файлы Windows обычно используют __declspec(dllimport) при объявлении функций. Для рассматриваемой функции ее определение в WinBase.h таково:

WINBASEAPI
DWORD
WINAPI
GetCurrentThreadId(
    VOID
    );

Когда вы расширяете все макросы и переформатируете, это становится:

__declspec(dllimport) DWORD __stdcall GetCurrentThreadId(void);

Теперь использование __declspec(dllimport) и __stdcall сообщает компоновщику, что декорированное имя функции — __imp__GetCurrentThreadId@0. Ожидается, что вы предоставите эту функцию в библиотеке импорта, поставляемой с SDK. Вы не можете сделать это в Delphi, потому что он этого не принимает. У вас есть множество вариантов. Наиболее очевидным является реализация функции в коде Delphi. Но это довольно сложно сделать, потому что имя невыразимо. Вы не можете дать функции Delphi такое имя.

Вы можете отказаться от заголовочных файлов Windows в своем коде C и заменить их своими вариантами, которые включают в себя только то, что вам нужно в плане типов и функций. И определите функции без использования __declspec(dllimport). Например:

С

typedef unsigned long DWORD; // taken from the Windows header files

DWORD GetCurrentThreadId(void); // this is implemented in the Delphi code to which you link

DWORD MyGetCurrentThreadId(void)
{
   return GetCurrentThreadId();
}

Дельфи

{$APPTYPE CONSOLE}

uses
  Winapi.Windows;

{$LINK MyGetCurrentThreadId.obj}

function _GetCurrentThreadId: DWORD; cdecl;
begin
  Result := Winapi.Windows.GetCurrentThreadId;
end;

function MyGetCurrentThreadId: DWORD; cdecl; external name '_MyGetCurrentThreadId';

begin
  Writeln(MyGetCurrentThreadId);
  Readln;
end.

Это не очень весело. Но я не вижу особой альтернативы. Декорация __stdcall добавит @XX к именам ваших функций, и, насколько мне известно, вы не можете реализовать такие функции в Delphi из-за невыразимого символа @.

Очевидно, что в вашем реальном коде вы поместите объявления типов и функций в заголовочный файл, который можно будет использовать вместо заголовочных файлов Windows.


Вы можете избежать всего этого беспорядка, пост-обработав объектные файлы. Я не знаю, существует ли инструмент, но вы могли бы обработать объектный файл, чтобы заменить ссылки на __imp__GetCurrentThreadId@0 ссылками на GetCurrentThreadId, тогда жизнь была бы проще. Компоновщик Delphi будет искать это имя функции и найдет его в Winapi.Windows.


В комментариях вы показали, как использовать инструмент objconv от Agner Fog, чтобы сделать именно это. Это работает следующим образом:

С

#include <Windows.h>

DWORD MyGetCurrentThreadId(void)
{
   return GetCurrentThreadId();
}

Компиляция кода C

cl /c MyGetCurrentThreadId.c

Постобработка файла .obj для удаления имен

objconv -nr:__imp__GetCurrentThreadId@0:GetCurrentThreadId MyGetCurrentThreadId.obj MyGetCurrentThreadId_undecorated.obj

Дельфи

{$APPTYPE CONSOLE}

uses
  Winapi.Windows;

{$LINK MyGetCurrentThreadId_undecorated.obj}

const
   _GetCurrentThreadId: function: DWORD; stdcall = Winapi.Windows.GetCurrentThreadId;

function MyGetCurrentThreadId: DWORD; cdecl; external name '_MyGetCurrentThreadId';

begin
  Writeln(MyGetCurrentThreadId);
  Readln;
end.

По неизвестным мне причинам я не могу убедить компоновщика выбрать Winapi.Windows.GetCurrentThreadId напрямую. Если я уберу GetCurrentThreadId, а не _GetCurrentThreadId, и удалю const, то программа скомпилируется и скомпонуется. Но выдает нарушение прав доступа во время выполнения. В любом случае, этот трюк, предложенный @user15124, обеспечивает управляемый обходной путь.

person David Heffernan    schedule 22.10.2015
comment
Что это обозначает? Какую функцию Delphi вы имеете в виду? Помните, что у вас нет доступа к компоновщику C++. Это не в игре здесь. Я не вижу никакого способа обойти @ украшение __stdcall API-функций. Вы знаете, как объявить функцию Delphi с @ в имени? - person David Heffernan; 22.10.2015
comment
Я думаю, вам нужно лучше понять процесс компоновки C++. Если вы включаете заголовочные файлы WIndows как есть, то они просто определяют функции. Реализации предоставляются во время ссылки. Либо фактическими реализациями в одном из файлов obj, либо в файле lib (статическом или импортированном). Но вы не можете повлиять на оформление функций, определенных в заголовочных файлах Windows. Вы просто не можете остановить добавление этого @. При компоновке в Delphi вы должны реализовать эту функцию. Как реализовать функцию с @ в имени? - person David Heffernan; 22.10.2015
comment
Мне удалось разобрать его с objconv.exe с objconv -nr:__imp__GetCurrentThreadId@0:GetCurrentThreadId source.obj source2.obj - person user15124; 22.10.2015
comment
Он изменяет символ. - person user15124; 22.10.2015
comment
Ты не можешь быть точным? Какой символ? Этот вопрос касается деталей. - person David Heffernan; 22.10.2015
comment
Он преобразует __imp__GetCurrentThreadId@0 в GetCurrentThreadId, который затем я могу связать. Однако затем приложение аварийно завершает работу при обращении к Test() agner.org/optimize. - person user15124; 22.10.2015
comment
Я добавил постскриптум по этому поводу. Какой именно инструмент вы бы использовали, который мог бы это сделать, во многом зависит от используемого вами компилятора, формата obj и т. д. Чего я не знаю. В любом случае, если такого инструмента не существует, вы наверняка могли бы его написать. - person David Heffernan; 22.10.2015
comment
О, это не objtool, это objconv. Наверное, тогда я зря потратил время на поиски objtool. - person David Heffernan; 22.10.2015
comment
Это нужно только для x32! Работает без патчей на x64! :) Хотя еще можно. - person user15124; 22.10.2015
comment
Предположительно на х64 украшение не добавляет несказанного @ и поэтому можно использовать функцию редиректа? Хотя я предполагаю. Немного подробностей в ваших комментариях было бы неплохо. Это мой вам совет. Будьте более внимательны к деталям. Пожалуйста. Я напишу трюк objconv в своем ответе. - person David Heffernan; 22.10.2015
comment
Я тестировал Clang для VS2015, он отлично работает с Delphi. - person user15124; 22.10.2015
comment
Опять же, я не знаю, к чему это относится. Здесь не хватает вашей точности и внимания к деталям. - person David Heffernan; 22.10.2015
comment
Статическая связь с Delphi и последней версией Clang SVN для Visual Studio 2015 работает хорошо. - person user15124; 22.10.2015
comment
Кстати этот синтаксис проще. const _GetCurrentThreadId: Pointer = @Winapi.Windows.GetCurrentThreadId; - person user15124; 22.10.2015