代码改变世界

非MFC DLL学习笔记

2012-11-01 09:36  龙成  阅读(285)  评论(0编辑  收藏  举报

动态链接库

动态链接库和静态链接库的区别:

    动态链接库与静态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意lib中的指令都被直接包含在最终生的EXE文件中;而DLL则不必被包含在最终EXE中,EXE文件可以随时调用和卸载DLL。另一个区别是静态链接库里面不能再包含其他的动态或者静态链接库,而动态链接库可以。

动态链接库的分类(dll(dynamic linkable library)):

NON_MFC DLL(非MFC动态链接库)、

MFC Regular DLL(MFC规则DLL)、

MFC Extension DLL(MFC扩展DLL);

非MFC动态库不采用MFC类库结构,其导出函数为标准C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL包含一个继承自CwinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,只能被MFC应用程序调用。

非MFC动态链接库的调用与创建

学习了简单的dll调用与创建:主要代码是

dlltest.h

#ifndef _DLLTEST_H

#define _DLLTEST_H

extern "C" int __declspec(dllexport) add(int,int);

#endif

dlltest.cpp

#include "stdafx.h"

#include "dlltest.h"

 

int add(int a,int b)

{

    return a+b;

}

Dllcall.cpp

#include <iostream>

#include <windows.h>

using namespace std;

typedef int(*lpAddFun)(int,int);

int main()

{

    HINSTANCE hDll;

    lpAddFun addFun;

    hDll = LoadLibrary(L"C:\\Documents and Settings\\luojingcheng\\My Documents\\Visual Studio 2008\\Projects\\dlltest\\Debug\\dlltest.dll");

    if(hDll != NULL)

    {

       addFun = (lpAddFun)GetProcAddress(hDll,"add");

       if(addFun != NULL)

       {

           cout << addFun(2,3) << endl;

       }

    }

    FreeLibrary(hDll);

    return 0;

}

__declspec(dllexport):这个语句的含义是声明函数add为dll的导出函数。

dllexport:导出函数

dllimport:导入函数

DLL内的函数分为两种:

DLL导出函数,可供应用程序调用;

DLL内部函数,只能在DLL程序使用,应用程序无法调用。

另一种调用导出函数的方法

创建一个.def文件;

LIBRARY dlltest

EXPORTS

add @ 1

def文件的规则:

(1)LIBRARY语句说明.def文件相应的dll

(2)EXPORTS语句后列出要导出函数的名字。可在.def文件中的导出函数名加 @n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥作用)

(3).def文件中的注释由每个注释行开始处得分号指定,且注释不能与语句共享一行。

DLL的调用方式

Dll动态调用方式:LoadLibrary→GetProcAddress→FreeLibrary

这是由系统Api提供的三位一体“DLL加载→DLL函数地址获取→DLL释放”;

DLL动态调用方式

#include <iostream>

using namespace std;

#pragma comment(lib,"C:\\Documents and Settings\\luojingcheng\\My Documents\\Visual Studio 2008\\Projects\\dlltest\\Debug\\dlltest.lib")

extern "C" int __declspec(dllimport)add(int x,int y);

int main()

{

    cout << add(2,3) << endl;

    return 0;

}

(1)  告诉编译器与DLL相对应的。Lib文件所在的路径及文件名,#pragma comment(lib,”dlltest.lib”)起这个作用。

(2)  声明导入函数,extern “C”__declspec(dllimport) add(int x,int y)语句中的__declspec(dllimport)发挥这个作用。

静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,WINDOWS将根据这些信息发现并加载DLL,然后通过符号实现对DLL函数的动态链接。

DLL DLLMain 函数

DLLMAIN就如在控制台或DOS程序需要Main函数、WIN32需要WINMAIN函数一样。如果DLL没有提供DLLMain函数,系统会从其他运行库引入一个不做任何操作的缺省DLLMain函数版本。

DLLMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DLLMain函数,DLLMain是自动被调用的。

代码

DLLMAIN.CPP

#include "stdafx.h"

#include <iostream>

using namespace std;

 

BOOL APIENTRY DllMain( HMODULE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                   )

{

    switch (ul_reason_for_call)

    {

    case DLL_PROCESS_ATTACH:

       cout << "process attach dll!" << endl;

       break;

    case DLL_THREAD_ATTACH:

       cout << "thread attach dll!" << endl;

       break;

    case DLL_THREAD_DETACH:

       cout << "thread detach dll!" << endl;

       break;

    case DLL_PROCESS_DETACH:

       cout << "process detach dll!" << endl;

       break;

    }

    return TRUE;

}

代码分析:

    APIENTRY 在C++中的定义是 typedef APIENTRY WINAPI

    WINAPI   在C++中的定义是 typedef WINAPI __stdcall

    所以APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;

DLLMain函数的参数中 HMODUBLE和HINSTANCE的值是相同的,这两种类型可以替换使用。

进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块的进程虚拟空间中的起始地址。

DLLCALL.CPP的代码

#include <iostream>

#include <windows.h>

using namespace std;

typedef int(*lpAddFun)(int,int);

int main()

