动态链接库开发说明


第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++创建一个动态链接库文件。这个文件将导出两个函数StringReverseA、StringReverseW,前者将一个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)修饰符用来导出函数StringReverseA和StringReverseW。它还可以导出变量和类,这个后面介绍。

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确实导出了两个函数。请注意:每个导出函数都有一个序号,它是一个正整数。

不过有意思的是:导出函数的名称并不是StringReverseA和StringReverseW,而是[email protected]和[email protected]。

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

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

1.1.7 DEF文件

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


EXPORTS

StringReverseA

StringReverseW

上述内容表示:导出函数StringReverseA和StringReverseW。此时,这两个函数前面的__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指定的目录。不用记这么多,最保险的做法就是将dll和exe放在同一文件夹下。如果为了多个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); //卸载动态库文件

}

}

需要说明的是

1、LoadLibrary("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");

3、GetProcAddress的第二个参数可以指定函数名,如:"StringReverseA"。还可以指定为序号,如:GetProcAddress(hDll,(LPCSTR)1)或GetProcAddress(hDll,MAKEINTRESOURCEA(1))。其中1表示导出函数的序号为1。GetProcAddress如何区分第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内完成,而不是在客户程序里完成。


class __declspec(dllexport) CUseTemplate

{

public:

CTemplate<int>* pi;

CTemplate<double>* pd;

};

1.4.3 内联成员函数

内联函数相当于宏,编译的时候用来替换源代码,用以提高效率。一般它是不会被编译成目标代码的,但是一旦使用了__declspec(dllexport),编译程序将会为其生成一份目标代码,并导出。

1.4.4 友元函数

友元函数的实质上还是一个函数,只不过它们是类的朋友,可以访问类的私有成员变量。友元函数的导出,需要专门声明。具体方法如下:


方法1:

class __declspec(dllexport) CTest

{

public:

int m_nValue;

public:

//导出友元函数要专门声明

friend __declspec(dllexport) CTest operator+(const CTest&a,const CTest&b);

};


方法2:

//类声明

class __declspec(dllexport) CTest;

//友元函数声明

__declspec(dllexport) CTest operator+(const CTest&a,const CTest&b);

class CTest

{

public:

int m_nValue;

public:

friend CTest operator+(const CTest&a,const CTest&b);

};

1.4.5 嵌套类

下面是导出嵌套类的示例代码:


class __declspec(dllexport) CTest

{

public:

//导出嵌套类,前面也要使用__declspec(dllexport)

class __declspec(dllexport) CNest

{

}

};

1.4.6 静态成员变量

一个类被导出,则该类的所有静态成员变量被当作变量导出。如:下面的代码:


class __declspec(dllexport) CTest

{

public:

static int s_nValue; //s_nValue 被当作变量导出

};

1.4.7 查看导出

按下图设置VC++6.0,使得编译时生成map文件。

编译如下代码


class __declspec(dllexport) CTest

{

public:

void        SetValue(int v)        {m_Value = v;}

int        GetValue();

private:

int        m_Value;

public:

static int    s_nValue;

};

int CTest::s_nValue = 1;

int CTest::GetValue()                {return m_Value;}

编译后查看 map 文件,提取包含 CTest 的函数或变量:


Publics by Value


Rva+Base


说明


[email protected]@@[email protected]


10001030 f i


SetValue函数


[email protected]@[email protected]@@Z


10001070 f i


构造函数


[email protected]@@QAEHXZ


100010b0 f


GetValue函数


[email protected]@@2HA


1002ba30


s_nValue

注意上表的第2列,f表示函数,i表示内联。如果将 class __declspec(dllexport) CTest 中的 __declspec(dllexport) 去掉,重新编译,则上表第2列包含 f i 的在map文件中不会再出现。

去掉CTest定义中的__declspec(dllexport)后,可以使用 DEF文件导出一些CTest成员函数,如:下面的DEF文件导出了函数CTest::GetValue和变量CTest::s_nValue。


EXPORTS

[email protected]@@QAEHXZ

[email protected]@@2HA DATA

1.5 导入类

客户端程序可以使用dll导出的类,定义类的时候需要__declspec(dllimport)


方法1:

class __declspec(dllimport) CTest

{

public:

int m_nValue;

public:

//友元函数

friend __declspec(dllimport) CTest operator+(const CTest&a,const CTest&b);

};


方法2:

class __declspec(dllimport) CTest; //类声明

//下面这句话不再需要

//friend __declspec(dllimport) CTest operator+(const CTest&a,const CTest&b);

class CTest

{

public:

int m_nValue;

public:

//友元函数

friend
CTest operator+(const CTest&a,const CTest&b);

};

