2016-2-17

1. C/C++预处理器:点击打开链接

在将 C 和 C++ 文件传递到编译器之前,预处理器将对这些文件执行预先操作。可以使用预处理器有条件地编译代码、插入文件、指定编译时错误消息以及将计算机特定规则应用于代码节。

预处理器指令:

预处理器指令(如 #define 和 #ifdef)通常用于简化源程序在不同的执行环境中的更改和编译。源文件中的指令告知预处理器执行特定操作。例如,预处理器可以替换文本中的标记,将其他文件的内容插入源文件,或通过移除几个部分的文本来取消一部分文件的编译。在扩展宏之前,将识别并执行预处理器行。因此,如果宏扩展到类似于预处理器命令的内容,该预处理器无法识别此命令。

预处理器语句使用的字符集与源文件语句的相同,只不过转义序列不受支持。预处理器语句中使用的字符集与执行字符集相同。预处理器还可识别负字符值。

预处理器可识别下列指令:

数字符号 (#) 必须是包含指令的行上的第一个非空白字符;空白字符可能出现在数字符号与指令的第一个字母之间。某些指令包含参数或值。所有跟在指令后面(指令包含的参数或值除外)的文本的前面必须有单行注释分隔符 (//) 或者必须括在注释分隔符 (/* */) 中。包含预处理器指令的行可以通过紧靠在结束行标记前放置反斜杠 (\) 继续。

预处理器指令可以出现在源文件中的任何位置,但是它们仅应用于源文件的剩余部分。

#define指令(C/C++)

#define 创建一个宏,该宏是标识符或参数化标识符与标记字符串的关联。在定义宏之后,编译器可用标记字符串替换源文件中标识符的每个匹配项。

#define identifier token-string opt

#define identifier ( identifier opt , ... , identifier opt ) token-string opt

#define 指令促使编译器用 token-string 替换源文件中 identifier 的每个匹配项。仅当 identifier 构成标记时才替换它。也就是说,如果 identifier 出现在注释、字符串或较长的标识符中,则不会替换它。有关详细信息,请参阅 C++ 标记

token-string 参数由关键字、常量或完整语句等一系列标记构成。一个或多个空白字符必须将 token-string 与 identifier 隔开。此空白不会被视为替换文本的一部分,也不是跟在文本最后一个标记后面的任何空白。

没有 token-string 的 #define 将从源文件中移除 identifier 的匹配项。 identifier 将保持定义,并可以使用 #if defined 和 #ifdef 指令进行测试。

第二种语法形式定义一个带有参数的类似函数的宏。此形式接受必须出现在括号内的参数的可选列表。定义宏之后,identifier 的每个后续匹配项(identifieropt、...、identifieropt)将替换为 token-string 参数的一个版本(具有替换形参的实参)。

形参名称将出现在 token-string 中以标记实际值的替换位置。每个参数名称可在 token-string 中出现多次,并且名称可以按任意顺序出现。调用中的参数数目必须与宏定义中的参数数目一致。对括号的自由使用将确保正确解释复杂的实参。

将用逗号分隔列表中的形参。列表中的每个名称必须是唯一的,并且列表必须用括号括起。 identifier 与左括号之间不能有空格。对于多个源行上的长指令,请使用行串连,即在换行符前紧接着放置一个反斜杠 (\)。形参名称的范围将扩展到结束 token-string 的新行。

使用第二种语法形式定义宏之后,后面跟有参数列表的后续文本实例指示一个宏调用。跟在源文件中的identifier 实例后面的实参将与宏定义中对应的形参匹配。 token-string 中前面未带 stringizing (#)、charizing (#@) 或 token-pasting (##) 运算符或后面未跟 ## 运算符的每个形参将由对应的实参替换。实参中的所有宏都将在指令替换形参之前扩展。(预处理器运算符中介绍了运算符。)

下面带参数的宏的示例演示了 #define 语法的第二种形式:

// Macro to define cursor lines 
#define CURSOR(top, bottom) (((top) << 8) | (bottom))

// Macro to get a random integer with a specified range 
#define getrandom(min, max) \
    ((rand()%(int)(((max) + 1)-(min)))+ (min))

有副作用的参数有时会导致宏产生意外的结果。给定形参可能会多次出现在 token-string 中。如果将该形参替换为有副作用的表达式,则可能多次计算该表达式及其副作用。(请参阅 Token-Pasting 运算符 (##)下的示例。)

#undef 指令促使忘记标识符的预处理器定义。有关详细信息,请参阅 #undef 指令

如果要定义的宏的名称出现在 token-string 中(即使是作为另一个宏扩展的结果),将不会扩展该名称。

除非第二个标记序列与第一个标记序列相同,否则名称相同的宏的第二个 #define 将生成警告。

Microsoft 专用

如果新定义在语法上与原始定义相同,则 Microsoft C/C++ 允许您重新定义宏。换言之,这两个定义可以具有不同的参数名称。此行为不同于 ANSI C,后者需要两个词法相同的定义。

例如,下面两个宏除参数名称外完全相同。ANSI C 不允许此类重定义,但 Microsoft C/C++ 编译它时不会出错。

#define multiply( f1, f2 ) ( f1 * f2 )
#define multiply( a1, a2 ) ( a1 * a2 )

另一方面,下面两个宏完全不同,将在 Microsoft C/C++ 中生成一条警告。

#define multiply( f1, f2 ) ( f1 * f2 )
#define multiply( a1, a2 ) ( b1 * b2 )

结束 Microsoft 专用

此示例演示了 #define 指令:

#define WIDTH       80
#define LENGTH      ( WIDTH + 10 )

第一个语句将标识符 WIDTH 定义为整数常量 80,并根据 WIDTH 将 LENGTH 定义为整数常量 10。LENGTH 的每个匹配项将由 (WIDTH + 10) 替换。接下来,WIDTH + 10 的每个匹配项将由表达式 (80 + 10) 替换。 WIDTH + 10 两边的括号非常重要,因为它们控制语句中的解释,如下所示:

var = LENGTH * 20;

在预处理阶段之后,语句将变为:

var = ( 80 + 10 ) * 20;

其计算结果为 1800。如果没有括号,则结果将为:

var = 80 + 10 * 20;

其计算结果为 280。

Microsoft 专用

使用 /D 编译器选项定义宏和常量具有与在文件开头使用 #define 处理指令一样的效果。通过使用 /D 选项,最多可定义 30 个宏。

结束 Microsoft 专用

#error 指令 (C/C++)

#error 指令在编译时发出用户指定的错误消息然后终止编译。

#error token-string

此指令发出的错误消息包含 token-string 参数。 token-string 参数不遵循宏展开。此指令在处理向该开发人员通知项目不一致和约束冲突时最有用。以下示例演示预处理过程中的错误进程:

#if !defined(__cplusplus)
#error C++ compiler required.
#endif

#if、#elif、#else 和 #endif 指令 (C/C++)

#if 指令与 #elif#else 和 #endif 指令一起控制源文件部分的编译。如果您编写的表达式(在 #if 后)有一个非零值,则在翻译单元中保留紧跟 #if 指令的行组。

conditional :

if-part elif-parts opt else-part opt endif-line

if-part :

if-line text

if-line :

#if constant-expression

#ifdef identifier

#ifndef identifier

elif-parts :

elif-line text

elif-parts elif-line text

elif-line :

#elif constant-expression

else-part :

else-line text

else-line :

#else

endif-line :

#endif

源文件中的每个 #if 指令必须与表示结束的 #endif 指令匹配。任意数量的 #elif 指令可以出现在 #if 和#endif 指令之间,但最多允许一个 #else 指令。 #else 指令(如果有)必须是 #endif 之前的最后一个指令。

#if#elif#else 和 #endif 指令可以嵌套在其他 #if 指令的文本部分中。每个嵌套的 #else#elif 或#endif 指令属于最靠近的前面的 #if 指令。

所有条件编译指令(例如,#if 和 #ifdef)都必须与文件末尾前面的表示结束的 #endif 指令匹配;否则,将生成错误消息。当条件编译指令包含在包含文件中时,这些指令必须满足相同的条件:包含文件的末尾不能有未匹配的条件编译指令。

在 #elif 命令后面的命令行部分中执行宏替换,以便能够在 constant-expression 中使用宏调用。

预处理器选择 text 的给定匹配项之一以进行进一步处理。 text 中指定的块可以是文本的任意序列。它可占用多个行。通常,text 是对编译器或预处理器有意义的程序文本。

预处理器处理选定的 text,并将其传递给编译器。如果 text 包含预处理器指令,则预处理器将执行这些指令。仅编译预处理器所选的文本块。

预处理器通过计算每个 #if 或 #elif 指令后面的常数表达式来选择单个 text 项,直到找到实际(非零)常量表达式。它选择所有文本(包括以 # 开头的其他预处理器指令),直到其关联的 #elif#else 或#endif

如果 constant-expression 的所有匹配项都为 false,或者 #elif 指令未出现,则预处理器将选择 #else 子句后面的文本块。如果 #else 子句被省略,并且 #if 块中的 constant-expression 的所有实例都为 false,则不选择文本块。

constant-expression 是具有下列其他限制的整数常量表达式:

  • 表达式必须具有整型,并且只能包含整数常量、字符常量和 defined 运算符。

  • 表达式不能使用 sizeof 或 type-cast 运算符。

  • 目标环境无法表示整数的所有范围。

  • 转换表示与类型 int(与类型 long 相同)和 unsigned int(与 unsigned long 相同)。

  • 转换器可以将字符常量转换为与目标环境的集不同的代码值集。若要确定目标环境的属性,请从为目标环境生成的应用程序中的 LIMITS.H 中检查宏的值。

  • 该表达式不得执行任何环境查询,并且必须不受与目标计算机上的实现详细信息的影响。

预处理器运算符 defined 可用于特定常量表达式中,如以下语法所示:

defined( identifier )

defined identifier

如果当前定义了 identifier,则该常量表达式被视为 true(非零);否则条件为 false (0)。定义为空文本的标识符被视为已定义。 defined 指令可用于 #if 和 #elif 指令,但在其他位置不可用。

在下面的示例中,#if 和 #endif 指令控制三个函数调用之一的编译:

#if defined(CREDIT)
    credit();
#elif defined(DEBIT)
    debit();
#else
    printerror();
#endif

如果定义了标识符 CREDIT,则编译对 credit 的函数调用。如果定义了标识符 DEBIT,则编译对 debit 的函数调用。如果两个标识符都未定义,则编译对 printerror 的调用。请注意,CREDIT 和 credit 是 C 和 C++ 中的不同标识符,因为它们的情况不同。

以下示例中的条件编译语句假定一个名为 DLEVEL 的之前定义的符号常量。

#if DLEVEL > 5
    #define SIGNAL  1
    #if STACKUSE == 1
        #define STACK   200
    #else
        #define STACK   100
    #endif
#else
    #define SIGNAL  0
    #if STACKUSE == 1
        #define STACK   100
    #else
        #define STACK   50
    #endif
#endif
#if DLEVEL == 0
    #define STACK 0
#elif DLEVEL == 1
    #define STACK 100
#elif DLEVEL > 5
    display( debugptr );
#else
    #define STACK 200
#endif

第一个 #if 块显示两组嵌套的 #if#else 和 #endif 指令。仅当 DLEVEL > 5 为 true 时,才会处理第一组指令。否则,处理 #else 后面的语句。

第二个示例中的 #elif 和 #else 指令用于根据 DLEVEL 的值做出四种选择之一。将常量 STACK 设置为 0、100 或 200,具体取决于 DLEVEL 的定义。如果 DLEVEL 大于 5,则编译

#elif DLEVEL > 5
display(debugptr);

该语句,并且不会定义 STACK

条件编译的常见用途是防止多次包含同一个头文件。在 C++ 中,通常在头文件中定义类,如下构造可用于防止多个定义:

/*  EXAMPLE.H - Example header file  */
#if !defined( EXAMPLE_H )
#define EXAMPLE_H

class Example
{
...
};

#endif // !defined( EXAMPLE_H )

前面的代码将检查以查看是否定义了符号常量 EXAMPLE_H如果是这样,则已包括该文件,并且不需要重新处理该文件。否则,定义常量 EXAMPLE_H 以将 EXAMPLE.H 标记为已处理.

#ifdef 和 #ifndef 指令 (C/C++)

在将 #ifdef 和 #ifndef 指令与 defined(identifier) 一起使用时,它将执行与 #if 指令相同的任务。

#ifdef identifier
#ifndef identifier

// equivalent to
#if defined identifier
#if !defined identifier

只要能够使用 #if,就可以使用 #ifdef 和 #ifndef 指令。在定义 identifier 时,#ifdef identifier 语句与#if 1 等效;当 identifier 未定义或没有使用 #undef 指令进行定义时,该语句与 #if 0 等效。这些指令只检查使用 #define 定义的标识符是否存在,而不检查在 C 或 C++ 源代码中声明的标识符。

提供这些指令只是为了实现与该语言的早期版本的兼容性。优先选择将 defined( identifier ) 常量表达式与 #if 指令一起使用。

#ifndef 指令检查 #ifdef 所检查的条件的相反值。如果尚未定义标识符(或已使用 #undef 移除其定义),则条件为 true(非零)。否则,条件为 false (0)。

Microsoft 专用

可使用 /D 选项从命令行中传递 identifier可使用 /D 指定最多 30 个宏。

这对于检查定义是否存在很有用,因为可以从命令行中传递定义。例如:

// ifdef_ifndef.CPP
// compile with: /Dtest /c
#ifndef test
#define final
#endif

结束 Microsoft 专用

#import 指令 (C++)

C++ 专用

过去一直合并类型库中的信息。类型库的内容将转换为 C++ 类,主要描述 COM 接口。

#import "filename" [attributes]
#import <filename> [attributes]

filename

指定要导入的类型库。 filename 可以是以下项之一:

  • 包含类型库的文件的名称,如 .olb、.tlb 或 .dll 文件。每个文件名之前可以放置关键字file:

  • 类型库中控件的 progid。每个 progid 之前可以放置关键字 progid:例如:

    #import "progid:my.prog.id.1.5"
    

    有关 progid 的详细信息,请参阅指定本地化 ID 和版本号

    请注意,在 64 位操作系统上使用交叉编译器编译时,该编译器只能读取 32 位注册表配置单元。您可能需要使用本机 64 位编译器才能生成和注册 64 位类型库。

  • 类型库的库 ID。每个库 ID 前可以放置关键字 libid:例如:

    #import "libid:12341234-1234-1234-1234-123412341234" version("4.0") lcid("9")
    

    如果未指定版本或 lcid,则适用于 progid: 的规则也适用于 libid:

  • 可执行 (.exe) 文件。

  • 包含类型库资源(例如 .ocx)的库 (.dll) 文件。

  • 包含类型库的复合文档。

  • 可由 LoadTypeLib API 理解的任何其他文件格式。

attributes

一个或多个 #import 特性用空格或逗号分隔每个特性。例如:

#import "..\drawctl\drawctl.tlb" no_namespace, raw_interfaces_only

- 或 -

#import "..\drawctl\drawctl.tlb" no_namespace raw_interfaces_only

filename 后可跟目录规范(可选)。文件名必须命名现有文件。两种语法形式之间的差异在于预处理器在未完整指定路径时搜索类型库文件的顺序。

语法形式

操作

带引号的形式

指示预处理器首先在包含 #import 语句的文件目录中查找类型库,然后在包含 (#include) 该文件的任何文件目录中查找类型库文件。然后预处理器沿如下所示的路径执行搜索。

尖括号形式

指示预处理器沿下列路径搜索类型库文件:

  1. PATH 环境变量路径列表

  2. LIB 环境变量路径列表

  3. /I(附加包含目录)编译器选项指定的路径,但该编译器搜索另一具有no_registry 特性的类型库引用的类型库。

指定 progid 时,还可以指定 progid 的本地化 ID 和版本号。例如:

#import "progid:my.prog.id" lcid("0") version("4.0)

如果未指定本地化 ID,则根据下列规则选择 progid:

  • 如果只有一个本地化 ID,则使用这一个。

  • 如果有多个本地化 ID,则使用版本号为 0、9 或 409 的第一个本地化 ID。

  • 如果有多个本地化 ID,而它们都不为 0、9 或 409,则使用最后一个本地化 ID。

  • 如果未指定版本号,则使用最新版本。

#import 将创建两个头文件以重新构造 C++ 源代码内的类型库内容。主头文件类似于 Microsoft 接口定义语言 (MIDL) 编译器生成的文件,但具有附加的编译器生成的代码和数据。 主头文件具有与类型库相同的基名称以及 .TLH 扩展。次头文件具有与类型库相同的基名称以及 .TLI 扩展。它包含编译器生成的成员函数的实现,并且包含 (#include) 在主头文件中。

如果导入使用 byref 参数的调度接口属性,则 #import 将不会为函数生成 __declspec(属性)语句。

两个头文件都放置在 /Fo(名称对象文件)选项指定的输出目录中。然后它们由编译器进行读取和编译,就像 #include 指令命名主头文件一样。

下列编译器优化伴随 #import 指令出现:

  • 头文件在创建后将给定与类型库相同的时间戳。

  • 处理 #import 时,编译器首先会检查标头是否存在并且是否最新。如果是,则不需要重新创建。

#import 指令也参与最小重新生成并可放置在预编译头文件中。有关详细信息,请参阅创建预编译头文件

主类型库头文件包含 7 个部分:

  • 标题样本:包含注释、COMDEF.H(定义标头中使用的一些标准宏)的 #include 语句和其他杂项安装信息。

  • 转发引用和 typedef:包含结构声明(例如 struct IMyInterface 和 typedef)。

  • 智能指针声明:模板类 _com_ptr_t 是智能指针实现,该实现封装了接口指针并免除了调用AddRefReleaseQueryInterface 函数的需要。此外,它在创建新的 COM 对象时会隐藏 CoCreateInstance 调用。本节使用宏语句 _COM_SMARTPTR_TYPEDEF 将 COM 接口的 typedef 建立为 _com_ptr_t 模板类的模板专用化。例如,对于接口 IMyInterface,.TLH 文件将包含:

    _COM_SMARTPTR_TYPEDEF(IMyInterface, __uuidof(IMyInterface));
    

    编译器可将其扩展为:

    typedef _com_ptr_t<_com_IIID<IMyInterface, __uuidof(IMyInterface)> > IMyInterfacePtr;
    

    然后类型 IMyInterfacePtr 可以代替原始的接口指针 IMyInterface* 使用。因此,无需调用各种 IUnknown 成员函数

  • Typeinfo 声明:主要包含类定义和其他公开 ITypeLib:GetTypeInfo 返回的各个 typeinfo 项的其他项。在本节中,类型库中每个 typeinfo 都会反映在标头中,具体形式取决于TYPEKIND 信息。

  • 可选旧式 GUID 定义:包含名为 GUID 常量的初始化。这些是窗体 CLSID_CoClass 和IID_Interface 的名称,与 MIDL 编译器生成的类似。

  • 用于次要类型库标头的 #include 语句。

  • 页脚样本:当前包含 #pragma pack(pop)

除标题样本和页脚样本部分之外的所有部分都封装在其名称由原始 IDL 文件中的 library 语句定义的命名空间中。您可使用类型库标头的名称,方法是通过显式限定命名空间名称或通过包括以下语句:

using namespace MyLib;

在源代码中紧跟在 #import 语句之后。

可使用 #import 指令的 特性禁止显示命名空间。787d1112-e543-40d7-ab15-a63d43f4030a#_predir_no_namespace但是,禁止显示命名空间可能会导致名称冲突。还可通过 特性重命名命名空间。787d1112-e543-40d7-ab15-a63d43f4030a#_predir_rename_namespace

编译器提供了至其当前处理的类型库所需的任何类型库依赖项的完整路径。路径是以注释形式写入编译器为每个已处理的类型库生成的类型库标头 (.TLH) 中的。

如果类型库包括对其他类型库中所定义类型的引用,则 .TLH 文件将包括下列顺序的注释:

//
// Cross-referenced type libraries:
//
//  #import "c:\path\typelib0.tlb"
//

#import 注释中的实际文件名为交叉引用类型库存储在注册表中的完整路径。如果由于缺少类型定义遇到错误,请检查位于 .TLH 头处的注释来了解需要先导入的依赖类型库。错误可能是语法错误(例如,C2143、C2146、C2321)、C2501(缺少声明说明符)或者编译 .TLI 时的 C2433(数据声明上不允许“inline”)。

您必须确定系统标头中不允许以其他形式提供的依赖项注释,再在某一时刻提供 #import 指令,然后依赖类型库的 #import 指令才能解析错误。

有关详细信息,请参阅知识库文章“#import 包装器方法可能导致访问冲突”(Q242527) 或“将 #import 用于 XML 时的编译器错误”(Q269194)。知识库文章可在 MSDN 库媒体或http://support.microsoft.com/support/ 上找到。

#import 可以选择性地包含一个或多个特性。这些特性通知编译器修改类型库标头的内容。反斜杠 (\) 符号可以用于在单个 #import 语句中包含附加行。例如:

#import "test.lib" no_namespace \
   rename("OldName", "NewName")

有关详细信息,请参阅 #import 特性 (C++)

结束 C++ 专用

#undef 指令 (C/C++)

移除(取消定义)之前使用 #define 创建的名称。

#undef 
identifier

#undef 指令可移除 identifier 的当前定义。因此,identifier 的后续匹配项被预处理器忽略。若要使用#undef 移除宏定义,请仅给定宏 identifier;不要给定参数列表。

您还可以将 #undef 指令应用于之前没有定义的标识符。这将确保该标识符是不确定的。宏替换不在#undef 语句中执行。

#undef 指令通常与 #define 指令成对,用来在源程序中创建一个区域,使其中的标识符具有特殊含义。例如,源程序的特定函数可使用清单常量定义不会影响程序的其余部分的环境特定值。 #undef 指令还可与 #if 指令一起使用来控制源程序的条件编译。有关详细信息,请参阅 #if、#elif、#else 和 #endif 指令

在以下示例中,#undef 指令将移除符号常量和宏的定义。请注意,只给定宏的标识符。

#define WIDTH 80
#define ADD( X, Y ) ((X) + (Y))
.
.
.
#undef WIDTH
#undef ADD

Microsoft 专用

可以使用 /U 选项后跟要取消定义的宏名称从命令行取消定义宏。发出此命令的效果等效于文件开头的一系列 #undef macro-name 语句。

结束 Microsoft 专用

#line 指令 (C/C++)

#line 指令告诉预处理器将编译器内部存储的行号和文件名更改为给定行号和文件名。

#line 
digit-sequence ["filename"]

该编译器使用行号和选项文件名来引用它在编译时发现的错误。行号通常指当前输入行,文件名指当前输入文件。处理了每行后递增行号。

digit-sequence 值可以是任何整数常数。宏替换在预处理标记可执行,但结果必须计算为正确的语法。filename 可以为字符的任意组合,且必须括在双引号 (" ") 内。如果省略 filename ,前面的文件名保持不变。

您还可以通过编写 #line 指令更改源行号和文件名。转换器使用行号和文件名确定预定义宏 __FILE__ 和__LINE__的值。可以使用这些宏将自述性的错误消息插入程序文本。有关这些预定义的宏的更多信息,请参见预定义的宏

__FILE__ 宏扩展成内容是文件名的字符串,并放在双引号内 (" ")。

如果更改行号和文件名,该编译器忽略以前的值并用新值继续处理。程序生成器通常使用 #line 指令导致错误消息以引用原始源文件而不是已生成的程序。

以下示例阐释 #line 和 __LINE__ 以及 __FILE__ 宏。

在此语句中,在内部存储的行号将设置为 151,文件名更改为 copy.c

#line 151 "copy.c"

在此示例中,如果给定的“断言”未得到满足,宏 ASSERT 使用预定义的宏 __LINE__ 和 __FILE__ 打印有关源文件中的错误消息。

#define ASSERT(cond)

if( !(cond) )\
{printf( "assertion error line %d, file(%s)\n", \
__LINE__, __FILE__ );}

#using 指令 (C++)

 

用 /clr将元数据导入程序编译。

#using file [as_friend]

file

MSIL .dll、.exe、.netmodule 或 .obj。例如,

#using <MyComponent.dll>

as_friend

指定所有类型 file 都是可访问的。有关详细信息,请参阅友元程序集 (C++)

file 可以是您为其托管数据导入和管理构造的 microsoft 中间语言 (msil) 文件。如果 .dll 文件包含一个程序集清单,则导入清单中引用的所有 .dll,并将生成的程序集和在元数据 file 为程序集引用。

如果 file 不包含程序集 (如果 file 是模块),并且,如果您在当前 (程序集) 应用程序不想使用从模块的类型信息,则具有清单的选项模块作为部分程序集;使用 /ASSEMBLYMODULE模块中的类型和被引用程序集的对所有应用程序都可用。

使用#using 的替代项为 /FU 编译器选项。

将生成的 .exe 程序传递给 #using 应当被 /clr:safe 或 /clr:pure编译,或者与其他任意的 Visual Studio 编译器 (如:Visual Basic 或 Visual C#)。试图从 .exe 程序集导入元数据编译 /clr 将会导致文件加载异常。

System_CAPS_note注意

使用引用 #using 的元素可以运行带有文件的不同版本导入在编译时,导致客户端应用程序产生意外的结果。

为了让编译器识别出程序集中的类型(而不是模块),需要强制解析类型,例如可以通过定义此类型的实例来完成。也有其他方法来为编译器解析程序集中的类型名称,例如,如果从一个程序集的类型继承,编译器就会知道此类型名称。

当导入生成的元数据使用 __declspec(thread)中的源代码,线程语义在元数据不会保留。例如,通过#using声明 __declspec(thread),生成将作为 .NET Framework 公共语言运行时生成的程序,然后导入的变量,将不再具有在变量中 __declspec(thread) 语义。

所有导入的类型 (管理和本机) 在 #using 引用的文件可用,但是,编译器将本机类型作为不是声明定义。

在使用编译 /clr时,mscorlib.dll 自动引用。

LIBPATH 环境变量指定要搜索的目录,则编译器尝试解析文件名并传递给 #using

编译器将沿下列路径搜索引用:

  • 在 #using 语句中指定的路径。

  • 当前目录。

  • .NET Framework 系统目录。

  • 从目录添加与 /AI 编译器选项。

  • 在 LIBPATH 环境变量的目录。

如果您生成程序集 (c) 和引用自身引用其他程序集的程序集 (b),您不需要显式引用程序集 A,除非您显式使用一个来输入 C。

// using_assembly_A.cpp
// compile with: /clr /LD
public ref class A {};
// using_assembly_B.cpp
// compile with: /clr /LD
#using "using_assembly_A.dll"
public ref class B {
public:
   void Test(A a) {}
   void Test() {}
};

在下面的示例中,因为程序在 using_assembly_A.cpp,不使用定义的任何一个类型不引用的 using_assembly_A.dll 编译器错误。

// using_assembly_C.cpp
// compile with: /clr
#using "using_assembly_B.dll"
int main() {
   B b;
   b.Test();
}

#include 指令 (C/C++)

 

告知预处理器将已指定文件的内容视为它们在源程序中指令出现处出现的方式处理。

      #include  "path-spec"
#include  <path-spec>

可以将常数和宏定义编入包含文件中,然后使用 #include 指令将它们添加到任何源文件中。包含文件还可用于合并外部变量和复杂数据类型的声明。在为此目的而创建的包含文件中,类型只能定义和命名一次。

path-spec 是一个文件名,可以选择性地在其前放置一个目录说明。文件名必须命名现有文件。 path-spec 的语法取决于编译程序时基于的操作系统。

有关如何在使用 /clr 编译的 C++ 应用程序中引用程序集的信息,请参阅 #using

两种语法形式都会导致指令被替换为指定包含文件的整个内容。两种形式之间的区别在于,在未完全指定路径时预处理器搜索标头文件的顺序。下表显示了这两种语法形式之间的差异。

语法形式

操作

带引号的形式

预处理器按以下顺序搜索包含文件:

  1. 在包含 #include 语句的文件所在的同一目录中。

  2. 在当前打开的包含文件的目录中,采用与打开它们的顺序相反的顺序。搜索从父包含文件的目录中开始进行,然后继续向上到任何祖父包含文件的目录。

  3. 跟随每个 /I 编译器选项指定的路径。

  4. 跟随 INCLUDE 环境变量指定的路径。

尖括号形式

预处理器按以下顺序搜索包含文件:

  1. 跟随每个 /I 编译器选项指定的路径。

  2. 通过命令行进行编译时,跟随 INCLUDE 环境变量指定的路径。

只要找到具有给定名称的文件,预处理器就会停止搜索。如果在两个双引号 (" ") 之间括住包含文件的完整明确的路径说明,则预处理器只搜索该路径说明,并忽略标准目录。

如果用双引号括起来的文件名是不完整的路径规格,则预处理器将首先搜索“父”文件的目录。父文件是包含#include 指令的文件。例如,如果将名为 file2 的文件包括在名为 file1 的文件中,则 file1 为父文件。

包含文件可以“嵌套”;即 #include 指令可以出现在由另一个 #include 指令命名的文件中。例如,file2可以包含 file3在这种情况下,file1 依然为 file2 的父级,但它可能为 file3 的“祖父级”。

当嵌套了包含文件并从命令行开始编译时,目录搜索会从父文件的目录开始,然后在所有祖父文件的目录中继续进行。即,搜索将相对于包含当前正在处理的源的目录开始。如果找不到该文件,则搜索会移动到由 /I 编译器选项指定的目录。最后,将搜索 INCLUDE 环境变量指定的目录。

在开发环境中,将忽略 INCLUDE 环境变量。有关如何设置要为包含文件搜索的目录的信息(同样应用于 LIB 环境变量),请参阅 。E027448B-C811-4C3D-8531-4325AD3F6E02

此示例使用尖括号显示文件包含:

#include <stdio.h>

此示例将名为 STDIO.H 文件的内容添加到源程序。尖括号会促使预处理器在搜索 /I 编译器选项指定的目录之后,搜索 STDIO.H 的 INCLUDE 环境变量指定的目录。

下一个示例用引号形式显示文件包含:

#include "defs.h"

此示例将 DEFS.H 指定的文件的内容添加到源程序。双引号意味着,预处理器将首先搜索包含父源文件的目录。

包含文件的嵌套可扩展至 10 个级别。处理嵌套的 #include 时,预处理器将继续在源文件中插入封闭的包含文件。

Microsoft 专用

为了查找可包含的源文件,预处理器会先搜索 /I 编译器选项指定的目录。如果 /I 选项不存在或失败,预处理器会使用 INCLUDE 环境变量在尖括号中查找包含文件。INCLUDE 环境变量和 /I 编译器选项可以包含使用分号 (;) 分隔的多个路径。如果多个目录显示为 /I 选项的一部分或在 INCLUDE 环境变量中,预处理器会按它们的出现顺序搜索它们。

例如,命令

CL /ID:\MSVC\INCLUDE MYPROG.C

会促使预处理器搜索包含文件(如 STDIO.H)的目录 D:\MSVC\INCLUDE\。命令

SET INCLUDE=D:\MSVC\INCLUDE
CL MYPROG.C

具有同样的作用。如果两组搜索都失败,则会生成严重的编译器错误。

如果为路径包含冒号的包含文件指定完整的文件名(例如,F:\MSVC\SPECIAL\INCL\TEST.H),则预处理器会遵循该路径。

对于指定为 #include "path-spec" 的包含文件,目录搜索从父文件的目录开始,然后在任何祖父文件的目录中继续进行。也就是说,搜索将相对于包含当前正在处理的 #include 指令的源文件内容的目录开始。如果没有祖父文件且没有找到该文件,则搜索会像文件名括在尖括号中一样继续进行。

结束 Microsoft 专用






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值