{

    HINSTANCE hDll;

    lpAddFun addFun;

    hDll = LoadLibrary(L"C:\\Documents and Settings\\luojingcheng\\My Documents\\Visual Studio 2008\\Projects\\dlltest\\Debug\\dlltest.dll");

    if(hDll != NULL)

    {

//     addFun = (lpAddFun)GetProcAddress(hDll,"add");

       addFun = (lpAddFun)GetProcAddress(hDll,MAKEINTRESOURCEA(1));//(LPCSTR)

       if(addFun != NULL)

       {

           cout << addFun(2,3) << endl;

       }

    }

    FreeLibrary(hDll);

    return 0;

}

执行此代码输出顺序为:

    Process attach dll

    5

    Process detach dll

代码分析:

    此代码中的GetProcAddress(hDll,MAKEINTRESOURCE(1))值得留意,它直接通过.def文件中为add函数指定的书序号访问add函数,MAKEINTRESOURCE(1), MAKEINTRESOURCE是一个通过序号获取函数名的宏,

其中MAKEINTRESOURCEA表示的类型是(LPSTR)

MAKEINTRESOURCEW表示的类型是(LPWSTR)

 

__stdcall约定

Dlltest.h

将   extern “C” int add(int,int);

改为 int __stdcall add(int,int);

DLLCALL.CPP

将   typedef int(*lpAddFun)(int,int);

改为 typedef int(*__stdcall*lpAddFun)(int,int);

DLL导出变量

\\dlltest.def

LIBRARY    "dlltest"

EXPORTS

dllGlobalVar DATA

\\ dlltest.h

增加extern int dllGlobalVar;

\\dllmain.cpp

在DllMain前面增加全局的变量

Int dllGlobalVar;

 

/*Dllcall.cpp*/

#include <iostream>

#include <windows.h>

using namespace std;

#pragma comment(lib,"C:\\Documents and Settings\\luojingcheng\\My Documents\\Visual Studio 2008\\Projects\\dlltest\\Debug\\dlltest.lib")

extern int _declspec(dllimport)dllGlobalVar;

int main()

{

    cout << dllGlobalVar << endl;

    return 0;

}

 

分析:

    这种通过_declspec(dllimport)方式导入的就是DLL全局变量本身不是地址

 

DLL导出类

Point.h

#ifndef POINT_H

#define POINT_H

 

#ifdef DLL_FILE

class _declspec(dllexport)point

#else

class _declspec(dllimport)point

#endif

//class _declspec(dllexport) point

{

public:

    float x,y;

    point();

    point(float x_coordinate,float y_coordinate);

};

#endif

 

Circle.h

#ifndef CIRCLE_H

#define CIRCLE_H

#include "point.h"

 #ifdef DLL_FILE

 class _declspec(dllexport)circle

 #else

 class _declspec(dllimport)circle

 #endif

//class _declspec(dllexport) circle

{

public:

    void SetCentre(const point centrePoint);

    void SetRadius(float r);

    double GetGirth();

    double GetArea();

    circle();

private:

    float radius;

    point centre;

};

#endif

 

Point.cpp

#ifndef DLL_FILE

#define DLL_FILE

#endif

#include "stdafx.h"

#include "point.h"

 

 

point::point()

{

    x = 0.0;

    y = 0.0;

}

 

point::point(float x_coordinate,float y_coordinate)

{

    x = x_coordinate;

    y = y_coordinate;

}

Circle.cpp

#ifndef DLL_FILE

 #define DLL_FILE

 #endif

#include "stdafx.h"

#include "circle.h"

 

const double PI = 3.1415926;

 

circle::circle()

{

    centre = point(0,0);

    radius = 0;

}

 

double circle::GetArea()

{

    return PI*radius*radius;

}

 

double circle::GetGirth()

{

    return 2*PI*radius;

}

 

void circle::SetCentre(const point centrePoint)

{

    centre = centrePoint;

}

 

void circle::SetRadius(float r)

{

    radius = r;

}

Dllcall.cpp

#include <iostream>

#include "C:\\Documents and Settings\\luojingcheng\\My Documents\\Visual Studio 2008\\Projects\\dlltest\\dlltest\\circle.h"

#pragma comment(lib,"C:\\Documents and Settings\\luojingcheng\\My Documents\\Visual Studio 2008\\Projects\\dlltest\\Debug\\dlltest.lib");

using namespace std;

int main()

{

    circle c;

    point p(2.0,2.0);

    c.SetCentre(p);

    c.SetRadius(1.0);

    cout << "area:" << c.GetArea() << endl;

    cout << "girth:" << c.GetGirth() << endl;

    return 0;

}

 

代码分析:

DLL代码中定义了宏DLL_FILE,所以在DLL实现中所包含的类声明实际上为:

Class _declspec(dllexport) point

{

}

Class _declspec(dllexport)circle

{

 

}

 

在应用工程中没有定义DLL_FILE,所以包含point.h,circle.h后引入的类声明为:

Class _declspec(dllimport) point

{

}

Class _declspec(dllimport)circle

{

 

}

就是通过DLL中的dllexport和应用程序中的dllimport匹配来完成类的导入和导出。

   我们往往通过在类的声明头文件中用一个宏来决定使其编译为class_declspec(dllexport)class_name还是class_declspec(dllimport)class_name版本,这样就不需要两个头文件。

代码中使用的是:

#ifdef DLL_FILE

Class _declspec(dllexport)class_name//导出类

#else

Class _declspec(dllimport)class_name//导入类

#endif