您的位置:首页 > 其它

#Pragma指令简介

2013-02-18 14:24 309 查看
pragma指令简介
在编写程序的时候,我们经常要用到#pragma指令来设定编译器的状态或者是指示编译器完成一些特定的动作。

一.message参数

message它能够在编译消息输出窗口中输出相应的消息,这对于源代码信息的控制非常重要的,使用方法为:
#pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的候进行检查,假设我们希望判断自己有没有源代码的什么地方定义了_X86这个宏可以用下面的方法:
#ifdef _x86
#pragma message("_x86 macro activated!")
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示"_x86 macro activated!"。

二. code_seg参数

另一个使用得比较多的#pragma参数是code_seg。格式如:
#pragma code_seg([[{push|pop},][identifier,]]["segment-name"][,"segment-class"])
该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节为.text节。如果code_seg没有带参数的话,则函数存放在.text节中。 
push (可选参数): 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名 
pop(可选参数):将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名 
identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈 
"segment-name" (可选参数) 表示函数存放的节名 
例如: 
//默认情况下,函数被存放在.text节中 
void func1() { // stored in .text 


//将函数存放在.my_data1节中 
#pragma code_seg(".my_data1") 
void func2() { // stored in my_data1 


//r1为标识符,将函数放入.my_data2节中 
#pragma code_seg(push, r1, ".my_data2") 
void func3() { // stored in my_data2 


int main() { 

三. #pragma data_seg参数

1,#pragma data_seg()一般用于DLL中。也就是说,在DLL中定义一个共享的,有名字的数据段。最关键的是:这个数据段中的全局变量可以被多个进程共享。否则多个进程之间无法共享DLL中的全局变量。
2,共享数据必须初始化,否则微软编译器会把没有初始化的数据放到.BSS段中,从而导致多个进程之间的共享行为失败。
3,你所谓的结果正确是一种错觉。如果你在一个DLL中这么写:
#pragma data_seg("MyData")
int g_Value; // Note that the global is not initialized.
#pragma data_seg()

DLL提供两个接口函数:
int GetValue() {
 return g_Value;
}
void SetValue(int n){
g_Value = n;
}
然后启动两个进程A和B,A和B都调用了这个DLL,假如A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值不一定是5,而是一个未定义的值。因为DLL中的全局数据对于每一个调用它的进程而言,是私有的,不能共享的。假如你对g_Value进行了初始化,那么g_Value就一定会被放进MyData段中。换句话说,如果A调用了SetValue(5); B接着调用int m = GetValue();那么m的值就一定是5!这就实现了跨进程之间的数据通信!
----------------------------------------------------------------------------------------------------
有的时候我们可能想让一个应用程序只启动一次,就像单件模式(singleton)一样,实现的方法可能有多种,这里说说用#pragma data_seg来实现的方法,很是简洁便利。
应用程序的入口文件前面加上
#pragma data_seg("flag_data")
int app_count = 0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:flag_data,RWS")
然后程序启动的地方加上
if(app_count>0)    { // 如果计数大于0,则退出应用程序。//MessageBox(NULL, "已经启动一个应用程序", "Warning", MB_OK); 
return FALSE;
}
app_count++;

#pragma data_seg("flag_data")
   int count=0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:flag_data,RWS")
这种方法只能在没有def文件时使用,如果通过def文件进行导出的话,那么设置就要在def文件内设置而不能
在代码里设置了。
SETCTIONS 
flag_data READ WRITE SHARED

在主文件中,用#pragma data_seg建立一个新的数据段并定义共享数据,其具体格式为: 

#pragma data_seg ("shareddata") //名称可以 

//自己定义,但必须与下面的一致。 

HWND sharedwnd=NULL;//共享数据 

#pragma data_seg() 

仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的 (其效果是相同的),一种方法是在.DEF文件中加入如下语句: SETCTIONS shareddata READ WRITE SHARED 另一种方法是在项目设置链接选项(Project Setting --〉Link)中加入如下语句: /SECTION:shareddata,rws 

第一点:什么是共享数据段?为什么要用共享数据段??它有什么用途?? 
在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,DLL函数中的代码所创建的任何对象 (包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复 制该DLL的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。 

因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程之间共享存储器是通过存储器映射文件技术 实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。 

#pragma data_seg预处理指令用于设置共享数据段。例如: 

#pragma data_seg("SharedDataName") HHOOK hHook=NULL; //必须在定义的同时进行初始化!!!!
#pragma data_seg() 

在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。再加上一条指令#pragma comment(linker,"/section:.SharedDataName,rws"),[注意:数据节的名称is case sensitive]那么这个数据节中的数据可以在所有DLL的实例之间共享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。 

当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进 程的一部分,以这个进程的身份执行,使用这个进程的堆栈。(这项技术又叫code Injection技术,被广泛地应用在了病毒、黑客领域!呵呵^_^) 

第二点:在具体使用共享数据段时需要注意的一些问题! 
Win32 DLLs are mapped into the address space of the calling process. By default, each process using a DLL has its own instance of all the DLLs global and static variables. (注意: 即使是全局变量和静态变量也都不是共享的!) If your DLL needs to share data with other instances of it loaded by other applications, you can use either of the following approaches: 

· Create named data sections using the data_seg pragma. 

· Use memory mapped files. See the Win32 documentation about memory mapped files. 
Here is an example of using the data_seg pragma: 

#pragma data_seg (".myseg") 
int i = 0; 
char a[32] = "hello world"; 
#pragma data_seg() 

data_seg can be used to create a new named section (.myseg in this example). The most typical usage is to call the data segment .shared for clarity. You then must specify the correct sharing attributes for this new named data section in your .def file or with the linker option /SECTION:.MYSEC,RWS. (这个编译参数既可以使用pragma指令来指定,也可以在VC的IDE中指定!) 

There are restrictions to consider before using a shared data segment: 

· Any variables in a shared data segment must be statically initialized. In the above example, i is initialized to 0 and a is 32 characters initialized to hello world. 

· All shared variables are placed in the compiled DLL in the specified data segment. Very large arrays can result in very large DLLs. This is true of all initialized global variables. 

· Never store process-specific information in a shared data segment. Most Win32 data structures or values (such as HANDLEs) are really valid only within the context of a single process. 

· Each process gets its own address space. It is very important that pointers are never stored in a variable contained in a shared data segment. A pointer might be perfectly valid in one application but not in another. 

· It is possible that the DLL itself could get loaded at a different address in the virtual address spaces of each process. It is not safe to have pointers to functions in the DLL or to other shared variables. 

四. #pragma once(比较常用)

这是一个比较常用的指令,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次#pragma once用来防止某个头文件被多次include,#ifndef,#define,#endif用来防止某个宏被多次定义。
#pragma once是编译相关,就是说这个编译系统上能用,但在其他编译系统不一定可以,也就是说移植性差,不过现在基本上已经是每个编译器都有这个定义了。
#ifndef,#define,#endif这个是C++语言相关,这是C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式

五. #pragma hdrstop参数

#pragma hdrstop 表示预编译头文件到此为止,后面的头文件不进行预编译。 

BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。 

六. #pragma warning指令 

该指令允许有选择性的修改编译器的警告消息的行为 

指令格式如下: 
#pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...] 
#pragma warning( push[ ,n ] ) 
#pragma warning( pop ) 

主要用到的警告表示有如下几个: 

once:只显示一次(警告/错误等)消息 
default:重置编译器的警告行为到默认状态 
1,2,3,4:四个警告级别 
disable:禁止指定的警告信息 
error:将指定的警告信息作为错误报告 

如果大家对上面的解释不是很理解,可以参考一下下面的例子及说明 

#pragma warning( disable : 4507 34; once : 4385; error : 164 ) 
等价于: 
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息 
#pragma warning(once:4385) // 4385号警告信息仅报告一次 
#pragma warning(error:164) // 把164号警告信息作为一个错误。 
同时这个pragma warning 也支持如下格式: 
#pragma warning( push [ ,n ] ) 
#pragma warning( pop ) 
这里n代表一个警告等级(1---4)。 
#pragma warning( push )保存所有警告信息的现有的警告状态。 
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告 
等级设定为n。 
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的 
一切改动取消。例如: 
#pragma warning( push ) 
#pragma warning( disable : 4705 ) 
#pragma warning( disable : 4706 ) 
#pragma warning( disable : 4707 ) 
#pragma warning( pop ) 

在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707) 

在使用标准C++进行编程的时候经常会得到很多的警告信息,而这些警告信息都是不必要的提示, 所以我们可以使用#pragma warning(disable:4786)来禁止该类型的警告 

在vc中使用ADO的时候也会得到不必要的警告信息,这个时候我们可以通过#pragma warning(disable:4146)来消除该类型的警告信息 

七、#pragma comment

该宏放置一个注释到对象文件或者可执行文件。
#pragma comment( comment-type [,"commentstring"] )

comment-type是一个预定义的标识符,指定注释的类型,应该是compiler,exestr,lib,linker之一。
commentstring是一个提供为comment-type提供附加信息的字符串,
Remarks:
1、compiler:放置编译器的版本或者名字到一个对象文件,该选项是被linker忽略的。
2、exestr:在以后的版本将被取消。
3、lib:放置一个库搜索记录到对象文件中,这个类型应该是和commentstring(指定你要Liner搜索的lib的名称和路径)这个库的名字放在Object文件的默认库搜索记录的后面,linker搜索这个这个库就像你在命令行输入这个命令一样。你可以在一个源文件中设置多个库记录,它们在object文件中的顺序和在源文件中的顺序一样。如果默认库和附加库的次序是需要区别的,使用Z编译开关是防止默认库放到object模块。
4、linker:指定一个连接选项,这样就不用在命令行输入或者在开发环境中设置了。
只有下面的linker选项能被传给Linker.
/DEFAULTLIB
/EXPORT
/INCLUDE
/MANIFESTDEPENDENCY
/MERGE
/SECTION
(1)/DEFAULTLIB:library

/DEFAULTLIB 选项将一个 library 添加到 LINK 在解析引用时搜索的库列表。用 /DEFAULTLIB指定的库在命令行上指定的库之后和 .obj 文件中指定的默认库之前被搜索。
忽略所有默认库 (/NODEFAULTLIB) 选项重写 /DEFAULTLIB:library。如果在两者中指定了相同的 library 名称,忽略库(/NODEFAULTLIB:library) 选项将重写 /DEFAULTLIB:library。(2)/EXPORT:entryname[,@ordinal[,NONAME]][,DATA]
使用该选项,可以从程序导出函数,以便其他程序可以调用该函数。也可以导出数据。通常在 DLL 中定义导出。entryname 是调用程序要使用的函数或数据项的名称。ordinal 在导出表中指定范围在 1 至 65,535 的索引;如果没有指定 ordinal,则 LINK 将分配一个。NONAME 关键字只将函数导出为序号,没有 entryname。DATA 关键字指定导出项为数据项。客户程序中的数据项必须用 extern __declspec(dllimport) 来声明。
有三种导出定义的方法,按照建议的使用顺序依次为:源代码中的 __declspec(dllexport)
.def 文件中的 EXPORTS 语句
LINK 命令中的 /EXPORT 规范
所有这三种方法可以用在同一个程序中。LINK 在生成包含导出的程序时还创建导入库,除非生成中使用了 .exp 文件。
LINK 使用标识符的修饰形式。编译器在创建 .obj 文件时修饰标识符。如果 entryname 以其未修饰的形式指定给链接器(与其在源代码中一样),则 LINK 将试图匹配该名称。如果无法找到唯一的匹配名称,则LINK 发出错误信息。当需要将标识符指定给链接器时,请使用 Dumpbin 工具获取该标识符的修饰名形式。(3)/INCLUDE:symbol
/INCLUDE 选项通知链接器将指定的符号添加到符号表。若要指定多个符号,请在符号名称之间键入逗号 (,)、分号 (;) 或空格。在命令行上,对每个符号指定一次 /INCLUDE:symbol。
链接器通过将包含符号定义的对象添加到程序来解析 symbol。该功能对于添包含不会链接到程序的库对象非常有用。用该选项指定符号将通过 /OPT:REF 重写该符号的移除。
我们经常用到的是#pragma comment(lib,"*.lib")这类的。#pragma comment(lib,"Ws2_32.lib")表示链接Ws2_32.lib这个库。和在工程设置里写上链入Ws2_32.lib的效果一样,不过这种方法写的程序别人在使用你的代码的时候就不用再设置工程settings了.
转自:http://blog.csdn.net/hanchengxi/article/details/8455847
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: