X++ 中的宏

注释

社区兴趣团体现已从 Yammer 迁移到Microsoft Viva Engage。 若要加入 Viva Engage 社区并参与最新讨论,请填写 “请求访问财务和运营 Viva Engage 社区 ”表单,然后选择要加入的社区。

本文介绍如何在 X++ 中创建和使用宏。

预编译程序指令(即宏)在编译代码之前在概念上进行处理。 指令声明并处理宏及其值。 指令将替换为他们指定的内容,因此编译器永远不会遇到它们。 X++ 编译器只看到指令写入 X++ 代码中的字符序列。

警告

宏是旧版功能,可能在将来的版本中弃用。 改用语言构造:而不是宏,请使用如下语言构造:

定义宏

使用如下所示的语法定义命名宏

  • #define。MyMacro(Value) 创建具有可选值的宏。
  • #if。MyMacro 检查是否定义了宏。
  • #undef。MyMacro 删除宏定义。

#define 和 #if 指令

所有预编译程序指令和符号都以 # 字符开头。

使用以下语法定义宏:

#define.MyMacro(Value) // creates a macro with a value.
#define.AnotherMacro() // creates a macro without a value.

可以在代码中的任何位置定义宏。 宏可以具有一个字符序列的值,但不需要具有值。 该 #define 指令指示预编译程序创建宏变量,可以选择包括值。

#if 指令检查变量是否已定义,并且(可选)它是否具有特定值,如以下示例所示:

#if.MyMacro
  // The MyNaacro is defined.
#endif

#ifnot.MyMacro
  // The MyNaacro is not defined.
#endif

X++ 预编译程序指令、它们定义的宏名称以及 #if 指令值测试都是不区分大小写的。 但是,定义以大写字母开头的宏名称。

#undef 指令

使用该 #undef 指令删除前面 #define存在的宏定义。

#undef.MyMacro
#if.MyMacro
   // The macro is not defined, so this is not included
#endif

可以使用另#undef一个宏重新定义删除的宏名称#define

使用宏值

可以定义一个宏名称来具有一个值。

#define.Offset(42)
...
print #Offset; // 42

宏值没有特定的数据类型 - 它只是一系列字符。 通过在指令末尾 #define.MyMacro 提供括在括号中的值来为宏赋值。 无论希望在 X++ 代码中出现值的位置,都使用宏符号。 宏符号是宏的名称,其 # 字符添加为前缀。 下面的代码示例演示宏符号 #MyMacro。 该符号由宏的值替换。

测试宏值

可以测试宏以确定其是否具有值。 还可以确定其值是否等于特定字符序列。 通过这些测试,你可以在 X++ 程序中有条件地包括代码行。 无法测试定义的宏是否具有值。 只能测试宏值是否与特定值匹配。 最佳做法是始终为定义的任何宏名称定义一个值,或者永远不会定义值。 在这些模式之间交替时,代码变得难以理解。

#defInc 和 #defDec 指令

#defInc 也是 #defDec 解释宏值的唯一指令。 它们仅适用于具有预编译程序可以转换为正式 int 类型的值的宏。 这些指令在编译时更改宏的数值。 该值只能包含数字。 唯一允许的非数字字符是前导负号(-)。 整数值被视为 X++ int,而不是 int64。 对于指令使用的宏名称 #defInc#define 创建宏的指令不应驻留在类声明中。 在这些情况下的行为是不可预知的 #defInc 。 而是仅在方法中定义此类宏。 #defInc仅对具有整数值的宏使用 and #defDec 指令。 预编译程序遵循宏值不是整数或值异常或极端时的特殊规则 #defInc 。 下表列出了转换为零(0),然后递增的值 #defInc 。 将值转换为 0 时 #defInc ,即使使用 #defDec,也不能恢复原始值。

