c++ 中__declspec 的相关用法

其他相关文章:https://www.cnblogs.com/whiteyun/archive/2011/07/22/2113668.html

1.__declspec(dllexport)和__declspec(dllimport)

(1)解决的问题:

        考虑下面的需求,使用一个方法,一个是提供者,一个是使用者,二者之间的接口是头文件。头文件中声明了方法,在提供者那里方法应该被声明为__declspec(dllexport),在使用者那里,方法应该被声明为__declspec(dllimport)。二者使用同一个头文件,作为接口,怎么办呢?

(2)解决办法:

  使用条件编译:定义一个变量,针对提供者和使用者,设置不同的值。

#ifndef DLL_H_
#define DLL_H_

#ifdef DLLProvider
#define DLL_EXPORT_IMPORT __declspec(dllexport)
#else
#define DLL_EXPORT_IMPORT __declspec(dllimport)
#endif

DLL_EXPORT_IMPORT int add(int, int);

#endif

(3)注意问题:__declspec(dllimport)的作用主要体现在导出类的静态成员方面
比如在动态链接库中定义这样一个导出类

class __declspec(dllexport) CBtt  
{  
public:  
    CBtt(void);  
    ~CBtt(void);  
public:  
    CString m_str;  
    static int GetValue()  
    {  
        return m_nValue;  
    }  
private:  
    static int m_nValue;  
};  

照上面这样声明,外部虽然可以使用CBtt类,但不能使用CBtt类的GetValue函数,一使用就会出现无法解析的外部符号 "public: static int CBtt::m_nValue" (?m_nValue@CBtt@@2HA)。只有如下声明才能使用CBtt类的GetValue函数:

#ifdef _EXPORTING  
#define API_DECLSPEC    __declspec(dllexport)  
#else  
#define API_DECLSPEC    __declspec(dllimport)  
#endif  
class API_DECLSPEC CBtt  
{  
public:  
    CBtt(void);  
    ~CBtt(void);  
public:  
    CString m_str;  
    static int GetValue()  
    {  
        return m_nValue;  
    }  
private:  
    static int m_nValue;  
};  

原因是静态成员如果不import,是不能够被编译器从lib文件里找到的。使用dll的工程在编译时也会将dll相关的头文件列入编译对象,而不会理会dll的cpp文件中的初始化过程,因此会出现a没有定义的情况,这时__declspec(dllimport)就派上用场了,他会告诉使用dll的工程去lib中找到这个静态变量的定义。

以下为转载:https://blog.csdn.net/acoolgiser/article/details/99735282

首先要知道,头文件是C++的接口文件,不仅本工程需要使用头文件来进行编译,给其他工程提供dll的时候也要提供此dll的头文件才能让其他人通过编程的方式来使用dll。记住:头文件要给自己用还要给别人用。

  比如一个项目中的Class中含有一个静态变量,生成dll的时候只采用了__declspec(dllexport) 如下:

dll工程

A1.h:
 
  #define OS_API_EXPORT __declspec(dllexport)
 
  class OS_API_EXPORT A {static int a;}
 
 
A.cpp:
 
  #include “A.h”
 
  static A::a=0; //静态变量的初始化要写在cpp文件中

这样做的时候编译dll工程的时候没有问题,但是如果把dll和头文件提供给别人使用的时候就会出“unsloved symbol a”的问题。

  原因是静态成员如果不import,是不能够被编译器从lib文件里找到的

  使用dll的工程在编译时也会将dll相关的头文件列入编译对象,而不会理会dll的cpp文件中的初始化过程,因此会出现a没有定义的情况,这时

__declspec(dllimport)就派上用场了,他会告诉使用dll的工程去lib中找到这个静态变量的定义。提供给别人使用的dll头文件应当写成:
A2.h:
 
  #define OS_API_IMPORT __declspec(dllimport)
 
  class OS_API_IMPORT A {static int a;}

当使用A.dll的工程链接上A2.h后,就不会出现“unsloved symbol a”的问题了。

最终为了方便程序的开发,不用分别写出dll工程的头文件和使用dll工程的头文件,头文件可以写为如下形式:

A.h
 
    #define OS_API_IMPORT __declspec(dllimport)
      #define OS_API_EXPORT __declspec(dllexport)
 
      #ifdef BUILD_DLL
      #define OS_API OS_API_EXPORT //如果是生成dll工程,那么导出
      #else
      #define OS_API OS_API_IMPORT //如果是生成使用dll的工程,那么导入
      #endif 
 
      class OS_API A{static int a;}

 

  同时别忘了在dll工程属性下设置预处理器定义BUILD_DLL

下面说明一下“在dll工程属性下设置预处理器定义BUILD_DLL”:

具体的设置是在项目上面点击右键,打开属性页,如图所示,找到预处理器定义,进行设置。

c++ 中__declspec 的相关用法_第1张图片

(4)加__declspec(dllimport)的好处

①不加__declspec(dllimport)时,在使用dll中的函数时,编译器并不能区别这是个普通函数,还是从其它dll里导入的函数,所以其生 成的代码如下:

call 地址1

地址1:
jmp 实际函数地址

②有 __declspec(dllimport)时,编译器知道这是要从外部dll导入的函数,从而在生成的exe的输入表里留有该项,以便在运行 exe,PE载入器加载exe时对输入地址表IAT进行填写,这样生成的代码如下:

call dword ptr[输入表里哪项对应的内存地址] (注意:现在就不需要jmp stub了)。这里
有兴趣的朋友可以参看《编译原理》和 PE文件格式。

