1 生成windows中静态链接的静态库
和2中linux的完全相同。
2 生成和使用linux中的.a静态链接库。
如下例:
/* hellos.h */
#ifndef _HELLO_S_H
#define _HELLO_S_H
#include
void printS(char* str);
#endif
输入命令:
gcc -c -o hellos.o hellos.c
ar cqs libhellos.a hellos.o
于是得到了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的程序时,就有了两种可选方案。
另两个重要的、需要区分的概念是:对象库(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
#include
#include
extern "C" __declspec(dllexport) void NumberList();
extern "C" __declspec(dllexport) void LetterList();
#endif
#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;
}
#include
#include
#include
#include
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++ -o usedll usedll.cpp
运行即有结果
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
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共享文件(未完待续...累了,睡觉去)