您的位置:首页 > 运维架构 > Linux

windows中dll和linux中so的动态链接库的详解

2011-07-17 18:32 806 查看
1 生成windows中静态链接的静态库
和2中linux的完全相同。

2 生成和使用linux中的.a静态链接库
如下例:
/* hellos.h */
#ifndef _HELLO_S_H
#define _HELLO_S_H

#include <stdio.h>
void printS(char* str);

#endif
输入命令:
gcc -c -o hellos.o hellos.c
ar cqs libhellos.a hellos.o

linux中.a文件的名字是有规则的lib[name].a
于是得到了libhellos.a这么一个静态链接库

2:主程序
/* main.c */
#include "hellos.h"

void main() {
char* text = "Hello World!\n";
printS(text);
}
编译链接:
gcc -o hello main.c -L. -lhellos
注意-main.c要放在-L. -lhellos前面,否则出错。

然后运行hello可以看到输出
print in static way: Hello World!
删除libhellos.a和hellos.*后, 程序仍然正常运行。
不知道g++为什么不行。

3 生成windows中的dll,并进行隐式链接使用
在上面hellos.h hellos.c hellos 三个文件的基础上。
生成dll:gcc -shared -o helloc.dll helloc.c
使用dll:gcc -o hello main.c -L. -lhellos
-----------------------------------------------------------------------------------------------------------------------------------------------------------

这样就将hellos.dll和hello链接上了。删除hellos.dll时候会报错找不到dll。
感觉这算是gcc提供的一种机制了?
由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码,此处属于其中的一种方式把。

注意:这里并没有进行windows中下面的各种复杂声明。

windows中的两种链接方法的原理性解释
链接库分为静态链接库和动态链接库,而动态链接库在使用时,又进一步分为装载时链接和运行时链接。装载时链接是指该动态链接库是在程序装入时进行加载链接的,而运行时链接是指该动态链接库是在程序运行时执行LoadLibrary(或LoadLibraryEx,下同)函数动态加载的。因此,由于动态链接库有这两种链接方式,所以在编写使用DLL的程序时,就有了两种可选方案。
可能有人会问“为什么需要装载时链接?直接静态链接不就行了吗?”,这是模块化程序设计的需要。试想,如果你开发一个很大的程序,并且经常需要更新。如果你选择静态链接,那么每次更新就必须更新整个exe文件,而如果你把需要经常更新的模块做成dll,那么只需要更新这个文件即可,每次程序运行时加载这个更新的文件即可。

另两个重要的、需要区分的概念是:对象库(Object Library)和导入库(Import Library)。对象库是指普通的库文件,比如C运行时库libc.lib;而导入库是一种比较特殊的对象库文件,与一个动态链接库相对应。它们都有后缀.lib,并且都仅在程序编译链接时使用,被链接器用来解析函数调用。然而,导入库不包含代码,它只为链接器提供动态链接库的信息,以便于链接器对动态链接库中的对象作恰当地链接。

关于__stdcall:如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用约定声明为__stdcall方式,WINAPI、CALLBACK都采用这种方式,而C/C++缺省的调用方式却为__cdecl。__stdcall方式与__cdecl对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern "C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionName@number ,而__cdecl调用约定仅在输出函数名前面加下划线,形如_functionName。
这些调用约定是给编译器使用的,用来确定生成最终生成的符号的。

这个对象库(Object Library至今不知道怎么生成)囧了,暂时先不管他。
更细可参考:http://blog.csdn.net/ljx0305/article/details/4513074

---------------------------------------------------------------------------------------------------------------------------------------------------------

4 生成windows中的dll,并进行显示加载
先写一个最简单的显示动态加载:
dlltest.h:

#ifndef _DLLTEST_H_
#define _DLLTEST_H_

#include <iostream.h>
#include <stdio.h>
#include <windows.h>

extern "C" __declspec(dllexport) void NumberList();
extern "C" __declspec(dllexport) void LetterList();

#endif

dlltest.cpp

#include "dlltest.h"

extern "C" __declspec(dllexport)
void NumberList() {

cout << "This in in NumberList" << endl;
}

extern "C" __declspec(dllexport)
void LetterList() {

cout << "This is in LetterList!" << endl;
}