1.5.1 内联成员函数

考虑如下代码:


class __declspec(dllimport) CTest

{

public:

int m_nValue;

public:

int GetValue()

{

return m_nValue;

}

};

现在的问题是:对于内联函数GetValue,到底使用这里的定义,还是使用dll导出的内联函数?经测试发现:在__declspec(dllimport)存在的情况下,将使用dll导出的内联函数(此时就不是内联了);在删除上面的__declspec(dllimport)之后,将使用上面定义的内联函数。

第2章 MFC Regular DLL

2.1 三种DLL

使用VC++生成dll,共有三种类型。分别为:non-MFC Win32 DLL、MFC Regular DLL和MFC Extension DLL。

2.1.1 non-MFC Win32 DLL

上一章创建的 Win32 Dynamic-Link Library 即为non-MFC Win32 DLL。它的特点是可以使用MFC,也可以不使用MFC。导出的函数、类涉及界面、Windows消息处理较少。它特别适合封装数值计算、硬件控制等功能,此外它还可用于制作纯资源dll(连接时指定 /NOENTRY 即可)。

它可以被多种编程语言所支持。

2.1.2 MFC Regular DLL

对于过多的界面处理,如果不借助MFC,则难度是比较大的。此时,可以使用MFC Regular DLL。它必须使用MFC共享库,可以静态连接也可以动态链接MFC共享库,建议动态链接。

它也可以被多种编程语言所支持。

2.1.3 MFC Extension DLL

扩展的含义是对MFC类的扩展,它主要用于实现MFC类的派生类。如:如果觉得CButton类不太好,可以派生一个CButtonEx类,并在MFC Extension DLL里实现、导出CButtonEx。任何MFC程序都可以直接使用这个CButtonEx。

它必须使用MFC共享库,而且必须动态连接MFC共享库。只有MFC程序才能使用它。

2.2 模块状态

使用MFC Regular DLL要特别注意模块状态。在导出函数的第一行请增加如下代码:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

它在栈上创建了一个对象。该对象记录下当前的模块状态,然后设置当前模块状态为本dll模块状态。导出函数返回的时候,对象被析构。析构时,会恢复当前模块状态。

如果不注意模块状态的切换,则本dll内的资源可能无法被调用,甚至程序会崩溃。

2.3 InitInstance

CWinApp派生类的InitInstance函数必须返回 TRUE,否则dll将加载失败。

如果dll内部需要 ActiveX 控件或 OLE 拖放,请在InitInstance里调用OleInitialize(NULL),ExitInstance里调用OleUninitialize。因为每个MFC模块都有自己的模块状态,所以exe里调用了AfxOleInit对MFC Regular DLL是没有什么影响的,后者需要再次初始化COM库。但是AfxOleInit这个函数在MFC Regular DLL里没有发挥应有的作用(可能是MFC的BUG吧)。

请参考如下代码:


class CDllApp : public CWinApp

{

public:

BOOL InitInstance()

{

AfxEnableControlContainer();

OleInitialize(NULL);

return TRUE;

}

int ExitInstance()

{

OleUninitialize();

return CWinApp::ExitInstance();

}

}theApp;

顺便说一句OleInitialize与CoInitialize、CoInitializeEx的区别:CoInitializeEx是CoInitialize的扩展;OleInitialize将调用CoInitialize初始化COM库,并且还做了其它一些工作,以支持OLE拖放等操作。

2.4 AfxGetApp

AfxGetApp() 等价于 AfxGetModuleState()->m_pCurrentWinApp,而AfxGetModuleState()则是当前模块状态。

所以AFX_MANAGE_STATE(AfxGetStaticModuleState());之后,AfxGetModuleState()就是AfxGetStaticModuleState(),即当前模块状态是本dll模块状态。AfxGetApp()将返回本dll内CWinApp全局对象的地址(即theApp的地址)。

如果从exe调用dll的导出函数,而导出函数没有调用AFX_MANAGE_STATE(AfxGetStaticModuleState()),则AfxGetModuleState()将是AfxGetAppModuleState(),即当前模块状态是exe模块状态。AfxGetApp()将返回exe内CWinApp全局对象的地址。如果exe不是MFC程序,而是VB6.0、VC#程序,则AfxGetAppModuleState的返回值是不能使用的。

2.5 PreTranslateMessage

虽然MFC Regular DLL派生了CWinApp类,并有一个theApp全局对象。但它不包含CWinApp::Run机制,主消息由exe负责接收、分发。如果DLL 生成了无模式对话框或有自己的主框架窗口,则它应该导出函数来调用 PreTranslateMessage。exe程序需要调用这个导出函数。示例代码如下:


DLL端需要导出函数,调用AfxGetApp()->PreTranslateMessage

__declspec(dllexport) BOOL DllPreTranslateMessage(MSG* pMsg)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState()); //切换模块状态

return AfxGetApp()->PreTranslateMessage(pMsg);

}


exe端需要调用DLL的导出函数

class CTestApp : public CWinApp

{

public:

BOOL PreTranslateMessage(MSG* pMsg)

{

if(DllPreTranslateMessage(pMsg))

{

return TRUE;

}

return CWinApp::PreTranslateMessage(pMsg);

}

... ... ...

}theApp;

如果不这么做,则dll内部的非模态对话框PreTranslateMessage函数不会被执行,对话框内按Tab键也无法切换焦点。

如果exe不是MFC程序,而是VB6.0、VC#程序,该如何处理PreTranslateMessage?这个问题需要再进行深入研究。此时,非模态对话框内按Tab键无法切换焦点,该如何处理?估计得使用键盘钩子了……

2.6 OnIdle

一个标准的MFC程序里,主窗口菜单项、工具栏的显示更新,以及临时对象的销毁是在应用程序类的OnIdle里进行处理的。如果MFC Regular DLL里有框架窗口(CFrameWnd),并有菜单、工具栏,则它也需要处理OnIdle。方法与PreTranslateMessage的处理相同,其代码如下:


DLL端需要导出函数,调用AfxGetApp()->OnIdle

__declspec(dllexport) void DllOnIdle(LONG lCount)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState()); //切换模块状态

AfxGetApp()->OnIdle(lCount);

}


exe端需要调用DLL的导出函数

class CTestApp : public CWinApp

{

public:

BOOL OnIdle(LONG lCount)

{

DllOnIdle(lCount);

return CWinApp::OnIdle(lCount);

}

... ... ...

}theApp;

如果exe端不是MFC程序,则在dll内部需要定时调用如下代码:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

AfxGetApp()->OnIdle(0);    //更新菜单、工具栏

AfxGetApp()->OnIdle(1);    //删除临时对象

可以把这几行代码放在导出函数的最前面。

第3章 MFC Extension DLL

如前所言,MFC Extension DLL主要用于实现MFC类的派生类。如:如果觉得CButton类不太好,可以派生一个CButtonEx类,并在MFC Extension DLL里实现、导出CButtonEx。任何MFC程序都可以直接使用这个CButtonEx。当然是可以在MFC Extension DLL里创建一个非模态对话框的,但它的PreTranslateMessage同样不会被主动执行。也就是说MFC Extension DLL的主要功能不是处理界面、Windows消息,而是扩展MFC已有的类。

MFC Extension DLL必须使用MFC共享库,而且必须动态连接MFC共享库。只有MFC程序才能使用它。

顺便说一句:MFC Extension DLL不再像MFC Reguler DLL那样需要切换模块状态。

3.1 显式链接

大多数情况下,MFC Extension DLL都是隐式连接的,因为它导出的基本上都是扩展MFC的类。但显式链接也是可行的,不过使用的不是LoadLibrary,而是AfxLoadLibrary。卸载的函数也由FreeLibrary改为AfxFreeLibrary。

3.2 查找资源

代码CDialog(_T("DlgRes")).DoModal();用于显示一个对话框。为了显示这个对话框,需要查找对话框资源"DlgRes"。查找资源的顺序如下图所示:

即:首先在本模块(MFC Regular DLL或EXE)里查找,然后在各个MFC Extension DLL里查找,最后在MFC42.DLL里查找。

注意:如果是MFC Regular DLL,则默认情况下不会在各个MFC Extension DLL里查找资源。需要做如下处理后才行:

1、MFC Extension DLL导出一个函数


__declspec(dllexport) void WINAPI ExtFunc()

{

new CDynLinkLibrary(ExtDLL);

}

2、在MFC Regular DLL的InitInstance函数里导入调用该函数


BOOL CRegDllApp::InitInstance()

{

{//

AFX_MANAGE_STATE(AfxGetStaticModuleState());

ExtFunc();

}

return CWinApp::InitInstance();

}

现在MFC Regular DLL里的代码CDialog(_T("DlgRes")).DoModal();在执行时就会在MFC Extension DLL里查找资源"DlgRes"了。

3.3 代码解析

MFC模块状态是一个AFX_MODULE_STATE,它里面有一个成员变量

CTypedSimpleList<CDynLinkLibrary*> m_libraryList;

它是exe模块用到的所有的MFC Extension DLL链表。MFC Extension DLL是通过DllMain完成初始化的,其典型代码如下:


