动态链接库开发说明

Posted hanford

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态链接库开发说明相关的知识,希望对你有一定的参考价值。

1基本概念    1

1.1 一个简单的例子    1

1.1.1 新建一个VC++项目    1

1.1.2 添加源文件    3

1.1.3 输入源代码    6

1.1.4 __declspec(dllexport)    7

1.1.5 WINAPI    7

1.1.6 导出符号    7

1.1.7 DEF文件    8

1.2 调用动态库    9

1.2.1 隐式链接    9

1.2.2 显式链接    10

1.3 导出数据    11

1.3.1 隐式链接    11

1.3.2 显式链接    12

1.4 导出类    12

1.4.1 成员类    12

1.4.2 导出模板类    13

1.4.3 内联成员函数    13

1.4.4 友元函数    14

1.4.5 嵌套类    14

1.4.6 静态成员变量    15

1.4.7 查看导出    15

1.5 导入类    16

1.5.1 内联成员函数    17

2 MFC Regular DLL    18

2.1 三种DLL    18

2.1.1 non-MFC Win32 DLL    18

2.1.2 MFC Regular DLL    18

2.1.3 MFC Extension DLL    18

2.2 模块状态    18

2.3 InitInstance    19

2.4 AfxGetApp    20

2.5 PreTranslateMessage    20

2.6 OnIdle    21

3 MFC Extension DLL    22

3.1 显式链接    22

3.2 查找资源    22

3.3 代码解析    23

 

 

1基本概念

1.1 一个简单的例子

下面将使用VC++创建一个动态链接库文件。这个文件将导出两个函数StringReverseAStringReverseW,前者将一个ANSI字符串逆序,后者将一个Unicode字符串逆序。

1.1.1 新建一个VC++项目

对于VC++6.0而言,项目类型请选择Win32 Dynamic-Link Library。输入项目名称后,单击"OK"按钮。

在接下来的界面里,选择"An empty DLL project",然后单击"Finish"按钮。

在接下来的界面里单击"OK"按钮完成项目创建。

对于VC++9.0(即VC++2008)而言,项目类型请选择Win32。输入项目名称后,单击"确定"按钮。

在接下来的界面里,请选择"应用程序设置"下的"DLL"和"空项目"。单击"完成"按钮完成项目创建。

1.1.2 添加源文件

对于VC++6.0而言,在Workspace 窗口的 FileView 选项卡内,右键单击"Test files",在右键菜单里单击【Add Files to Project...】菜单项

输入源文件名后,单击"OK"按钮

弹出对话框里询问是否在项目里增加Test.c这个文件的引用。请单击"是"按钮。

此时鼠标双击Test.c。因为这个文件还不存在,VC++6.0会提示是否创建,请单击"是"按钮。

对于VC++9.0而言,在解决方案资源管理器里,右键单击"Test",在右键菜单里单击【添加】【新建项】菜单项。

接下来的界面内,请选择"C++文件(.cpp)",并输入源文件名Test.c,然后单击"添加"按钮。完成Test.c文件的添加和创建。

1.1.3 输入源代码

Test.c里输入如下源代码:

#include <windows.h>

 

/***************************************************************************\\

将一个 Unicode 字符串逆序

\\***************************************************************************/

__declspec(dllexport) wchar_t* WINAPI StringReverseW(wchar_t*wzStr)

{

if(wzStr)

{

int p1 = 0;

int p2 = wcslen(wzStr) - 1;

wchar_t t;

 

while(p1 < p2)

{

t = wzStr[p1];

wzStr[p1++] = wzStr[p2];

wzStr[p2--] = t;

}

}

return wzStr;

}

 

/***************************************************************************\\

将一个 ANSI 字符串逆序

\\***************************************************************************/

__declspec(dllexport) char* WINAPI StringReverseA(char*szStr)

{

if(szStr)

{

int nLenA = strlen(szStr) + 1;

int nLenW = MultiByteToWideChar(CP_ACP,0,szStr,nLenA,NULL,0);

wchar_t*pStrW = (wchar_t*)malloc(nLenW * sizeof(wchar_t));

 

MultiByteToWideChar(CP_ACP,0,szStr,nLenA,pStrW,nLenW);

StringReverseW(pStrW);

WideCharToMultiByte(CP_ACP,0,pStrW,nLenW,szStr,nLenA,NULL,NULL);

free(pStrW);

}

return szStr;

}

1.1.4 __declspec(dllexport)

__declspec(dllexport)修饰符用来导出函数StringReverseAStringReverseW。它还可以导出变量和类,这个后面介绍。

1.1.5 WINAPI

