`
美丽的小岛
  • 浏览: 309275 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

C++DLL编程详解

    博客分类:
  • c
  • c++
 
阅读更多

DLL(Dynamic Link Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。

静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。

DLL:

  (1)DLL 的编制与具体的编程语言及编译器无关
  只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统 DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。

  (2)动态链接库随处可见
  我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。

  一般的程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。由此可见DLL对我们来说其实并不陌生。

  (3)VC动态链接库的分类
  Visual C++支持三种DLL,它们分别是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类库所编写的应用程序所调用。

LIB:

在VC++6.0中新建一个名称为libTest的static library工程,并新建lib.h和lib.cpp两个文件,lib.h和lib.cpp的源代码如下:
//文件:lib.h
#ifndef LIB_H
#define LIB_H
extern "C" int add(int x,int y);   //声明为C连接方式的外部函数
#endif

//文件:lib.cpp
#include "lib.h"
int add(int x,int y)
{
return x + y;
}

编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数。

怎么使用这个库,新建一个libCall工程。libCall工程仅包含一个main.cpp文件,

#include 
#include "路径\lib.h"
#pragma comment( lib, "路径
\\libTest.lib" )  //指定与静态库一起连接
int main(int argc, char* argv[])
{
printf( "2 + 3 = %d", add( 2, 3 ) );
}

#pragma comment( lib, "路径\\libTest.lib" ) 的意思是指本文件生成的.obj文件应与libTest.lib一起连接。

如果不用#pragma comment指定,则可以直接在VC++中设置,依次选择tools、options、directories、library files菜单或选项,填入库文件路径。

 非MFC DLL:

上面给出了以静态链接库方式提供add函数接口的方法,接下来看看怎样用动态链接库实现一个同样功能的add函数。在VC++中新建一个Win32 Dynamic-Link Library工程dllTest。注意不要选择MFC AppWizard(dll)。
在建立的工程中添加MyDll.h及MyDll.cpp文件,源代码如下:

/* 文件名:MyDll.h */

#ifndef MYDLL_H

#define MYDLL_H

extern "C" int __declspec(dllexport)add(int x, int y); //声明函数add为DLL的导出函数

#endif


/* 文件名:MyDll.cpp */

#include "MyDll.h"

int add(int x, int y)

{

return x + y;

}

调用方式:

建立应用工程dllCall,它调用DLL中的函数add,其源代码如下:


#include <stdio.h>

#include <windows.h>

typedef int(*lpAddFun)(int, int); //宏定义函数指针类型

int main(int argc, char *argv[])

{

HINSTANCE hDll; //DLL句柄

lpAddFun addFun; //函数指针

hDll = LoadLibrary("路径\\dllTest.dll");

if (hDll != NULL)

{

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

if (addFun != NULL)

{

int result = addFun(2, 3);

printf("%d", result);

}

FreeLibrary(hDll);

}

return 0;

}

DLL内的函数分为两种:

(1)DLL导出函数,可外部应用程序调用;

(2)DLL内部函数,只能在DLL中自己使用。

DLL中导出函数的声明有两种方式:

一种在函数声明中加上__declspec(dllexport;

另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。下面的代码演示了怎样用.def文件将函数add声明为DLL导出函数

; lib.def : 导出DLL函数

LIBRARY dllTest

EXPORTS

add @ 1

def文件的规则为:

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

  (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号

         为n(在进行函数调用时,这个序号将发挥其作用);

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

由此可以看出,例子中lib.def文件的含义为生成名为“dllTest”的动态链接库,导出其中的add函数,并指定add函数的序号为1。

DLL的调用方式:

动态调用:由“LoadLibrary-GetProcAddress-FreeLibrary”系统Api提供DLL加载-DLL函数地址获取-DLL释放方式。正如上面那个例子。

静态调用:这个方式要与静态库的调用方式区别开,是由编译系统完成对DLL的加载和应用程序结束时DLL 的卸载。当调用某DLL的应用程序结束时,若系统中还有其它程序使用该DLL,则Windows对DLL的应用记录减1,直到所有使用该DLL的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。

静态调用例子:静态调用方式需要完成两个动作:

(1)告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma comment(lib,"dllTest.lib")就是起这个作用。程序员在建立一个DLL文件时,连接器会自动为其生成一个对应的.lib文件,该文件包含了DLL 导出函数的符号名及序号(并不含有实际的代码)。在应用程序里,.lib文件将作为DLL的替代文件参与编译。

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

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

将编译dll工程所生成的.lib和.dll文件拷入dllCall工程所在的路径,dllCall执行下列代码:

#pragma comment(lib,"dllTest.lib") //.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息

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

int main(int argc, char* argv[])

{

int result = add(2,3);

printf("%d",result);

return 0;

}

 

DllMain函数

Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在前面的例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。

看一个DllMain函数的例子:
BOOL APIENTRY DllMain( HANDLE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved

)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

printf("\nprocess attach of dll");

break;

case DLL_THREAD_ATTACH:

printf("\nthread attach of dll");

break;

case DLL_THREAD_DETACH:

printf("\nthread detach of dll");

break;

case DLL_PROCESS_DETACH:

printf("\nprocess detach of dll");

break;

}

return TRUE;

}

DllMain函数在DLL被加载和卸载时被调用,在单线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH,以switch语句列出。
来仔细解读一下DllMain的函数头BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )。

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

进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用,这就是函数参数hModule的来历。

执行下列代码:
hDll = LoadLibrary("..路径
\\dllTest.dll");

if (hDll != NULL)

{

addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));

//MAKEINTRESOURCE直接使用导出文件中的序号

if (addFun != NULL)

{

int result = addFun(2, 3);

printf("\ncall add in dll:%d", result);

}

FreeLibrary(hDll);

}

代码中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) ),它直接通过def文件中为add函数指定的顺序号访问add函数,MAKEINTRESOURCE是一个通过序号获取函数名的宏,定义为(节选自winuser.h):


#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))

#define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))

#ifdef UNICODE

#define MAKEINTRESOURCE MAKEINTRESOURCEW

#else

#define MAKEINTRESOURCE MAKEINTRESOURCEA

出顺序为:输出顺序验证了DllMain被调用的时机

  process attach of dll

  call add in dll:5

  process detach of dll


DLL导出变量:

DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据,来看看在应用工程中引用DLL中变量的例子
/* 文件名:MyDll.h */

#ifndef MYDLL_H

#define MYDLL_H

extern int dllGlobalVar;

#endif


/* 文件名:MyDll.cpp */

#include "MyDll.h"

#include <windows.h>


int dllGlobalVar;


BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

dllGlobalVar = 100; //在dll被加载时,赋全局变量为100

break;

case DLL_THREAD_ATTACH:

case DLL_THREAD_DETACH:

case DLL_PROCESS_DETACH:

break;

}

return TRUE;

}


;文件名:MyDll.def

;在DLL中导出变量

LIBRARY "dllTest"

EXPORTS

dllGlobalVar CONSTANT

;或dllGlobalVar DATA

GetGlobalVar
从MyDll.h和MyDll.cpp中可以看出,全局变量在DLL中的定义和使用方法与一般的程序设计是一样的。若要导出某全局变量,我们需要在.def文件的EXPORTS后添加:


变量名 CONSTANT   //过时的方法
变量名 DATA        //VC++提示的新方法

在主函数中引用DLL中定义的全局变量:

#include <stdio.h>

#pragma comment(lib,"dllTest.lib")

extern int dllGlobalVar;

int main(int argc, char *argv[])

{

printf("%d ", *(int*)dllGlobalVar);

*(int*)dllGlobalVar = 1;

printf("%d ", *(int*)dllGlobalVar);


return 0;

}

特别要注意的是用extern int dllGlobalVar声明所导入的并不是DLL中全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。这一点,从*(int*)dllGlobalVar可以看出。因此在采用这种方式引用DLL全局变量时,千万不要进行这样的赋值操作:
dllGlobalVar = 1;其结果是dllGlobalVar指针的内容发生变化,程序中以后再也引用不到DLL中的全局变量了。在应用工程中引用DLL中全局变量的一个更好方法是:
#include <stdio.h>

 

 

#pragma comment(lib,"dllTest.lib")

extern int _declspec(dllimport) dllGlobalVar; //用_declspec(dllimport)导入

int main(int argc, char *argv[])

{

printf("%d ", dllGlobalVar);

dllGlobalVar = 1; //这里就可以直接使用, 无须进行强制指针转换

printf("%d ", dllGlobalVar);

return 0;

}

通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,建议在一切可能的情况下都使用这种方式。

 DLL导出类:

  DLL中定义的类可以在应用工程中使用。

  下面的例子里,在DLL中定义了point和circle两个类,并在应用工程中引用了它们//文件名:point.h,point类的声明

#ifndef POINT_H

#define POINT_H

#ifdef DLL_FILE

class _declspec(dllexport) point //导出类point

#else

class _declspec(dllimport) point //导入类point

#endif

{

public:

float y;

float x;

point();

point(float x_coordinate, float y_coordinate);

};

#endif


//文件名:point.cpp,point类的实现

#ifndef DLL_FILE

#define DLL_FILE

#endif

#include "point.h"

//类point的缺省构造函数

point::point()

{

x = 0.0;

y = 0.0;

}

//类point的构造函数

point::point(float x_coordinate, float y_coordinate)

{

x = x_coordinate;

y = y_coordinate;

}


//文件名:circle.h,circle类的声明

#ifndef CIRCLE_H

#define CIRCLE_H

#include "point.h"

#ifdef DLL_FILE

class _declspec(dllexport)circle //导出类circle

#else

class _declspec(dllimport)circle //导入类circle

#endif

{

public:

void SetCentre(const point ¢rePoint);

void SetRadius(float r);

float GetGirth();

float GetArea();

circle();

private:

float radius;

point centre;

};

#endif


//文件名:circle.cpp,circle类的实现

#ifndef DLL_FILE

#define DLL_FILE

#endif

#include "circle.h"

#define PI 3.1415926

//circle类的构造函数

circle::circle()

{

centre = point(0, 0);

radius = 0;

}

//得到圆的面积

float circle::GetArea()

{

return PI *radius * radius;

}

//得到圆的周长

float circle::GetGirth()

{

return 2 *PI * radius;

}

//设置圆心坐标

void circle::SetCentre(const point ¢rePoint)

{

centre = centrePoint;

}

//设置圆的半径

void circle::SetRadius(float r)

{

radius = r;

}

 

类的引用:
#include "..\circle.h"  //包含类声明头文件

#pragma comment(lib,"dllTest.lib");
int main(int argc, char *argv[])

{

circle c;

point p(2.0, 2.0);

c.SetCentre(p);

c.SetRadius(1.0);

printf("area:%f girth:%f", c.GetArea(), c.GetGirth());


return 0;

}

从上述源代码可以看出,由于在DLL的类实现代码中定义了宏DLL_FILE,故在DLL的实现中所包含的类声明实际上为:

class _declspec(dllexport) point //导出类point

{

}



class _declspec(dllexport) circle //导出类circle

{

}

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


class _declspec(dllimport) point //导入类point

{

}


class _declspec(dllimport) circle //导入类circle

{

}

不错,正是通过DLL中的


class _declspec(dllexport) class_name //导出类circle 

{

}

与应用程序中的
class _declspec(dllimport) class_name //导入类

{

}

匹对来完成类的导出和导入的!我们往往通过在类的声明头文件中用一个宏来决定使其编译为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

实际上,在MFC DLL的讲解中,您将看到比这更简便的方法,而此处仅仅是为了说明_declspec(dllexport)与_declspec(dllimport)匹对的问题。由此可见,应用工程中几乎可以看到DLL中的一切,包括函数、变量以及类,这就是DLL所要提供的强大能力。只要DLL释放这些接口,应用程序使用它就将如同使用本工程中的程序一样!

 

 

 

前面的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。

 

 

__declspec用法详解


  __declspec用于指定所给定类型的实例的与Microsoft相关的存储方式。其它的有关存储方式的修饰符如static与extern等是C和 C++语言的ANSI规范,而__declspec是一种扩展属性的定义。扩展属性语法简化并标准化了C和C++语言关于Microsoft的扩展。

用法:__declspec ( extended-decl-modifier )
extended-decl-modifier参数如下,可同时出现,中间有空格隔开:
align (C++)  __declspec用于指定所给定类型的实例的与Microsoft相关的存储方式。其它的有关存储方式的修饰符如static与extern等是C和 C++语言的ANSI规范,而__declspec是一种扩展属性的定义。扩展属性语法简化并标准化了C和C++语言关于Microsoft的扩展。
用法:__declspec ( extended-decl-modifier )
extended-decl-modifier参数如下,可同时出现,中间有空格隔开:
align (C++)
allocate
appdomain
deprecated (C++)
dllimport
dllexport
jitintrinsic
naked (C++)
noalias
noinline
noreturn
nothrow (C++)
novtable
process
property(C++)
restrict
selectany
thread
uuid(C++)
1.__declspec关键字应该出现在简单声明的前面。对于出现在*或&后面或者变量声明中标识符的前面的__declspec,编译器将忽略并且不给出警告。
2.要注意区分__declspec是修饰类型还是修饰变量:
__declspec(align(8)) struct Str b;修饰的是变量b。其它地方定义的struct Str类型的变量将不受__declspec(align(8))影响。
__declspec(align(8)) struct Str {};修饰的是struct Str类型。所有该类型的变量都受__declspec(align(8))影响。

align
格式:__declspec(align(n)) declarator
其中,n是对齐参数,其有效值是2的整数次幂(从1到8192字节),如2,4,8,16,32或64。参数declarator是要设置对齐方式的数据。
1.使用__declspec(align(n))来精确控制用户自定义数据的对齐方式。你可以在定义struct,union,class或声明变量时使用__declspec(align(n))。
2.不能为函数参数使用__declspec(align(n))。
3. 如果未使用__declspec(align(#)),编译器将根据数据大小按自然边界对齐。如4字节整数按4字节边界对齐;8字节double按8字节 边界对齐。类或结构体中的数据,将取数据本身的自然对齐方式和#pragma pack(n)设置的对齐系数中的最小值进行对齐。
4.__declspec(align(n))和#pragma pack(n)是一对兄弟,前者规定了对齐系数的最小值,后者规定了对齐系数的最大值。
5.当两者同时出现时,前者拥有更高的优先级。即,当两者同时出现且值矛盾时,后者将不起作用。
6.当变量size大于等于#pragma pack(n)指定的n,而且__declspec(align(n))指定的数值n比对应类型长度小的时候,这个__declspec(align(n))指定将不起作用。
7.当#pragma pack(n)指定的值n大于等于所有数据成员size的时候,这个值n将不起作用。

allocate
格式:__declspec(allocate("segname")) declarator
为数据指定存储的数据段。数据段名必须为以下列举中的一个:
code_seg
const_seg
data_seg
init_seg
section

appdomain
指定托管程序中的每个应用程序域都要有一份指定全局变量或静态成员变量的拷贝。

deprecated
与#pragma deprecated()的作用相同。用于指定函数的某个重载形式是不推荐的。当在程序中调用了被deprecated修饰的函数时,编译器将给出C4996警告,并且可以指定具体的警告信息。该警告信息可以来源于定义的宏。
例如:
// compile with: /W3
#define MY_TEXT "function is deprecated"
void func1(void) {}
__declspec(deprecated) void func1(int) {}
__declspec(deprecated("** this is a deprecated function **")) void func2(int) {}
__declspec(deprecated(MY_TEXT)) void func3(int) {}

int main() {
   func1();
   func1(1);   // C4996,警告信息:warning C4996: 'func1': was declared deprecated
   func2(1);   // C4996,警告信息:warning C4996: 'func2': ** this is a deprecated function **
   func3(1);   // C4996,警告信息:warning C4996: 'func3': function is deprecated
}

dllimport,dllexport
格式:
__declspec( dllimport ) declarator
__declspec( dllexport ) declarator
分别用来从dll导入函数,数据,或对象以及从dll中导出函数,数据,或对象。相当于定义了dll的接口,为它的客户exe或dll定义可使用的函数,数据,或对象。
将函数声明成dllexport就可以免去定义模块定义(.DEF)文件。
dllexport代替了__export关键字。
被声明为dllexport的C++函数导出时的函数名将会按照C++规则经过处理。如果要求不按照C++规则进行名字处理,请使用.def文件或使用extern "C"。

jitintrinsic
格式:__declspec(jitintrinsic)
用于标记一个函数或元素是64位通用语言运行时(CLR)。主要用于Microsoft提供的某些库中。
使用jitintrinsic会在函数签名中加入MODOPT(IsJitIntrinsic)。

naked
格式:__declspec(naked) declarator
此关键字仅用于x86系统,多用于虚拟设备驱动。此关键字可以使编译器在生成代码时不包含任何注释或标记。仅可以对函数的定义使用,不能用于数据声明、定义,或者函数的声明。

noalias
仅适用于函数,它指出该函数是半纯粹的函数。半纯粹的函数是指仅引用或修改局部变量、参数和第一层间接参数。它是对编译器的一个承诺,如果该函数引用全局变量或第二层间接指针参数,则编译器会生成中断应用程序的代码。

restrict
格式:__declspec(restrict) return_type f();
仅 适用于返回指针的函数声明或定义,如,CRT的malloc函数:__declspec(restrict) void *malloc(size_t size);它告诉编译器该函数返回的指针不会与任何其它的指针混淆。它为编译器提供执行编译器优化的更多信息。对于编译器来说,最大的困难之一是确定哪 些指针会与其它指针混淆,而使用这些信息对编译器很有帮助。有必要指出,这是对编译器的一个承诺,编译器并不对其进行验证。如果您的程序不恰当地使用 __declspec(restrict),则该程序的行为会不正确。

noinline
因为在类定义中定义的成员函数默认都是inline的,__declspec(naked)用于显式指定类中的某个函数不需要inline(内联)。如果一个函数很小而且对系统性能影响不大,有必要将其声明为非内敛的。例如,用于处理错误情况的函数。

noreturn
一个函数被__declspec(noreturn)所修饰,那么它的含义是告诉编译器,这个函数不会返回,其结果是让编译器知道被修饰为__declspec(noreturn)的函数之后的代码不可到达。
如果编译器发现一个函数有无返回值的代码分支,编译器将会报C4715警告,或者C2202错误信息。如果这个代码分支是因为函数不会返回从而无法到达的话,可以使用约定__declspec(noreturn)来避免上述警告或者错误。
将一个期望返回的函数约定为__declspec(noreturn)将导致未定义的行为。
在下面的这个例子中,main函数没有从else分支返回,所以约定函数fatal为__declspec(noreturn)来避免编译或警告信息。
__declspec(noreturn) extern void fatal () {}
int main() {
if(1)
   return 1;
else if(0)
   return 0;
else
   fatal();
}

nothrow:
格式:return-type __declspec(nothrow) [call-convention] function-name ([argument-list])
可用于函数声明。告诉编译器被声明的函数以及函数内部调用的其它函数都不会抛出异常。

novtable
可用于任何类声明中,但最好只用于纯接口类,即类本身从不实例化。此关键字的声明将阻止编译器对构造和析构函数的vfptr的初始化。可优化编译后代码大小。
如果试图实例化一个用__declspec(novtable)声明的类然后访问类中成员,则会在运行时产生访问错误(access violation,即AV)。

process
表示你的托管应用程序进程应该拥有一份指定全局变量,静态成员变量,或所有应用程序域 共享的静态本地变量的拷贝。在使用/clr:pure进行编译时,应该使用__declspec(process),因为使用/clr:pure进行编译 时,在默认情况下,每个应用程序域拥有一份全局和静态变量的拷贝。在使用/clr进行编译时,不必使用__declspec(process),因为使用 /clr进行编译时,在默认情况下,每个进程有一份全局和静态变量的拷贝。
只有全局变量,静态成员变量,或本地类型的本地静态变量可以用__declspec(process)修饰。
在使用/clr:pure进行编译时,被声明为__declspec(process)的变量同时也应该声明为const类型。
如果想每个应用程序域拥有一份全局变量的拷贝时,请使用appdomain。

property
格式:
__declspec( property( get=get_func_name ) ) declarator
__declspec( property( put=put_func_name ) ) declarator
__declspec( property( get=get_func_name, put=put_func_name ) ) declarator
该 属性可用于类或结构定义中的非静态“虚数据成员”。实际上就是做了一个映射,把你的方法映射成属性,以供访问。get和put就是属性访问的权限,一个是 读的权限,一个是写的权限。当编译器看到被property修饰的数据成员出现在成员选择符("." 或 "->")的右边的时候,它将把该操作转换成get或put方法。该修饰符也可用于类或结构定义中的空数组。
用法如下:
struct S {
   int i;
   void putprop(int j) {
      i = j;
   }
   int getprop() {
      return i;
   }
   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}

selectany
格式:__declspec(selectany) declarator
在 MFC,ATL的源代码中充斥着__declspec(selectany)的声明。selectany可以让我们在.h文件中初始化一个全局变量而不是 只能放在.cpp中。比如有一个类,其中有一个静态变量,那么我们可以在.h中通过类似__declspec(selectany) type class::variable = value;这样的代码来初始化这个全局变量。既是该.h被多次include,链接器也会为我们剔除多重定义的错误。对于template的编程会有很 多便利。
用法如下:

__declspec(selectany) int x1=1; //正确,x1被初始化,并且对外部可见

const __declspec(selectany) int x2 =2; //错误,在C++中,默认情况下const为static;但在C中是正确的,其默认情况下const不为static

extern const __declspec(selectany) int x3=3; //正确,x3是extern const,对外部可见

extern const int x4;
const __declspec(selectany) int x4=4; //正确,x4是extern const,对外部可见

extern __declspec(selectany) int x5; //错误,x5未初始化,不能用__declspec(selectany)修饰

class X {
public:
X(int i){i++;};
int i;
};

__declspec(selectany) X x(1); //正确,全局对象的动态初始化

thread
格式:__declspec(thread) declarator
声明declarator为线程局部变量并具有线程存储时限,以便链接器安排在创建线程时自动分配的存储。
线程局部存储(TLS)是一种机制,在多线程运行环境中,每个线程分配自己的局部数据。在标准多线程程序中,数据是在多个线程间共享的,而TLS是一种为每个线程分配自己局部数据的机制。
该属性只能用于数据或不含成员函数的类的声明和定义,不能用于函数的声明和定义。
该属性的使用可能会影响DLL的延迟载入。
该属性只能用于静态数据,包括全局数据对象(static和extern),局部静态对象,类的静态数据成员;不能用于自动数据对象。
该属性必须同时用于数据的声明和定义,不管它的声明和定义是在一个文件还是多个文件。
__declspec(thread)不能用作类型修饰符。
如果在类声明的同时没有定义对象,则__declspec(thread)将被忽略,例如:

// compile with: /LD
__declspec(thread) class X
{
public:
   int I;
} x;   //x是线程对象
X y;   //y不是线程对象

下面两个例子从语义上来说是相同的:

__declspec(thread) class B {
public:
   int data;
} BObject;   //BObject是线程对象

class B2 {
public:
   int data;
};
__declspec(thread) B2 BObject2;   // BObject2是线程对象

uuid
格式:__declspec( uuid("ComObjectGUID") ) declarator
将具有唯一标识符号的已注册内容声明为一个变量,可使用__uuidof()调用。
用法如下:
struct __declspec(uuid("00000000-0000-0000-c000-000000000046")) IUnknown;
struct __declspec(uuid("{00020400-0000-0000-c000-000000000046}")) IDispatch;

 

 

分享到:
评论

相关推荐

    Visual C++编程疑难详解(DLL章节节选)

    《Visual C++编程疑难详解》(DLL章节节选)

    C#调用C++ Dll关于结构体数组引用的传递及解析使用的展示代码

    ### C#调用C++ DLL:结构体数组引用的传递及解析使用详解 #### 引言 在跨语言编程环境中,经常会遇到不同编程语言之间进行交互的需求。C#与C++之间的互操作就是一个典型场景。当C#需要调用C++开发的动态链接库...

    C++dll查看器

    **C++ DLL 查看器详解** DLL(Dynamic Link Library)是Windows操作系统中的一种共享库机制,它允许多个程序共享同一段代码和数据,从而节省内存并简化软件开发。C++ DLL 查看器是一款专用于查看DLL文件内部结构的...

    C++socket编程详解

    ### C++ Socket编程详解 #### 一、C++ Socket编程概览 在现代软件开发中,网络编程是一项不可或缺的技术,特别是在分布式系统、互联网应用及移动应用等领域。**Visual C++**(简称VC)作为一种强大的开发工具,...

    ual C++编程疑难详解_ualC++编程疑难详解_

    《ual C++编程疑难详解》是一本专注于解决MFC(Microsoft Foundation Classes)编程中常见问题与挑战的专业书籍。MFC是微软提供的一套C++类库,它为开发者提供了构建Windows应用程序的框架,极大地简化了Windows API...

    一步一步教你用VC和VB调用C++ DLL.docx

    总结来说,本教程通过四个部分详细介绍了如何在VC++和VB中调用C++ DLL,涵盖了从创建DLL到在不同环境下调用的全过程,对于理解和实践DLL编程具有很高的指导价值。理解这些概念和步骤,开发者可以灵活地在不同语言...

    C#调用C++Dll资料大全葵花宝典.rar

    5. **C#(Csharp)调用C++编写的DLL动态链接库方法详解(含实例).pptx**:这是一份PPT教程,详细介绍了C#调用C++Dll的步骤和方法,包括如何定义DllImport特性,处理数据类型映射,以及解决跨语言调用时可能出现的问题...

    VC++ DLL动态链接库编程详解

    VC++ DLL编程是一项重要的技能,它能够帮助开发者构建高效的、可扩展的应用程序。理解并掌握DLL的工作原理、创建方法以及使用技巧,对于提升软件开发的质量和效率具有显著的作用。通过阅读《VC++ DLL动态链接库编程...

    MFC下DLL编程(图解)

    ### MFC 下 DLL 编程详解 #### 一、引言 动态链接库(Dynamic Link Library,简称 DLL)是微软为 Windows 和 OS/2 操作系统设计的一种供应用程序在运行时调用的共享函数库。DLL 作为一种重要的共享组件,在 Windows...

    Visual C++DLL动态链接库编程

    ### Visual C++ DLL动态链接库编程详解 #### 一、基础概念 ##### 1.1 概述 在深入探讨动态链接库(Dynamic Linkable Library, DLL)之前,我们需要先理解DLL的基本概念及其与静态链接库的区别。简单来说,DLL可以被...

    DLL技术详解

    "DLL技术详解" DLL(动态链接库)是Windows系统的核心,也是COM技术的基础。掌握动态链接库一直是技术人员的攻坚目标。DLL文件是一种不能单独运行的文件,它允许程序共享执行特殊任务所必需的代码和其他资源。 DLL...

    C++中DLL制作过程

    《C++中DLL制作过程详解》 动态链接库(DLL,Dynamic Link Library)在Windows操作系统中扮演着至关重要的角色,它是实现程序间资源共享和代码复用的重要机制。自16位Windows时代以来,DLL就成为了操作系统的核心...

    C++类库开发详解

    在C++编程中,类库开发是至关重要的一个环节,它允许程序员创建可重用的代码模块,提高软件开发的效率和质量。本教程“C++类库开发详解”将引领你深入理解如何构建和使用自己的C++类库。下面我们将详细探讨C++类库...

    java调用dll详解.rar

    在Java编程环境中,有时我们需要利用已经存在的DLL(动态链接库)文件来实现特定的功能,这通常是C++或C语言编写的本地代码。本资源详细阐述了如何在Java中调用DLL,以及在这个过程中可能遇到的问题和注意事项。 ...

    EVC_dll.rar_EVC_EVC D_EVC dll_EVC3_dll编程

    **EVC_DLL编程详解** EVC(Embedded Visual C++)是由微软公司推出的一款针对嵌入式系统的C++开发工具,特别适用于Windows CE平台的应用程序开发。EVC3.0是其第三个版本,提供了对Windows CE 3.0及更高版本的支持。...

    C#调用C++的dll文件

    ### C#调用C++的DLL文件:详细解析与实践 #### 一、背景与概述 在跨语言编程中,经常需要实现不同编程语言之间的互操作性。其中一个常见的场景是C#应用程序调用C++编写的DLL(动态链接库)。这种需求通常出现在...

    VC++数据库编程.pdf/VC++深入详解.pdf/VC++动态链接库(DLL)编程.pdf

    在给定的压缩包文件中,我们关注的焦点主要集中在三个主题上:VC++数据库编程、VC++动态链接库(DLL)编程以及深入理解VC++的相关知识。这些资源提供了广泛的信息,帮助开发者深入掌握Microsoft Visual C++(VC++)的...

Global site tag (gtag.js) - Google Analytics