static AFX_EXTENSION_MODULE TestDLL = { NULL, NULL };

extern "C" int APIENTRY

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)

{

if (dwReason == DLL_PROCESS_ATTACH)

{

if (!AfxInitExtensionModule(TestDLL, hInstance))

return 0;

new CDynLinkLibrary(TestDLL);

}

else if (dwReason == DLL_PROCESS_DETACH)

{

AfxTermExtensionModule(TestDLL);

}

return 1; // ok

}

new CDynLinkLibrary(TestDLL)将创建一个CDynLinkLibrary对象,并将自己加入m_libraryList,其代码如下:


CDynLinkLibrary::CDynLinkLibrary(AFX_EXTENSION_MODULE&,BOOL)

{

... ... ...

m_pModuleState->m_libraryList.AddHead(this);

... ... ...

}

C:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC\DLLINIT.CPP里AfxFindResourceHandle函数说明了资源查找的顺序:


HINSTANCE AFXAPI AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType)

{

HINSTANCE hInst;

... ...

if(!pModuleState->m_bSystem)

{//第1步

hInst = AfxGetResourceHandle();

}

... ...

for(CDynLinkLibrary* pDLL = pModuleState->m_libraryList;

pDLL != NULL;pDLL = pDLL->m_pNextDLL)

{//第2步

if(!pDLL->m_bSystem && pDLL->m_hResource != NULL

&& ::FindResource(pDLL->m_hResource, lpszName, lpszType))

{

return pDLL->m_hResource;

}

}

... ...

hInst = pModuleState->m_appLangDLL; //第3步

... ...

if(!pModuleState->m_bSystem)

{//第4步

hInst = AfxGetResourceHandle();

}

... ...

for(pDLL = pModuleState->m_libraryList; pDLL != NULL;

pDLL = pDLL->m_pNextDLL)

{//第5步

if(pDLL->m_bSystem && pDLL->m_hResource != NULL

&& ::FindResource(pDLL->m_hResource, lpszName, lpszType))

{

return pDLL->m_hResource;

}

}

... ...

return AfxGetResourceHandle();//第6步

}

注意:第2步与第5步的区别在于MFC Extension DLL一个是非系统的(!pDLL->m_bSystem),另一个是系统的(pDLL->m_bSystem)。MFC42.DLL就是一个系统的MFC Extension DLL。

注意CDynLinkLibrary构造函数里的代码m_pModuleState->m_libraryList.AddHead(this);其中的m_pModuleState是exe的模块状态,即AfxGetAppModuleState()的返回值。默认情况下MFC Extension DLL中的new CDynLinkLibrary(TestDLL)不会加入MFC Regular DLL的m_pModuleState->m_libraryList,在此情况下MFC Regular DLL需要的资源不可能在MFC Extension DLL里找到。下面的代码把MFC Extension DLL中的new CDynLinkLibrary(TestDLL)加入到MFC Regular DLL的m_pModuleState->m_libraryList。


BOOL CRegDllApp::InitInstance()

{

{//

AFX_MANAGE_STATE(AfxGetStaticModuleState());

ExtFunc();

}

return CWinApp::InitInstance();

}

时间: 12-12

动态链接库开发说明的相关文章

动态链接库DLL的通俗理解

今天主要布置了一个任务要求写一个ocx控件,是用VC开发,虽然一直以来都是用VC写程序,但是使用到的功能仅仅是写一个小程编译运行,连链接都省去了,因为所有的功能都在一个文件里面就省去了链接这一步,所以写个这个什么ocx空间也只能是从零开始,主管给了几个DLL文件和说明文档,在电脑C盘里面看见过许多DLL为后缀的文件,但是不知道是什么东西也没有去了解过,现在要用了,这篇文章就给大家介绍一下我对DLL的了解,我是菜鸟准备入门,前辈勿喷.首先看一百度百科上怎么说: "DLL文件又称"应用程序

(转)VC6.0中OpenGL开发环境配置

首先简单介绍一下OpenGL: OpenGL作为当前主流的图形API之一,它在一些场合具有比DirectX更优越的特性.       OpenGL官方网站(英文)    http://www.opengl.org 然后设置编程的一些环境,及其安装必备文件的步骤如下: 第一步:选择一个编译环境 现在Windows系统的主流编译环境有Visual Studio,Broland C++ Builder,Dev-C++等,它们都是支持OpenGL的.但这里我们选择VC++ 6.0作为学习OpenGL的环

使用VS2015创建和使用动态链接库-图文详解

