【问题标题】:How to link C code that contains WinAPI?如何链接包含 WinAPI 的 C 代码?
【发布时间】:2015-10-22 10:41:30
【问题描述】:

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

[dcc32 错误] Project1.dpr(16): E2065 Unsatisfied forward or external declaration: '__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();
}

【问题讨论】:

    标签: delphi delphi-xe8


    【解决方案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 函数起这个名字。

    您可以避开 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();
    }
    

    德尔福

    {$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 中找到它。


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

    C

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

    【讨论】:

    • 这是什么意思?您想到了哪些 Delphi 功能?请记住,您无权访问 C++ 链接器。它在这里没有发挥作用。我看不到 @ 装饰 __stdcall API 函数的任何方式。你知道如何声明一个名称中带有@ 的Delphi 函数吗?
    • 我认为您需要更好地了解 C++ 链接器过程。如果您按原样包含 WIndows 头文件,那么它们只是定义函数。在链接时提供实现。通过其中一个 obj 文件或 lib 文件(静态或导入)中的实际实现。但是 Windows 头文件中定义的函数的修饰不是您可以影响的。您根本无法阻止添加@。在 Delphi 中链接时,您必须实现该功能。如何实现名称中带有@ 的函数?
    • 我确实设法用objconv.exeobjconv -nr:__imp__GetCurrentThreadId@0:GetCurrentThreadId source.obj source2.obj 解开了它
    • 我为此添加了一个附言。您将使用什么工具可以做到这一点在很大程度上取决于您使用的编译器、obj 格式等。我不知道。不管怎样,如果没有这样的工具,你肯定会写的。
    • 大概在 x64 上,装饰不会添加无法形容的@,所以您可以使用函数重定向?不过我猜。您的 cmets 中的更多细节会很好。这是我给你的建议。更加注意细节。请。我会在我的回答中写下 objconv 技巧。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2010-09-18
    • 2018-08-06
    相关资源
    最近更新 更多