宏值 defInc 值 行为
(+55) 56 正号 (+) 前缀使预编译程序将此值视为非数值字符串。 预编译程序处理 (或#defInc) 指令时#defDec将所有非数值字符串视为 0。
("3") 1 括在引号中的整数被视为 0。
( ) 1 空格字符串被视为 0。
() 1 零长度字符串被视为 0。
(随机字符串。) 1 任何非数字字符字符串都被视为 0。
(0x12) 1 十六进制数字被视为非数值字符串。 因此,预编译程序将其转换为 0。
(-44) -43 负数是可接受的。
(2147483647) -2147483648 最大正 int 值溢出到最小负 int 值。#defInc
(999888777666555) 1 任何较大的数字,超出 int和 int64 的容量。
(5.8) 1 实数解释为 0。
1 如果未为指令 #define.MyValuelessMacro 提供任何值且没有括号,则值为 0。

#globaldefine 指令

#globaldefine 指令类似于该 #define 指令。 使用 #define 而不是 #globaldefine

#localmacro 和 #macro 指令

#localmacro如果希望宏具有长几行的值,或者当宏值包含右括号时,指令是一个不错的选择,因此最好包含源代码片段。

    #macro.RetailMatchedAggregatedSalesLine(
                %1.price            == %2.price
        &&      %1.businessDate     == %2.businessDate
        &&      %1.itemId           == %2.itemId
        &&      ((((%3) && (%1.qty <= 0)) || ((! %3) && (%1.qty > 0))) || (%4))
    )
    #endmacro

#localmacro 指令可以编写为 #macro. 但是, #localmacro 建议使用术语。 通过使用 #if 指令,可以测试是否使用 #define 指令声明宏名称。 但是,无法测试宏名称是否使用 #localmacro 指令声明。 只有使用 #define 指令声明的宏才会受到该 #undef 指令的影响。 #define在指令中,可以指定已在范围内的名称作为一个 #localmacro。 效果是放弃 #localmacro 并创建宏 #define 。 这也适用于相反的序列,这意味着可以 #localmacro 重新定义 a #define. #localmacro (具有宏名称和值)始终重写具有相同名称的上#localmacro一个名称。 此问题与 #globaldefine.. 宏和#define宏之间的#localmacro主要区别在于其语法的终止方式。 终止符如下所示:

  • #define – 由 – 终止 )
  • #localmacro – 由 – 终止 #endmacro

#localmacro 是具有多个行值的宏的更好选择。 多行值通常是 X++ 或 SQL 代码行。 X++ 和 SQL 包含大量括号,这些括号会过早终止 #define。 #define可以在#localmacro单行或后续行上声明和终止。 实际上, #define 在声明它的同一行上终止该作。 实际上,该 #localmacro 作在后续行中终止。

宏参数

可以定义宏值以包含参数符号。 第一个参数符号是,第二个 %1%2等。 引用扩展的宏符号名称时,传递参数的值。 宏参数值是无形式类型的字符序列,它们以逗号分隔。 无法将逗号作为参数值的一部分传入。 传递的参数数可以小于、大于或等于宏值旨在接收的参数数。 系统容忍传递的参数数不匹配。 如果传递的参数数少于宏预期,则每个省略的参数被视为字符长度为零的序列。

嵌套宏符号

可以在外部定义指令内嵌套预编译程序定义指令。 主要定义指令是 #define#localmacro

#define指令可以在指令内#localmacro给出,也可以#localmacro位于指令#define内。

#macrolib 指令

在代码节点下的“宏”节点下的应用程序资源管理器中,有许多库节点包含一组宏指令。 这两个宏 #define 库的内容通常 #localmacro 都出现在这些宏库中。 可以使用 #macrolib。MyAOTMacroLibrary ,用于在 X++ 代码中包含宏库的内容。 和#if#undef指令不适用于#macrolib名称。 但是,它们确实适用于 #define 作为宏内容的 #macrolib 指令。 指令 #macrolib。MyAOTMacroLibrary 也可以编写为 #MyAOTMacroLibrary。 建议使用 #macrolib 前缀,因为以后读取代码的人员从不明确。

#linenumber 指令

可以在开发和调试代码期间使用该 #linenumber 指令。 在进行任何宏扩展之前,它将被代码文件中的物理行号替换。

宏范围

可以引用宏的范围取决于定义宏的位置。 在类中,可以引用在父类中定义的宏。 当预编译程序处理子类时,它首先将继承链跟踪到根类。 然后,预编译程序将根类中的所有指令处理到要编译的类。 它将所有宏及其值存储在其内部表中。 每个类声明中指令的结果都应用于已从继承链前面找到的指令填充的内部表。

但是,预编译程序单独处理每个方法。 它更新其内部表,以便它可以在处理当前方法之前还原表的状态。 预编译程序处理第一个方法后,它会在处理下一个方法之前还原内部表。

在此上下文中,方法定义为应用程序对象树(AOT)中方法节点的内容。 在 AOT 中,可以展开“类”节点、展开类节点、右键单击方法节点,然后选择“ 编辑”。 然后,可以在方法声明之前添加 #define.MyMacro("abc") 一行。 预编译程序将此 #define 指令视为方法的一部分,即使 #define 该方法的块外 {} 发生也是如此。