__declspec的用法
__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
用__declspec(allocate("segname")) 声明一个已经分配了数据段的一个数据项。它和#pragma 的code_seg, const_seg, data_seg,section,init_seg配合使用,segname必须由这些东东声明。
e.g #pragma data_seg("share_data") int a = 0; int b; #pragma data_seg() __declspec(allocate("share_data")) int c = 1; __declspec(allocate("share_data")) int d;
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"。
//e.g 常规方式dll中 class ___declspec(dllexport) testdll{ testdll(){}; ~testdll(){}; }; //调用客户端中声明 #import comment(lib, "**.lib) class ___declspec(dllimportt) testdll{ testdll(){}; ~testdll(){}; }; //e.g 模板类:dll中 template<class t> class test{ test(){}; ~test(){}; } //调用客户端中声明 int main() { test< int > b; return 0; }
jitintrinsic:
格式:__declspec(jitintrinsic)
用于标记一个函数或元素是64位通用语言运行时(CLR)。主要用于Microsoft提供的某些库中。
使用jitintrinsic会在函数签名中加入MODOPT(IsJitIntrinsic)。
naked:
格式:__declspec(naked) declarator
此关键字仅用于x86系统,多用于虚拟设备驱动。此关键字可以使编译器在生成代码时不包含任何注释或标记。仅可以对函数的定义使用,不能用于数据声明、定义,或者函数的声明。
对于没有用naked声明的函数一般编译器都会产生保存现场(进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器 ——prolog)和清除现场(退出函数时则产生代码恢复这些寄存器的内容——epilog)代码,而对于用naked声明的函数一般不会产生这些代码,这个属性对于写设备驱动程序非常有用,我们自己可以写这样一个过程,它仅支持x86 。naked只对函数有效,而对类型定义无效。对于一个标志了naked的函数不能产生一个内联函数即使使用了__forceinline 关键字。
//e.g __declspec ( naked ) func() { int i; int j; __asm { push ebp mov ebp, esp sub esp, __LOCAL_SIZE } __asm { mov esp, ebp pop ebp ret } }
noalias:
仅适用于函数,它指出该函数是半纯粹的函数。半纯粹的函数是指仅引用或修改局部变量、参数和第一层间接参数。它是对编译器的一个承诺,如果该函数引用全局变量或第二层间接指针参数,则编译器会生成中断应用程序的代码。
restrict:
格式:__declspec(restrict) return_type f();
仅适用于返回指针的函数声明或定义,如,CRT的malloc函数:__declspec(restrict) void *malloc(size_t size);它告诉编译器该函数返回的指针不会与任何其它的指针混淆。它为编译器提供执行编译器优化的更多信息。对于编译器来说,最大的困难之一是确定哪些指针会与其它指针混淆,而使用这些信息对编译器很有帮助。有必要指出,这是对编译器的一个承诺,编译器并不对其进行验证。如果您的程序不恰当地使用__declspec(restrict),则该程序的行为会不正确。
------------------------------------------------------------------------------------------------------------------------------
__declspec(restrict) 和 __declspec(noalias)用于提高程序性能,优化程序。这两个关键字都仅用于函数,restrict针对于函数返回指针,restrict 说明函数返回值没有被别名化,返回的指针是唯一的,没有被别的函数指针别名花,也就是说返回指针还没有被用过是唯一的。编译器一般会去检查指针是否可用和是否被别名化,是否已经在使用,使用了这个关键字,编译器就不在去检查这些信息了。noalias 意味着函数调用不能修改或引用可见的全局状态并且仅仅修改指针参数直接指向的内存。如果一个函数指定了noalias关键字,优化器认为除参数自生之外,仅仅参数指针第一级间接是被引用或修改在函数内部。可见全局状态是指没有定义或引用在编码范围外的全部数据集,它们的直至不可以取得。编码范围是指所有源文件或单个源文件。其实这两个关键字就是给编译器了一种保证,编译器信任他就不在进行一些检查操作了。
//e.g #include <stdio.h> #include <stdlib.h> #define M 800
#define N 600
#define P 700
float * mempool, * memptr; __declspec(restrict) float * ma(int size) { float * retval; retval = memptr; memptr += size; return retval; } __declspec(restrict) float * init(int m, int n) { float * a; int i, j; int k=1; a = ma(m * n); if (!a) exit(1); for (i=0; i<m; i++) for (j=0; j<n; j++) a[i*n+j] = 0.1/k++; return a; } __declspec(noalias) void multiply(float * a, float * b, float * c) { int i, j, k; for (j=0; j<P; j++) for (i=0; i<M; i++) for (k=0; k<N; k++) c[i * P + j] = a[i * N + k] * b[k * P + j]; } int main() { float * a, * b, * c; mempool = (float *) malloc(sizeof(float) * (M*N + N*P + M*P)); if (!mempool) puts("ERROR: Malloc returned null"); exit(1); memptr = mempool; a = init(M, N); b = init(N, P); c = init(M, P); multiply(a, b, c); }
-------------------------------------------------------------------------------------------------------------------------------
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])
可用于函数声明。告诉编译器被声明的函数以及函数内部调用的其它函数都不会抛出异常。
//e.g #define WINAPI __declspec(nothrow) __stdcall void WINAPI f1(); void __declspec(nothrow) __stdcall f2(); void __stdcall f3() throw();
novtable:
可用于任何类声明中,但最好只用于纯接口类,即类本身从不实例化。此关键字的声明将阻止编译器对构造和析构函数的vfptr的初始化。可优化编译后代码大小。
如果试图实例化一个用__declspec(novtable)声明的类然后访问类中成员,则会在运行时产生访问错误(access violation,即AV)。
用在任意类的声明,但是只用在纯虚接口类,因此这样的不能够被自己实例话.它阻止编译器初始化虚表指针在构造和析构类的时候,这将移除对关联到类的虚表的引用.如果你尝试这实例化一个有novtable关键字的类,它将发生AV(access violation)错误.C++里virtual的缺陷就是vtable会增大代码的尺寸,在不需要实例化的类或者纯虚接口的时候,用这个关键字可以减小代码的大小.
//e.g #if _MSC_VER >= 1100 && !defined(_DEBUG) #define AFX_NOVTABLE __declspec(novtable) #else #define AFX_NOVTABLE #endif .... class AFX_NOVTABLE CObject { ... };
这是vc里面的一段代码,我们可以看出编译Release版本时,在CObject前是__declspec(novtable),在debug版本没有这个限制。
//e.g #include <stdio.h> struct __declspec(novtable) X { virtual void mf(); }; struct Y : public X { void mf() { printf_s("In Yn"); } };
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是线程对象
thread 用于声明一个线程本地变量. __declspec(thread)的前缀是Microsoft添加给Visual C++编译器的一个修改符。它告诉编译器,对应的变量应该放入可执行文件或DLL文件中它的自己的节中。__declspec(thread)后面的变量必须声明为函数中(或函数外)的一个全局变量或静态变量。不能声明一个类型为__declspec(thread)的局部变量。
//e.g __declspec(thread) class X{ public: int I; } x; // x is a thread objectX y; // y is not a thread object
uuid:
格式:__declspec( uuid("ComObjectGUID") ) declarator
将具有唯一标识符号的已注册内容声明为一个变量,可使用__uuidof()调用。
用法如下:
struct __declspec(uuid("00000000-0000-0000-c000-000000000046")) IUnknown; struct __declspec(uuid("{00020400-0000-0000-c000-000000000046}")) IDispatch;
/***************************************************************/
__declspec(dllexport)和__declspec(dllimport)
1、解决的问题:
考虑下面的需求,使用一个方法,一个是提供者,一个是使用者,二者之间的接口是头文件。头文件中声明了方法,在提供者那里方法应该被声明为 __declspec(dllexport),在使用者那里,方法应该被声明为__declspec(dllimport)。二者使用同一个头文件,作为 接口,怎么办呢?
2、解决办法:
使用条件编译:定义一个变量,针对提供者和使用者,设置不同的值。
1 #ifndef DLL_H_
2 #define DLL_H_
3
4 #ifdef DLLProvider
5 #define DLL_EXPORT_IMPORT __declspec(dllexport)
6 #else
7 #define DLL_EXPORT_IMPORT __declspec(dllimport)
8 #endif
9
10 DLL_EXPORT_IMPORT int add(int ,int);
11
12 #endif
上面代码里面的_delcspce(dllexport)被定义为宏,这样可以提高程序的可读性!这个的作是是将函数定义为导出函数,也就是说这个 函数要被包含这个函数的程序之外的程序调用!本语句中就是:int add(int ,int);
摘自msdn:在 32 位编译器版本中,可以使用 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数。__declspec(dllexport) 将导出指令添加到对象文件.
若要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:
__declspec(dllexport) void Function1(void);
若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:
class __declspec(dllexport) CExampleExport : public CObject
{
... class definition ...
};
生成 DLL 时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将 __declspec(dllexport) 添加到头文件中的声明。若要提高代码的可读性,请为 __declspec(dllexport) 定义一个宏并对正在导出的每个符号使用该宏:
#define DllExport __declspec( dllexport )
--------------------------------------------------------------------------------------------------
我相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用__declspec(dllimport) 才能导入 DLL 中使用的变量这个是什么意思??
那我就来试验一下,假定,你在DLL里只导出一个简单的类,注意,我假定你已经在项目属性中定义了 SIMPLEDLL_EXPORT.
//SimpleDLLClass.h
#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
class DLL_EXPORT SimpleDLLClass
{
public:
SimpleDLLClass();
virtual ~SimpleDLLClass();
virtual getValue() { return m_nValue;};
private:
int m_nValue;
};
//SimpleDLLClass.cpp
#include "SimpleDLLClass.h"
SimpleDLLClass::SimpleDLLClass()
{
m_nValue=0;
}
SimpleDLLClass::~SimpleDLLClass()
{
}
然后你再使用这个DLL类,在你的APP中include SimpleDLLClass.h时,你的APP的项目不用定义 SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不会存在了,这个时候,你在APP中,不会遇到问题。这正好对应MSDN上说的__declspec(dllimport)定义与否都可以正常使用。但我们也没有遇到变量不能正常使用呀。 那好,我们改一下SimpleDLLClass,把它的m_nValue改成static,然后在cpp文件中加一行.
int SimpleDLLClass::m_nValue=0;
如果你不知道为什么要加这一行,那就回去看看C++的基础。 改完之后,再去LINK一下,你的APP,看结果如何, 结果是LINK告诉你找不到这个m_nValue。明明已经定义了,为什么又没有了?? 肯定是因为我把m_nValue定义为static的原因。但如果我一定要使用Singleton的Design Pattern的话,那这个类肯定是要有一个静态成员,每次LINK都没有,那不是完了? 如果你有Platform SDK,用里面的Depend程序看一下,DLL中又的确是有这个m_nValue导出的呀。 再回去看看我引用MSDN的那段话的最后一句。 那我们再改一下SimpleDLLClass.h,把那段改成下面的样子:
#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
再LINK,一切正常。原来dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓。