如何link包含WinAPI的C代码?

How to link C code that contains WinAPI?

如何 link 包含调用 WinAPI 的 C 代码? linking 时出现以下错误:

[dcc32 Error] Project1.dpr(16): E2065 Unsatisfied forward or external declaration: '__imp__GetCurrentThreadId@0'

考虑以下示例。

Delphi:

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();
}

这是因为 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 函数指定该名称。

您可以避开 C 代码中的 Windows 头文件,并将它们替换为您自己的变体,这些变体仅包含您需要的类型和函数。并在不使用 __declspec(dllimport) 的情况下定义函数。例如:

C

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();
}

Delphi

{$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 中实现这样的函数,因为 [=24] 是不可描述的字符=].

显然,在您的实际代码中,您会将类型和函数声明放入一个头文件中,该头文件可用于代替 Windows 头文件。


您可以通过 post 处理目标文件来避免所有这些混乱。我不知道是否存在工具,但您可以处理目标文件以将对 __imp__GetCurrentThreadId@0 的引用替换为对 GetCurrentThreadId 的引用,这样生活就会变得简单。 Delphi 链接器将查找该函数名称并在 Winapi.Windows 中找到它。


在评论中,您展示了如何使用 Agner Fog's objconv tool 来做到这一点。它是这样运行的:

C

#include <Windows.h>

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

C代码编译

cl /c MyGetCurrentThreadId.c

Post-处理 .obj 文件以取消修饰名称

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

Delphi

{$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 建议的这个技巧提供了一个易于管理的解决方法。