c00000fd
c00000fd

Reputation: 22265

How to statically link to a DLL function that is exported by an ordinal?

Say, if a DLL has a function:

int TestFunc01(int v)
{
    WCHAR buff[256];
    ::StringCchPrintf(buff, _countof(buff), L"You passed a value of %d", v);
    return ::MessageBox(NULL, buff, L"Test Dll Message", MB_OK | MB_ICONINFORMATION);
}

that is exported by its ordinal value only (the following is a .def file):

LIBRARY   DllName
EXPORTS
   TestFunc01   @1 NONAME

So now when I want to statically link to that function from another module, I'd do the following if the function was exported by its name:

extern "C" __declspec(dllimport) int TestFunc01(int v);

int _tmain(int argc, _TCHAR* argv[])
{
    TestFunc01(123);
}

But how do I statically link to it only by its ordinal value?

PS. I'm using a Visual Studio C++ compiler & linker.

Upvotes: 3

Views: 1607

Answers (3)

c00000fd
c00000fd

Reputation: 22265

This is not to compete with the actual answer. David Heffernan raised a good point in the comments there, about the use of dllimport. So I thought to make several tests to see what difference does it make when I use __declspec(dllimport) and when I don't:

1. x86 release build

Function declaration in the DLL:

extern "C" int __cdecl TestFunc01(int v)
{
    WCHAR buff[256];
    ::StringCchPrintf(buff, _countof(buff), L"You passed a value of %d", v);
    return ::MessageBox(NULL, buff, L"Test Dll Message", MB_OK | MB_ICONINFORMATION);
}

then import and call it from another module:

With __declspec(dllimport)

extern "C" __declspec(dllimport) int __cdecl TestFunc01(int v);

int _tmain(int argc, _TCHAR* argv[])
{
    TestFunc01(123);
}

compiled machine code:

enter image description here

The call instruction reads the function address from the Import Address Table (IAT):

enter image description here

that gives it the location of imported function:

enter image description here


Without __declspec(dllimport)

extern "C" int __cdecl TestFunc01(int v);

int _tmain(int argc, _TCHAR* argv[])
{
    TestFunc01(123);
}

compiled machine code:

enter image description here

In this case it is a relative call to a single jmp instruction:

enter image description here

which in turn reads the function address from IAT:

enter image description here

and jumps to it:

enter image description here


2. x64 release build

For 64-bit we have to change the calling convention to __fastcall. The rest stays the same:

With __declspec(dllimport)

extern "C" __declspec(dllimport) int __fastcall TestFunc01(int v);

int _tmain(int argc, _TCHAR* argv[])
{
    TestFunc01(123);
}

compiled machine code:

enter image description here

Again the call instruction reads the function address from IAT (in case of x64 it uses relative address):

enter image description here

that gives it the function address:

enter image description here


Without __declspec(dllimport)

extern "C" int __fastcall TestFunc01(int v);

int _tmain(int argc, _TCHAR* argv[])
{
    TestFunc01(123);
}

compiled machine code:

enter image description here

Again a relative call to a single jmp:

enter image description here

that in turn reads the function address from IAT:

enter image description here

and jumps to it:

enter image description here

Conclusion

So as you see, by not using dllimport you're technically incurring an additional jmp. I'm not exactly sure what is the purpose of that jump, but it is sure not to make your code run faster. Maybe it's a code maintenance thing, maybe it's a way to do hot-patching for updates, maybe a debugging feature. So if someone can shed some light on the purpose of that jump I'll be glad to hear it.

Upvotes: 0

RbMm
RbMm

Reputation: 33706

But how do I statically link to it only by its ordinal value?

absolute the same way, when function exported by name - no any difference. in both case you need 2 things - correct declaration of the function:

extern "C" __declspec(dllimport) int /*calling convention*/ TestFunc01(int v);

and lib file, included to linker input.

when you include somename.def file to visual studio project, it automatically add /def:"somename.def" option to linker (otherwise you need manual add this option). generated lib file will be containing __imp_*TestFunc01* symbol - in place * will be different decoration based on c or c++ symbol and calling convention, in case x86.

from another side, when you call function, with __declspec(dllimport) attribute. compiler (CL) will be generate call [__imp_*TestFunc01*] - so reference to __imp_*TestFunc01* symbol (again * in place actual decoration). linker will be search for __imp_*TestFunc01* symbol and found it in lib file.

the NONAME option does not matter for this process - this this only affects how it will be formed IAT/INT entry for this function in PE (will be it imported by name or ordinal)

note that if we separate generate lib file from def file only, by link.exe /lib /def:somename.def - the linker will be have not correct declarations for exported functions (def file containing name only without calling convention and c or c++ name) - so it always will be considered symbols as extern "C" and __cdecl


in concrete case visible that in dll function implemented as int TestFunc01(int v) - so without extern "C" - as result in lib file will be symbol like __imp_?TestFunc01@@YAHH@Z (i assume __cdecl and x86), but in another module function used with extern "C" - so linker will be search for __imp__TestFunc01 and of course not found it, because it not exist in lib file. because this, when we export/import some symbol - it must be equal declared for both modules. the best declare it in separate .h file with explicit calling convention

Upvotes: 3

SoronelHaetir
SoronelHaetir

Reputation: 15162

You can use the lib.exe tool to create a .lib file from a .DEF file that you write, you do not have to actually provide any object files.

You would write a .def that matches the ordinals you want to export but also provide names and then use lib.exe to create a .lib.

In code you would then declare it as: extern "C" __declspec(dllimport) ret_type funcName(arg_type);

It is important that the calling convention match whatever the function actually uses but the name would have to match what your created .lib uses even if that name doesn't follow the decoration normal for that call type.

Upvotes: 3

Related Questions