链接过程函数名称改写问题汇总

简介

  • 由于实习时候的疏忽,在使用导出的模块时,发生了大量的LINK ERROR错误,耽误了很长时间非常影响工作进度,所以在此进行函数名称导出时候的问题汇总

链接过程

  • 历史上由于程序员最初编写代码时,是通过在卡片上打孔的方式,所以一旦在其中的增加一行代码或减少一行代码便会导致跳转地址出现错乱,为此人们便想到用符号去代替过程地址,这样便不需要在编写代码的时候去关心该过程的地址是多少,而是把符号转化为地址的过程委托给链接器去执行。换句话说,极大的解放了生产力!——《程序员的自我修养》

符号代替地址的方式除了解决地址修改的麻烦之外,还使得程序出现了一种新式的编程思想——模块化编程。人们可以对程序的各个功能进行拆分形成多个功能模块,保持整个程序的低耦合,如此一来在修改某个功能的时候,我们只需要在其单独的功能模块中去定位和修改代码即可,而不必去修改整个程序。但是这也引出了一个新的问题——各个模块在进行相互调用的时候如何去找到对应的符号地址呢?



下面引用《程序员的自我修养》中的一张图

程序链接示意图

程序链接示意图

模块就像是每个拼图一般,从模块中导出的符号名称就是拼图凸出的部分,从其它模块中导入的部分凹进的部分。但是凸出和凹进的形状必须一致,这也就是在编译期间对函数名称改写的规则必须一致的原因。否则便会发生LINK ERROR~


extern “C”关键字

该关键字通常出现在CPP代码中,在C代码中不会出现。由于CPP的函数重载特性,所以通常会将函数名和参数列表一起进行改写,这在链接的时候会导致很严重的后果——符号名称找不到(LINK ERROR)。举个例子,如果你的DLL模块是用CPP代码构建的,你的exe程序是用C代码构建的



1)dll模块代码(cpp风格改写)

// This is Module.cpp --> Module.dll
_declspec(dllexport) int TestFunc();

int TestFunc()
{
    return 0;
}


2)dll模块代码(c风格)
这里由于我使用的是VS,不支持c文件,所以在代码中加上了extern "C"表明这是c风格的函数

// This is Module.cpp --> Module.dll
extern "C" _declspec(dllexport) int TestFunc();

int TestFunc()
{
    return 0;
}

3)我们借助Dllview.exe(一个小工具,直接百度搜索)来查看.dll模块中导出的函数名

dll模块(cpp风格改写)

Cpp_cdecl

dll模块(c风格)

C__cdecl

4)exe程序代码(c风格)
这里由于我使用的是VS,不支持c文件,所以在代码中加上了extern "C"表明这是C风格的函数

// This is Module.cpp --> Module.dll
extern "C" _declspec(dllimport) int TestFunc();

int main(int argc, char *argv[])
{
    TestFunc();
    return 0;
}

5)我们通过VS工具包下的dumpbin来查看.obj文件中需要引用的导入符号名称命令如下

# %obj_path% 是你的.obj文件路径
dumpbin /all %obj_path%

import表

可以看到exe程序需要的符号名称是__imp__TestFunc,前面的__imp__表明这是一个从dll导入的符号,后面的是符号名称,因此只有当我们的C风格的程序使用c风格的dll模块里,链接过程才能正确执行!!又或者你可以保证dll模块导出的函数和exe程序中的导入的函数都是Cpp风格,才行!!!


调用约定

  • 除了我们上面提到的extern "C"关键字会对是否改写函数名称产生影响外,编译器的调用约定,也会对函数名称改写产生影响,废话不多说我们直接来实践一下就可以(关于调用约定,想要了解更多自行Google百度

Cpp风格中的调用约定

1)首先是Cpp风格__cdecl调用约定,我们的Module.cpp代码如下

// This is Module.cpp --> Module.dll
_declspec(dllexport) int __cdecl TestFunc();

int __cdecl TestFunc()
{
    return 0;
}

Cpp_cdecl



2)Cpp风格__stdcall调用约定,代码如下

// This is Module.cpp --> Module.dll
_declspec(dllexport) int __stdcall TestFunc();

int __stdcall TestFunc()
{
    return 0;
}

Cpp__stdcall



3)Cpp风格__fastcall调用约定

// This is Module.cpp --> Module.dll
_declspec(dllexport) int __fastcall TestFunc();

int __fastcall TestFunc()
{
    return 0;
}

Cpp__fastcall



4)Cpp风格__vertorcall调用约定

// This is Module.cpp --> Module.dll
_declspec(dllexport) int __vertorcall TestFunc();

int __vertorcall TestFunc()
{
    return 0;
}

Cpp__vectorcall

C风格的调用约定

1)C风格__cdecl调用约定

// This is Module.cpp --> Module.dll
extern "C" _declspec(dllexport)  __cdecl TestFunc();

int __cdecl TestFunc()
{
    return 0;
}

C__cdecl

2)C风格__stdcall调用约定

// This is Module.cpp --> Module.dll
extern "C" _declspec(dllexport) __stdcall TestFunc();

int __stdcall TestFunc()
{
    return 0;
}

C__stdcall

3)C风格__fastcall调用约定

// This is Module.cpp --> Module.dll
extern "C" _declspec(dllexport) __fastcall TestFunc();

int __fastcall TestFunc()
{
    return 0;
}

C__fastcall

4)C风格__vectorcall调用约定

// This is Module.cpp --> Module.dll
extern "C" _declspec(dllexport) __vectorcall TestFunc();

int __vectorcall TestFunc()
{
    return 0;
}

C__vectorcall

总结

  • 看了这么多改写函数的方式,是否感觉到头晕了呢,其实我们在使用的使用并不需要知道函数名称具体会改写成什么样子,但是我在使用libdll中的函数时,需要仔细去确保dll工程exe工程的几个项目配置一致

VS 调用约定

VS 附加库目录

VS 附加依赖项

猜你喜欢

转载自blog.csdn.net/Pig_Pig_Bang/article/details/81532106
今日推荐