windows 中如何生成 静态库(.a、.lib)和动态库(.so、.dll),又是如何 Invoke DLL/lib 详解

 http://blog.163.com/dingmz_frcmyblog/blog/static/21730402320137710164653/

VC6.0开发DLL动态链接库  

2013-08-08 00:00:13|  分类: VC-MFC资料 |  标签:dll动态链接库  vcdll  vc开发动态库   |举报|字号 订阅
使用DLL的好处在于:
1、可以采用多种编程语言来编写。
2、增强产品的功能。
3、提供二次开发的平台。
4、简化项目管理。
5、可以节省磁盘空间和内存。
6、有助于资源的共享。
7、有助于实现应用程序的本地化。
--------------------------------------------------------------------------------------
 
   常规DLL又可细分成静态链接到MFC和动态链接到MFC上的,这两种常规DLL的区别将在下面介绍。与常规DLL相比,使用扩展DLL用于导出增强MFC基础类的函数或子类,用这种类型的动态链接库,可以用来输出一个从MFC所继承下来的类。

   扩展DLL:是使用MFC的动态链接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。例如你已经创建了一个从MFC的CtoolBar类的派生类用于创建一个新的工具栏,为了导出这个类,你必须把它放到一个MFC扩展的DLL中。扩展DLL 和常规DLL不一样,它没有一个从CWinApp继承而来的类的对象,所以,开发人员必须在DLL中的DllMain函数添加初始化代码和结束代码。

    在Visual C++6.0开发环境下,打开FileNewProject选项,可以选择Win32 Dynamic-Link Library或MFC AppWizard[dll]。来以不同的方式来创建Non-MFC Dll、Regular Dll、Extension Dll等不同种类的动态链接库。

VC6.0创建DLL动态库

1. Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库
关于DLL入口函数 - DllMain()
    每一个DLL必须有一个入口点,这就象我们用C编写的应用程序一样,必须有一个WINMAIN函数一样。在Non-MFC DLL中DllMain是一个缺省的入口函数,你不需要编写自己的DLL入口函数,用这个缺省的入口函数就能使动态链接库被调用时得到正确的初始化。如果应用程序的DLL需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除操作时,需要在相应的DLL工程的.CPP文件中对DllMain()函数按照下面的格式书写。
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) 
{   
    switch( ul_reason_for_call )     
    {       
        case DLL_PROCESS_ATTACH:        
        case DLL_THREAD_ATTACH:        
        case DLL_THREAD_DETACH:         
        case DLL_PROCESS_DETACH:     
    }    
    return TRUE; 
} 
参数:
hMoudle是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);
ul_reason_for_call是一个说明动态库被调原因的标志,当进程或线程装入或卸载动态链接库的时候,操作系统调用入口函数,并说明动态链接库被调用的原因,它所有的可能值为:
DLL_PROCESS_ATTACH: 进程被调用
DLL_THREAD_ATTACH: 线程被调用
DLL_PROCESS_DETACH: 进程被停止
DLL_THREAD_DETACH: 线程被停止
lpReserved为保留参数。
DllMain只要这样写就好了,因为他只是动态库被加载时进行一些必要的初始化而已!
-------------------------------------------------------------------------------------------------------------
到此为止,DLL的入口函数已经写了,剩下部分的实现也不难,你可以在DLL工程中加入你所想要输出的函数或变量了。

说明:其实在开发DLL时这个入口函数并非必须的,完全可以不使用!这里只是简单的介绍一下DllMain函数。一下内容可以完全忽略这个函数。
-------------------------------------------------------------------------------------------------------------

DLL动态链接库实现
按照库函数导出方式的不同,可将实现方式分为一下两种:
1、在定义函数时使用导出关键字_declspec(dllexport)。
2、在创建DLL文件时使用模块定义文件.def。

下面通过两个例子来说明如何使用这两种方法创建DLL文件:
1)使用导出函数关键字_declspec(dllexport)创建MyDll.dll
该动态链接库中有两个函数,分别用来实现得到两个数的最大和最小数。在MyDll.h和MyDLL.cpp文件中分别输入如下原代码: 
MyDll.h 
#ifdef DLL_API
#else
#define DLL_API extern "C" _declspec(dllimport)
#endif