WINAPI 其实就是__stdcall。以StringReverseA为例,调用它时,参数szStr将被压入栈中,从StringReverseA返回时,参数szStr需要出栈。__stdcall表示由StringReverseA自己执行出栈操作。假如将__stdcall去掉或换为__cdecl,则由调用StringReverseA的函数负责执行出栈操作。说了这么多,最重要的是:某些语言,如VB6.0只支持__stdcall,所以为了让这个dll被尽可能多的编程语言支持,请使用WINAPI

1.1.6 导出符号

现在可以编译程序,生成Test.dll了。使用eXeScope6.30打开Test.dll,可以看到Test.dll确实导出了两个函数。请注意:每个导出函数都有一个序号,它是一个正整数。

不过有意思的是:导出函数的名称并不是StringReverseAStringReverseW,而是_StringReverseA@4_StringReverseW@4

如果把Test.c改名为Test.cpp,则导出的名称更为复杂。请参考下图:

这是什么原因呢?因为Test.c的扩展名为cVC++使用C编译器进行编译。Test.cpp的扩展名为cppVC++使用C++编译器进行编译。C++为了实现函数重载,编译时会根据参数类型和个数对函数名进行再次命名。

1.1.7 DEF文件

如何防止VC++编译器生成dll时将导出函数名更改掉?答案就是使用模块定义文件。请在VC++项目里增加模块定义文件Test.def。这个文件名可以是1.defA.def……只要扩展名是def即可。编辑Test.def,使其内容如下:

EXPORTS

StringReverseA

StringReverseW

上述内容表示:导出函数StringReverseAStringReverseW。此时,这两个函数前面的__declspec(dllexport)修饰符将不再需要。

DEF文件的功能还有很多,具体请参考MSDN

1.2 调用动态库

生成的动态库文件可以被多种编程语言使用。限于篇幅下面仅介绍VC++如何调用动态库。

1.2.1 隐式链接

编译动态库文件时,同时会生成Lib文件。使用动态库的VC++程序可以链接这个Lib文件,这就是隐式链接。可参考的代码如下:

#include <windows.h>

#include <stdio.h>

 

__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);    

#pragma comment(lib,"D:/VC6/Test/Debug/Test.lib")

 

void main()

{

char szStr[] = "隐式链接动态库";

puts(StringReverseA(szStr));

}

__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);    是函数声明。修饰符__declspec(dllimport)表示这是一个导入函数。去除这个修饰符不影响程序的编译、运行,但有了__declspec(dllimport)之后,生成的代码更小,运行更快。

注意:对于C++程序而言,可能需要这样声明函数:

extern "C"

{

__declspec(dllimport) char* WINAPI StringReverseA(char*szStr);    

}

extern "C" 的作用是:告诉C++编译器连接时不要以C++语法修改StringReverseA的名称。为什么说是"可能"需要extern "C"呢?这与dll的编译有关系。如果使用C编译器编译dll,则需要extern "C";如果使用C++编译器编译dll,则不需要extern "C"

#pragma comment(lib,"D:/VC6/Test/Debug/Test.lib")表示链接的时候使用D:\\VC6\\Test\\Debug\\Test.Lib文件。就是编译动态库时产生的那个Lib文件。

采用隐式链接,运行程序的时候动态库文件首先被加载至内存。系统如何定位dll文件呢?其搜索顺序为:exe所在目录、当前目录(GetCurrentDirectory)、System32目录(GetSystemDirectory)、Windows目录(GetWindowsDirectory)、环境变量PATH指定的目录。不用记这么多,最保险的做法就是将dllexe放在同一文件夹下。如果为了多个exe程序共享一个dll,请将这个dll文件复制到System32目录下。

1.2.2 显式链接

显式链接可以灵活控制动态库文件的加载、卸载。其使用步骤如下:

1、使用LoadLibrary函数载入动态库文件至内存;

2、使用GetProcAddress函数获得导出函数的地址;

3、调用导出函数;

4、使用FreeLibrary卸载动态库文件。

可参考如下代码:

#include <windows.h>

#include <stdio.h>

 

void main()

{

HINSTANCE hDll = LoadLibrary("Test.dll"); //载入动态库文件

if(hDll)

{//载入成功

char* (WINAPI*pfn)(char*szStr) = NULL; //声明一个函数指针

//获得函数StringReverseA的指针

#ifdef __cplusplus

(FARPROC&)pfn = GetProcAddress(hDll,"StringReverseA");

#else

(FARPROC)pfn = GetProcAddress(hDll,"StringReverseA");

#endif

if(pfn)

{//成功获得函数指针

char szStr[] = "显式链接动态库";

pfn(szStr); //调用函数,等价于(*pfn)(szStr)

puts(szStr);

}

FreeLibrary(hDll); //卸载动态库文件

}

}

需要说明的是