之前看过原创一篇<VS2010 动态库开发--第一章 演练:创建和使用动态链接库 (C++)>的帖子,地址是http://blog.sina.com.cn/s/blog_6fb3686501011ymn.html,感谢原创.这里我们使用VS2015创建和使用自己的动态链接库,配以图文详解. 我们先来演示如何使用VS2015创建一个动态链接库.1.新建一个"Win32控制台应用程序","名称"为MathFuncsDll,"解决方案名称"

【OpenCV入门教程之一】 OpenCV 2.4.8 +VS2010的开发环境配置

目录(?)[-] 因为读研期间的研究方向是图像处理所以浅墨这段时间闭门研究了很多OpenCV和图像处理相关的知识与内容眼看自己积累到一定的程度了于是决定开始开设这个OpenCV系列专栏总结自己所学也分享知识给大家 还是先放出待会儿的测试用图 下载和安装OpenCV SDK sources里面是源代码想查看完整的源代码需要用cmake来解包如何解包大家百度一下就可以或者下次浅墨来专门讲一讲这里就先不多说了 配置环境变量 工程包含include目录的配置 工程库lib目录的配置 链接库的配置 在Wi

Unity游戏开发学习之路——数据持久化

数据持久化 谈到数据持久化,在Unity的游戏开发中十分重要的,不管是是在本地和服务器端,数据持久化都是我们学习的难点,数据持久化的技术有很多种,这里只选取几种,目前也是我所学到的,在接下来的时间里会陆续整理到这里. Part1:PlayerPrefs类 这是unity圣典中给出的, PlayerPrefs 游戏存档 Description 描述 在游戏会话中储存和访问游戏存档.这个是持久化数据储存,比如保存游戏记录. Editor/Standalone 编辑器 / 桌面平台 Mac OS 在M

App Today Extension开发注意事项

从iOS 8起,就有了App Extension.Extension的种类至今也扩充到了19种,应用也很广泛,值得重点关注起来. Extension几乎可以看做一个内嵌的独立App,拥有独立的BundleID.证书.概要配置文件.进程空间.沙盒等等.只是需要打包在App内,类似于寄生在宿主App内,捆绑安装.不过一旦安装应用后,扩展可以由系统独立调用,执行扩展内的代码逻辑. 这篇只记录之前开发Today Extension的过程中,值得注意的事项.其实大多都是通用的. 1.创建Extension

windows7下的PHP开发环境搭建(iis7 + PHP7 + PHPStorm2016.2.1)

之前是一直做asp.net开发的,Visual Studio 2012 用起来感觉是相当的舒服,调试功能也是相当好.相当智能,大大的提高了开发效率减少了程 序BUG的出现,而且直接安装好就能用了,无需多余的操作,非常方便. 由于这次要做PHP开发,为了搭建开发环境也是搜索了不少资料,选择IDE也是参考了不少资料,我的首要目标就是要调试方便的,其实之前都没有 接触过,最后选择了PHPStorm(主要是看界面清爽),为了弄好调试功能也是搜索资料弄了好久啊,感觉还是没有VS方便,安装完成直接能用.所以

移植Openssh到开发板

1.下载源码 zlib,openssl , openssh,配置编译链,编译,安装.大致的配置如下: 编译zlib./configure --prefix=/home/potato/openssl/install/zlib-1.2.3make make install 编译openssl./Configure --prefix=/home/potato/openssl/install/openssl-0.9.8e  os/compiler:arm-linux-gccmake make insta

Visual studio 通用开发环境配置:SDL,FFMPEG为例

引言 每一个C++库的使用都是从开发环境的配置开始的,其实每个库的配置过程都是大同小异,总结下来有下面几个步骤: 下载库文件,这里假定是已经预先编译完成的. 配置库文件的包含目录(include)和库目录(lib) 配置库文件的动态链接库(dll),这一步是很多人容易忽略的.上面配置好包含目录和库目录,只是开发环境配置完成了,没有配置好dll,在使用该库的程序运行时,会造成操作系统无法加载库对应的动态链接库.下面以SDL的配置为例,详细说明是如何配置的. SDL在visual studio下的开

VC++中开发汇编语言(转)

汇编程序结构 一个显示字符串的汇编程序 程序格式 一.模式定义 二.includelib语句 三.函数声明语句 四.数据和代码部分 Visual C/C++环境 建立工程 汇编程序的调试 一.设置断点 二.内存窗口 三.寄存器窗口 四.监视窗口 常用调试命令 字符串输入.输出 printf sprintf scanf 常用Windows API调用 MessageBox 确定函数的声明语句和库文件 读取CPU标识 WinDbg调试工具 实验题:用MessageBox函数显示CPU信息 源自:ht