DLL1_API int add(int a,int b);
DLL1_API int subtract(int a,int b);
注意:每个函数前面必须加上关键字 - extern "C",只是在编译此库工程时告知编译器按照C函数编译方式编译此函数。这也是为了防止编译C++源文件时对函数名进行名字改编(也成为名字粉碎)。而在使用此库中函数的项目中也要使用extern "C"关键字进行声明!以说明声明的函数是按照C方式编译的函数。例如,在不加extern "C"关键词时,函数add、subtract导出的名字为:
VC6.0开发DLL动态链接库 - dingmz_frc - dingmz_frc的博客
 而是用extern "C"之后:
VC6.0开发DLL动态链接库 - dingmz_frc - dingmz_frc的博客
上图就是C++对函数名进行名字改编之后的效果,而是用extern "C"可以防止改编! 

MyDll.cpp 
#define DLL_API extern "C" _declspec(dllexport)
#include "MyDll.h"

int add(int a,int b)
{
    return a+b;
}

int subtract(int a,int b)
{
    return a-b;
}
说明:这是使用_declspec(dllexport)导出函数的标准形式,之所以定义DLL_API 宏名,只是为了既能够在本DLL中使用_declspec(dllexport)导出函数,也能在调用次库中函数的客户端程序中导入函数。因为,在MyDll.cpp 中定义了DLL_API,所以此时DLL_API就是_declspec(dllexport),而在使用此DLL的客户端中没有定义DLL_API,所以DLL_API就是_declspec(dllimport)。

注意:此例中使用extern “C”防止名字改编并不是一定可以防止的,在函数调用约定改变的情况下,导出函数的名字还是会改变。例如,在add、substract函数名之前加上_stdcall关键字即可改变函数调用约定,此时即使使用了extern "C"关键字,add、substract函数名字也会改变:
 VC6.0开发DLL动态链接库 - dingmz_frc - dingmz_frc的博客
 
关于“调用约定”请详见:《调用约定 - stdcall、cdecl 、CALLBACK、WINAPI等》

这样编译完此DLL工程即可添加到客户端程序中使用了,在客户端程序需要使用此库的源文件中加入#include "MyDll.h",在项目属性中加入MyDll.lib导入库路径,并将MyDll.dll放置到系统能够搜索到的目录下!

-------------------------------------------------------------------------------------------------------
2)用.def文件创建工程MyDll
重新建立一个DLL工程MyDll2同时往该工程中加入一个文本文件,命名为MyDll2.def,再在该文件中加入如下代码:
LIBRARY MyDll2

EXPORTS
add
subtract
LIBRARY MyDll2定义DLL内部库名,一般必须和工程名相同,但是这行在.def文件中比不是必须的,可不加。
EXPORTS下面即是所有需要导出的函数名称。

MyDll2.cpp文件:
int _stdcall add(int a,int b)
{
    return a+b;
}

int _stdcall subtract(int a,int b)
{
    return a-b;
}
    使用.Def文件就可以解决调用约定改变时extern ”C“无法防止函数名字改编的缺点,我们发现.Def文件中指定导出函数名即是库函数最终导出的格式,而无论add、substract前是否加了_stdcall !

    关于如何在客户端程序中使用MyDll2中定义的函数,其方法与MyDll一样,只要加入MyDll2.lib、MyDll2.dll,并在客户端程序源文件中声明函数即可:
extern "C" _declspec(dllimport) int add(int a,int b);
extern "C" _declspec(dllimport) int substract(int a,int b);
这也说明了,不论是用 .def 文件导出还是用 __declspec(dllexport) 关键字导出,而在客户程序使用库函数时,__declspec(dllimport) 关键字声明库函数的方式均有效!
################
到此两种实现DLL动态链接库的方式都介绍完了。而对于创建DLL动态库实现工程中可能还是存在一些疑问,这里做一下总结:
1、编译得到DLL及LIB(导入库),在客户端程序使用声明所需调用库函数时也可以直接写成以下形式:
extern add(int a, int b);
extern substract(int a, int b);
这与使用_declspec(dllimport)方式有什么区别吗?
答:其实区别也比较明显。使用extern方式只是一般的想编译器说明此函数是外部函数,而使用_declspec(dllimport)则直接告诉编译器此函数是由一个DLL动态库提供,这样在编译时会产生最佳的代码!
    此外,对于DLL库函数或全局变量使用extern是可行的的,但是比起使用_declspec(dllimport)效率上还是差了不少,毕竟知道来源才能有更快捷的方式去获取!
    关于_declspec(dllimport)作用详见:http://blog.csdn.net/mniwc/article/details/7993361。

2、.Def文件的实际实用意义在哪里?我们知道使用.Def文件也是为了导出库函数及变量,但是如果我们直接在DLL工程.cpp源文件中直接在函数名前加上_declspec(dllexport)关键字即能正确导出函数了,也就是在编译DLL是其实只要一个cpp文件即可,.Def与.h都不是必须的,例如只要:
 _declspec(dllexport) int _stdcall add(int a,int b) 
{
    return a+b;
}

_declspec(dllexport) int _stdcall subtract(int a,int b)
{
    return a-b;
}

即可实现函数的导出。

答:    确实如此,但是在使用时我们会发现,.h头文件的存在是为了很方便的在DLL工程及客户端代码中使用库函数的导出、导入声明(正如之前提到定义DLL_API 宏名的用意)。
    而.Def文件的作用也很明显,其最大的作用即在于:
    1)规范导出函数名字的格式,避免C++等高级语言对函数名字的改编,已实现此库中函数可以被多种不同语言调用,比使用extern "C"方式更合适。
    2)为库函数起别名。
关于.Def文件作用详见:《动态库.Def文件作用》


2.MFC AppWizard[dll]方式生成常规/扩展DLL

在MFC AppWizard[dll]下生成DLL文件又有三种方式,在创建DLL是,要根据实际情况选择创建DLL的方式:
1、常规DLL静态链接到MFC。
2、常规DLL动态链接到MFC。
两者区别:前者使用的是MFC的静态链接库,生成的DLL文件长度大,一般不使用这种方式,后者使用MFC的动态链接库,生成的DLL文件长度小;动态链接到MFC的规则DLL所有输出的函数应该以如下语句开始:    
AFX_MANAGE_STATE(AfxGetStaticModuleState( )) //此语句用来正确地切换MFC模块状态 

3、MFC扩展DLL,这种DLL特点是用来建立MFC的派生类,Dll只被用MFC类库所编写的应用程序所调用。
前面我们已经介绍过,Extension DLLs 和Regular DLLs不一样,它没有一个从CWinApp继承而来的类的对象,编译器默认了一个DLL入口函数DLLMain()作为对DLL的初始化,你可以在此函数中实现初始化,代码如下: 
BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDll,DWORD reason ,LPVOID flmpload) 
{ 
    switch(reason) 
    {
        //……………//初始化代码; 
    } 
    return true; 
}  
参数:
hinstDll 存放DLL的句柄。
reason 指明调用函数的原因。
lpReserved 是一个被系统所保留的参数。对于隐式链接是一个非零值,对于显式链接值是零。

在MFC下建立DLL文件,会自动生成def文件框架,其它与建立传统的Non-MFC DLL没有什么区别,只要在相应的头文件写入关键字_declspec(dllexport)函数类型和函数名等,或在生成的def文件中EXPORTS下输入函数名就可以了。需要注意的是在向其它开发人员分发MFC扩展DLL 时,不要忘记提供描述DLL中类的头文件以及相应的.LIB文件和DLL本身,此后开发人员就能充分利用你开发的扩展DLL了。
View Code

 

 

c++ 动态库、静态库   extern "C" __declspec(dllexport) __declspec(dllimport) 和 def

 

平台:WINDOWS

分2大类:

一种是:MFC AppWizard(DLL)  包含了一些MFC需要的头文件

   有DECLARE_MESSAGE_MAP 消息映射,一些MFC消息机制,        

   有def 文件,里面有接口名

        》》》》》用的不多,也不多说了。

 

 

另一种:win32 (Static Library)

    又分为1.win32 static Library

                  和Linux 一样

       

 

 

 

      2.win 32 Dynaminc-Link Library

 

      InvokeDLLwin32.h /InvokeDLLwin32.cpp

// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the INVOKEDLLWIN32_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// INVOKEDLLWIN32_API functions as being imported from a DLL, wheras this DLL sees symbols
// defined with this macro as being exported.
#ifdef INVOKEDLLWIN32_EXPORTS
#define INVOKEDLLWIN32_API __declspec(dllexport)
#else
#define INVOKEDLLWIN32_API __declspec(dllimport)
#endif