2.__declspec(align(#))精确控制用户自定数据的对齐方式

#是对齐值

__declspec(align(32)) 
struct Str1
{
   int a, b, c, d, e;
};

它与#pragma pack()是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值。同时出现时,前者优先级高。 __declspec(align())的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:在__declspec(align())之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec(align())的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值)),然后把被指定的数据类型从这个
点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec(align())。当所有数据填充完毕,把结构的整体对齐数值和__declspec(align())规定的值做比较,取其中较大的作为整个结构的对齐长度。 特别的,当__declspec(align())指定的数值比对应类型长度小的时候,这个指定不起作用

3.__declspec(allocate("segname"))

用__declspec(allocate("segname")) 声明一个已经分配了数据段的一个数据项。它和#pragma 的code_seg, const_seg, data_seg,section,init_seg配合使用,segname必须有这些东东声明

#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;

4.__declspec(deprecated)

用__declspec(deprecated ) 说明一个函数,类型,或别的标识符在新的版本或未来版本中不再支持,你不应该用这个函数或类型。它和#pragma deprecated作用一样

#define MY_TEXT "function is deprecated"
void func1(void) {}
__declspec(deprecated) void func1(int) { printf("func1n");}
__declspec(deprecated("** this is a deprecated function **")) void func2(int) { printf("func2n");}
__declspec(deprecated(MY_TEXT)) void func3(int) { printf("func3");} 
int main()
{  
fun1();  
fun2(); 
fun3();
}

5.__declspec(jitintrinsic)

用__declspec(jitintrinsic)标记一个函数或元素为64位公共语言运行时。具体用法未见到。

6. __declspec( naked ) 

对于没有用naked声明的函数一般编译器都会产生保存现场(进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器 ——prolog)和清除现场(退出函数时则产生代码恢复这些寄存器的内容——epilog) 代码,而对于用naked声明的函数一般不会产生这些代码,这个属性对于写设备驱动程序非常有用,我们自己可以写这样一个过程,它仅支持x86 。naked只对函数有效,而对类型定义无效。对于一个标志了naked的函数不能产生一个内联函数即使使用了__forceinline 关键字

void __declspec(naked) func()
{
	int i;
	int j;
	__asm    /* prolog */
	{
		push ebp
		mov    ebp, esp
		sub    esp, __LOCAL_SIZE
	}
	/* Function body */
	__asm    /* epilog */
	{
		mov    esp, ebp
		pop    ebp
		ret
	}
}

7.__declspec(restrict) 和 __declspec(noalias)

__declspec(restrict) 和 __declspec(noalias)用于提高程序性能,优化程序。这两个关键字都仅用于函数,restrict针对于函数返回指针,restrict 说明函数返回值没有被别名化,返回的指针是唯一的,没有被别的函数指针别名花,也就是说返回指针还没有被用过是唯一的。编译器一般会去检查指针是否可用和 是否被别名化,是否已经在使用,使用了这个关键字,编译器就不在去检查这些信息了。noalias 意味着函数调用不能修改或引用可见的全局状态并且仅仅修改指针参数直接指向的内存。如果一个函数指定了noalias关键字,优化器认为除参数自生之外, 仅仅参数指针第一级间接是被引用或修改在函数内部。可见全局状态是指没有定义或引用在编码范围外的全部数据集,它们的直至不可以取得。编码范围是指所有源 文件或单个源文件。其实这两个关键字就是给编译器了一种保证,编译器信任他就不在进行一些检查操作了

#include 
#include 
#define M 800#define N 600#define P 700float * 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

8. noinline__declspec(noinline) 

告诉编译器不去具体内联一个具体函数

9. noreturn__declspec(noreturn) 

告诉编译器没有返回值.注意添加__declspec(noreturn)到一个不希望返回的函数会导致已没有定义错误. 

10.nothrow__declspec(nothrow) 

用于函数声明,它告诉编译器函数不会抛出异常

#define WINAPI __declspec(nothrow) __stdcall
void WINAPI f1();
void __declspec(nothrow) __stdcall f2();
void __stdcall f3() throw();

11.novtable __declspec(novtable)

用在任意类的声明,但是只用在纯虚接口类,因此这样的不能够被自己实例话.它阻止编译器初始化虚表指针在构造和析构类的时候,这将移除对关联到类的虚表的 引用.如果你尝试这实例化一个有novtable关键字的类,它将发生AV(access violation)错误.C++里virtual的缺陷就是vtable会增大代码的尺寸,在不需要实例化的类或者纯虚接口的时候,用这个关键字可以减 小代码的大小.

#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版本没有这个限制

#include 
struct __declspec(novtable) X
{
	virtual void mf();
};
struct Y : public X
{
	void mf()
	{
		printf_s("In Yn");
	}
};

12.__declspec(selectany)

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

class test
{
  public: 
  static int t;
};
__declspec(selectany) int test::t = 0; 

13. __declspec(thread)

thread 用于声明一个线程本地变量. __declspec(thread)的前缀是Microsoft添加给Visual C++编译器的一个修改符。它告诉编译器,对应的变量应该放入可执行文件或DLL文件中它的自己的节中。__declspec(thread)后面的变量 必须声明为函数中(或函数外)的一个全局变量或静态变量。不能声明一个类型为__declspec(thread)的局部变量

__declspec(thread) 
class X
{
public: 
  int I; 
} x; // x is a thread objectX y; // y is not a thread object

14.uuid__declspec(uuid)

用于编译器关联一个GUID到一个有uuid属性的类或结构的声明或者定义

struct __declspec(uuid("00000000-0000-0000-c000-000000000046")) IUnknown;struct __declspec(uuid("{00020400-0000-0000-c000-000000000046}")) IDispatch;我们可以在MFC中查看源码.:)

你可能感兴趣的:(c++ 中__declspec 的相关用法)