1LoadLibrary("Test.dll")在载入Test.dll时是有搜索顺序的:首先在exe所在目录查找,然后在当前目录(GetCurrentDirectory)下查找,然后在System32目录下查找……具体请参考MSDN帮助;

2、注意获得函数指针的C代码和C++代码是不同的,它们通过#ifdef __cplusplus这个条件编译语句来区分。或者使用C/C++通用的代码:

typedef char* (WINAPI*STRINGREVERSEA)(char*szStr);

STRINGREVERSEA pfn = (STRINGREVERSEA)GetProcAddress(hDll,"StringReverseA");

3GetProcAddress的第二个参数可以指定函数名,如:"StringReverseA"。还可以指定为序号,如:GetProcAddress(hDll,(LPCSTR)1)GetProcAddress(hDll,MAKEINTRESOURCEA(1))。其中1表示导出函数的序号为1GetProcAddress如何区分第2个参数是名称还是序号?对于字符串而言,首地址是一定大于0xFFFF的,而序号必须小于等于0xFFFF。这就是判断的依据。使用序号定位导出函数,效率上会高一些,但是这样的代码不利于阅读和维护;

4、关于FreeLibrary,需要说明的是:不能自己释放自己。如下面的函数在Test.dll内,其意图是自己释放自己。实际上它是行不通的:

void FreeMyself(HINSTANCE hDll)

{

FreeLibrary(hDll);

}

1.3 导出数据

dll导出数据很简单,下面是一个示例:

__declspec(dllexport) int nDataInDll;

或者

extern "C"

{//C++编译时,防止重命名导出符号nDataInDll

__declspec(dllexport) int nDataInDll;

}

或者使用DEF文件,其内容如下

EXPORTS

nDataInDll DATA

1.3.1 隐式链接

客户端程序隐式链接dll时,使用导出数据很简单。首先是按下列语法声明变量,然后就可以使用了。

extern "C"    //是否使用extern "C"需要根据实际情况而定

{

__declspec(dllimport) int nDataInDll;

}

1.3.2 显式链接

客户端程序显式链接dll时,使用导出数据稍显麻烦,其代码如下:

HINSTANCE hDll = LoadLibrary("Test.dll"); //载入动态库文件

if(hDll)

{//载入成功

int* nDataDll = NULL;

#ifdef __cplusplus

(FARPROC&)nDataDll = GetProcAddress(hDll,"nDataInDll");

#else

(FARPROC)nDataDll = GetProcAddress(hDll,"nDataInDll");

#endif

//使用数据

... ... ...

FreeLibrary(hDll); //卸载动态库文件

}

注意:GetProcAddress获得的是数据的地址。

1.4 导出类

导出类的语法有两种。方法1是定义类的时候同时定义导出,方法2是先声明类导出,再定义类。方法2比方法1灵活,但有时会有限制。

方法1

class __declspec(dllexport) CTest

{

public:

int        m_nValue;

CObj    m_obj;

};

方法2

//类声明,说明是一个导出类

class __declspec(dllexport) CTest;    

class CTest

{

public:

int        m_nValue;

CObj    m_obj;

};

导出类的实质其实就是把类的成员函数给导出了。

1.4.1 成员类

以上面的代码为例,实例化CTest时需要构造m_obj。因此CObj也必须被导出,否则编译的时候会产生警告,客户程序可能无法正常构造CTest类(Debug版正常,Release版分配内存但不调用构造函数)。

1.4.2 导出模板类

首先看下面的代码

template <class TYPE>

class CTemplate

{

public:

CTemplate() { a = 0; }

public:

TYPE a;

};

 

template class __declspec(dllexport) CTemplate<int>;

template class __declspec(dllexport) CTemplate<double>;

 

class __declspec(dllexport) CUseTemplate

{

public:

CTemplate<int> i;

CTemplate<double> d;

};

导出CUseTemplate时,CTemplate<int>CTemplate<double>也应该被导出。

注意:类模板是无法导出的,如下面的代码无法导出CTemplate

template <class TYPE>

class __declspec(dllexport) CTemplate

{

public:

CTemplate() { a = 0; }

public:

TYPE a;

};

如果不想导出模板类,请修改成员变量为指针类型。这样的话,成员变量的构造、析构将在DLL内完成,而不是在客户程序里完成。

以上是关于动态链接库开发说明的主要内容,如果未能解决你的问题,请参考以下文章

Android NDK 开发CMake 中查找链接 Android 自带动态库位置说明 ( ndk-bundleplatformsandroid-29arch-armusrlib )

使用C++开发动态链接库的过程总结

使用C++开发动态链接库的过程总结

Linux动态链接库.so文件的命名及用途总结

静态库与动态库的简单说明

怎么在C#中添加用C语言开发的动态链接库dll文件(vs2010)

(c)2006-2024 SYSTEM All Rights Reserved IT常识