// This class is exported from the InvokeDLLwin32.dll
class INVOKEDLLWIN32_API CInvokeDLLwin32 {
public:
    CInvokeDLLwin32(void);
    // TODO: add your methods here.
};

extern INVOKEDLLWIN32_API int nInvokeDLLwin32;

INVOKEDLLWIN32_API int fnInvokeDLLwin32(void);
View Code
// InvokeDLLwin32.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "InvokeDLLwin32.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}


// This is an example of an exported variable
INVOKEDLLWIN32_API int nInvokeDLLwin32=0;

// This is an example of an exported function.
INVOKEDLLWIN32_API int fnInvokeDLLwin32(void)
{
    return 42;
}

// This is the constructor of a class that has been exported.
// see InvokeDLLwin32.h for the class definition
CInvokeDLLwin32::CInvokeDLLwin32()
{ 
    return; 
}
View Code

 

 

编写win 32 Dynaminc-Link Library的时候需要注意

1.VC 动态库 入口点 的定义
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

 

2.INVOKEDLLWIN32_EXPORTS 宏用于判断是接口导出,还是导入,

#ifdef INVOKEDLLWIN32_EXPORTS
#define INVOKEDLLWIN32_API __declspec(dllexport)
#else
#define INVOKEDLLWIN32_API __declspec(dllimport)
#endif

库的工程中 project ->Setting->c/c++  [Preprocessor definitions:] 可以看到这个宏,是定义在工程属性中。

而导出__declspec(dllexport)   与导入__declspec(dllimport) 函数的调用约定,下面有详细说明。

 

 

3.     .h文件中  声明      变量与函数 

extern INVOKEDLLWIN32_API int nInvokeDLLwin32; //声明一个变量

INVOKEDLLWIN32_API int fnInvokeDLLwin32(void);  //声明一个函数

 


4.    .cpp 中的实现
// This is an example of an exported variable
INVOKEDLLWIN32_API int nInvokeDLLwin32=0;

// This is an example of an exported function.
INVOKEDLLWIN32_API int fnInvokeDLLwin32(void)
{
    return 42;
}

// This is the constructor of a class that has been exported.
// see InvokeDLLwin32.h for the class definition
CInvokeDLLwin32::CInvokeDLLwin32()
{
    return;
}

======================================

 

平台: unix like

 http://www.cnblogs.com/scotth/p/3977928.html

 

 

 

==============================

C++中的函数被编译成汇编代码的时候,必须遵循一定的规范,如参数怎么传递,栈指针怎么增减。Visual C++中,一共有5种情况:

  • __cdecl
  • __stdcall
  • __fastcall
  • __thiscall

    默认情况下,是__cdecl。__cdecl 和__stdcall的区别是:__cdecl是调用者清理栈,而__stdcall是被调用者清理栈。所以,理论来说,__cdecl生成的代码体积会 更大。但是,对于varargs函数,由于被调用者并不知道参数的具体长度,所以这样的函数只能采用__cdecl。

 

MSDN

http://msdn.microsoft.com/en-us/library/8fskxacy%28v=VS.80%29.aspx

http://msdn.microsoft.com/en-us/library/aa271769%28v=vs.60%29.aspx

http://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx

 

 

 

extern "C" __declspec(dllexport) __declspec(dllimport) 和 def

 

 

 

 

转自 http://www.cppblog.com/FateNo13/archive/2009/08/03/92052.aspx

前面的extern "C"  __declspec(dllexport)  __declspec(dllimport)都是用于函数或者变量,甚至类的声明的(可以把extern "C"放在class的前面,但是编译器会忽略掉,最后产生的还是C++修饰符,而不是C修饰符)这样的用法有个好处就是下面的代码可以在混有类的函数和 变量上使用下面的宏,虽然对类不起作用:

#ifdef __cplusplus
extern "C"
{
//函数声明
//变量声明,变量一般前面都有extern
//类声明,这个不起作用,编译器直接忽略掉class前面的extern “C”
#ifdef __cplusplus
}

#endif

C 和C++ 对应不同的调用约定,产生的修饰符也各不相同,如下:

调用约定 extern "C" 或 .c 文件 .cpp、.cxx 或 /TP

C 命名约定 (__cdecl)

_test

?test@@ZAXXZ

Fastcall 命名约定 (__fastcall)

@test@0

?test@@YIXXZ

标准调用命名约定 (__stdcall)

_test@0

?test@@YGXXZ