usedll.cpp

#include <windows.h>
#include <iostream.h>
#include <stdio.h>
#include <conio.h>

typedef void (*cfunc)();

cfunc NumberList;
cfunc LetterList;

int main() {

HINSTANCE hLib=LoadLibrary("DLLTEST.DLL");

if(hLib==NULL) {

cout << "Unable to load library!" << endl;
return 0;
}

NumberList=(cfunc)GetProcAddress((HMODULE)hLib, "NumberList");
LetterList=(cfunc)GetProcAddress((HMODULE)hLib, "LetterList");

if((NumberList==NULL) || (LetterList==NULL)) {

cout << "Unable to load function(s)." << endl;
FreeLibrary((HMODULE)hLib);
return 0;
}

NumberList();
LetterList();

FreeLibrary((HMODULE)hLib);
return 0;

}

编译选项:
g++ -shared -o dlltest.dll dlltest.cpp //生成动态dll。使用了__declspec(dllexport)关键字将函数导出。

g++ -o usedll usedll.cpp
运行即有结果

This in in NumberList!
This is in LetterList!

下面解释一下:extern “C” 和__stdcall ,__cdecl存在的原因
说的简单点就是为了让名字清晰,可以被使用dll的人正确找到函数。可以使用VC带的Dependency工具查看dll中的export的函数名字。
1 默认c语言在编译时候,使用__cdecl方式进行编译,这个convention包括很多东西,寄存器,堆栈,命名等。这里编译时候c不改变函数的名字。
所以在动态寻找函数名字时候能够正确的找到。
2 Win32中都使用__stdcall,这样的话命名方式与__cdecl有些不同,如果不使用def文件,而使用__declspec(dllexport)导出的话,上面dlltest.cpp中的2个函数
会变成
NumberList@0
[code]LetterList@0
这样如果在usedll的代码中仍然使用NumberList去找的话,就会找不到。
3 extern “C” 这是为了避免c++对c中的命名的更改。
因为c++中支持
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:  void foo( int x, int y );  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangledname”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y)编译生成的符号是不相同的,后者为_foo_int_float。
所以名字也会发生变化,这样我们也会找不到对应的函数,于是加上extern “C”告诉c++编译器按照c语言的方式去编译!

然后其实使用的编译器g++/gcc,是否定义def文件,dll中的调用方式,caller中的调用方式都会影响到调用是否成功。
水木上总结的是:
测试环境 vc6.0绿色版
新建工程 testdll(win32 dynamic lib)用来生成dll
新建工程 testmydll(win32 console app),显示调用dll

生成dll文件testdll.cpp
测试dll文件testmydll.cpp
采用depends观察dll的输出函数名

1 使用_declspec(dllexport)关键字,C编译,_cdecl
导出函数名 fnTestdll _cdecl调用成功, __stdcall调用失败

2 使用_declspec(dllexport)关键字,C编译,_stdcall
导出函数名 _fnTestdll@4 _cdecl调用失败, __stdcall调用失败

3 使用_declspec(dllexport)关键字,C++编译,_cdecl
导出函数名 ?fnTestdll@@YAHH@Z _cdecl调用失败, __stdcall调用失败

4.使用_declspec(dllexport)关键字,C++编译,_stdcall
导出函数名 ?fnTestdll@@YGHH@Z _cdecl调用失败, __stdcall调用失败

5.使用DEF文件 , C编译, _cdecl
导出函数名 fnTestdll, _cdecl调用成功, __stdcall调用失败

6.使用DEF文件, C编译, _stdcall
导出函数名 fnTestdll, _cdecl调用失败, __stdcall调用成功

7.使用DEF文件 , C++编译, _cdecl
导出函数名 fnTestdll, _cdecl调用成功, __stdcall调用失败

8.使用DEF文件, C++编译, _stdcall
导出函数名 fnTestdll, _cdecl调用失败, __stdcall调用成功

结论: 尽可能还是用DEF文件定义输出函数吧:)
特别是需要给非C++程序调用(使用__stdcall)的时候.;

5 linux中隐式和显式调用SO共享文件(未完待续...累了,睡觉去)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: