user15124
user15124

Reputation: 299

How to link C code that contains WinAPI?

How can I link C code which contains calls to WinAPI? While linking I get the following error:

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

Consider the following example.

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

Upvotes: 4

Views: 577

Answers (1)

David Heffernan
David Heffernan

Reputation: 613511

This is happening because the Windows header files typically use __declspec(dllimport) when they declare functions. For the function in question, its definition in WinBase.h is:

WINBASEAPI
DWORD
WINAPI
GetCurrentThreadId(
    VOID
    );

When you expand all macros, and re-format that becomes:

__declspec(dllimport) DWORD __stdcall GetCurrentThreadId(void);

Now, the use of __declspec(dllimport), and __stdcall, tells the linker that the decorated name of the function is __imp__GetCurrentThreadId@0. You are expected to provide that function in the import library provided with the SDK. You cannot do that in Delphi because it does not accept it. You have a variety of options. The most obvious is to implement the function in the Delphi code. But that is pretty tricky to do because the name is unspeakable. You cannot give a Delphi function that name.

You could shun the Windows header files in your C code and replace them with your own variants that include just what you need in way of types and functions. And define the functions without using __declspec(dllimport). For example:

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.

This is not much fun. But I don't see much alternative. The __stdcall decoration is going to place @XX suffices on your function names, and to the very best of my knowledge you cannot implement such functions in Delphi, because of the unspeakable character @.

Obviously in your real code you'd place the type and function declarations into a header file that could be used in place of the Windows header files.


You could avoid all of this mess by post-processing the object files. I don't know if a tool exists, but you could process the object file to replace references to __imp__GetCurrentThreadId@0 with references to GetCurrentThreadId then life would be simple. The Delphi linker would look for that function name and find it in Winapi.Windows.


In the comments you have shown how to use Agner Fog's objconv tool to do exactly this. It runs like this:

C

#include <Windows.h>

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

Compilation of C code

cl /c MyGetCurrentThreadId.c

Post-processing of .obj file to undecorate names

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.

For reasons unknown to me, I cannot persuade the linker to pick up Winapi.Windows.GetCurrentThreadId directly. If I undecorate to GetCurrentThreadId rather than _GetCurrentThreadId, and remove the const, then the program compiles and links. But throws an access violation at runtime. Anyway, this trick as suggested by @user15124 provides a manageable workaround.

Upvotes: 5

Related Questions