__declspec(dllexport)  __declspec(dllimport)一般也是使用宏的形式:

#ifdef ONEDLL_EXPORTS
#define ONEDLL_API __declspec(dllexport)
#else
#define ONEDLL_API __declspec(dllimport)
#endif

这样在DLL代码本身就是__declspec(dllexport) ,在使用DLL的程序中就变成了__declspec(dllimport),这两标志分别用来指明当前的函数将被导出,和当前函数是被导入的。
 

上面的两个宏结合一下就是下面这样的了:

// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 ONEDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// ONEDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef ONEDLL_EXPORTS
#define ONEDLL_API __declspec(dllexport)
#else
#define ONEDLL_API __declspec(dllimport)
#endif

// 此类是从 OneDll.dll 导出的
#ifdef __cplusplus
extern "C"
{
#endif
class ONEDLL_API COneDll {
public:
    COneDll(
void);
    
~COneDll(void);
    
    
// TODO: 在此添加您的方法。
    int m_a;
    
int m_b;
    
int *m_p;
    
int m_n;

    
void AddValue();

}
;

extern ONEDLL_API int nOneDll;

ONEDLL_API 
int fnOneDll(void);

#ifdef __cplusplus
}

#endif


如果调用模块和被调用模块都是C++(而且是同一种编成环境,如VC,甚至需要同一版本的VC),那么就不需要extern “C”了,因为这个标志的作用就是用在函数和变量声明前,无论是调用模块,还是被调用模块,都将生成C修饰符,调用模块将需要C修饰符的函数,而被调用模 块将产生C修饰符的函数,所以这个标志在两者都是C++的时候使用并不受影响,不使用这个标志,也不受影响。
但是如果C模块要调用C++ 模块,那么C++模块就需要使用extern “C”,当然C不用,由于是在头文件的声明中使用,所以使用下面的宏能够使得这个头文件也在C中顺利使用:

#ifdef __cplusplus
extern "C"
{
//函数声明
//变量声明,变量一般前面都有extern
//类声明,这个不起作用,编译器直接忽略掉class前面的extern “C”
#ifdef __cplusplus
}

#endif


如果C++模块要调用C模块,那么C++模块还是需要extern “C”,当然C不用,由于是在头文件的声明中使用,所以使用上面的宏同样能够使得这个头文件也在C中顺利使用。

总结一下就是加上extern “C”在什么情况下都没错,但是要注意函数重载的问题。



def文件是一种比较麻烦的方法,下面是MSDN中的部分内容:
 

模块定义 (.def) 文件是包含一个或多个描述 DLL 各种属性的 Module 语句的文本文件。如果不使用 __declspec(dllexport) 关键字导出 DLL 的函数,则 DLL 需要 .def 文件。

.def 文件必须至少包含下列模块定义语句:
1.文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。
2.EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。

例如,包含实现二进制搜索树的代码的 DLL 看上去可能像下面这样:

LIBRARY   BTREE
EXPORTS
   Insert   @
1
   Delete   @
2
   Member   @
3
   Min   @
4

 

提示:

如果希望优化 DLL 文件的大小,请对每个导出函数使用 NONAME 属性。使用 NONAME 属性时,序号存储在 DLL 的导出表中而非函数名中。如果导出许多函数,这样做可以节省相当多的空间。


其实__declspec(dllexport)的作用就是让编译器按照某种预定的方式(前面大致解释了这种方式的规则)来输出导出函数及变量的符号,而def文件则是自己为每一个函数和变量指定导出符号,所以def是一个非自动化,手工很强的方式,不是特殊情况的话,实在没有必要浪费这些时间。
还有一个问题,就是使用def会把调用方式和__declspec(dllexport)的作用全部覆盖掉,所以还需要自己处理调用方式不同产生的错误。
一般使用def文件的情况是你需要使用运行时加载,并且需要使用GetProcAddress函数获得函数地址,这个函数需要直接指明函数产生的导出符号,而可以自己指定导出符号的方式就是使用def。
def文件的具体语法可以看看msdn。

 

 

 

参考

http://www.cnblogs.com/scotth/p/3977928.html

 

 

我写了个创建DLL,测试DLL demo 

链接:https://pan.baidu.com/s/1ghhE3tD

密码:g0y1

posted @ 2013-12-14 17:58  scott_h  阅读(1637)  评论(0编辑  收藏  举报