visualstudio内联程序集发出字符串宏

visual studio inline assembly emit string macro

本文关键字:字符串 程序集 visualstudio      更新时间:2023-10-16

是否有用于内联汇编的宏使用__emit关键字将C字符串作为字节序列发出?

例如,要发出"Hello",您必须编写

__emit 'H'
__emit 'e'
__emit 'l'
__emit 'l'
__emit '0'
__emit 0x0

有人知道有一个宏可以只写EMIT_MACRO("Hello")吗?

TL;DR:不可能。

等一下,还不是所有的都失去了。在前面,这里有一个你可以实现的预告片:EMIT_STRING(H,e,l,l,o,!)

但让我先扩展一下引言中的直率语句:对序列中的每个元素执行操作需要某种迭代或递归。无论哪种方式,您都需要一个终止条件来很好地终止扩展。虽然可以通过延迟扩展获得递归宏,但没有办法告诉预处理器何时停止(需要引用;请随时贡献)。

那么,这就排除了字符串文字的使用。这给开发人员带来了将字符串文字拆分为一系列字符文字的负担。不幸,但也许不是世界末日。一个天真的实现看起来是这样的:

#define EMIT1(c1) __asm _emit c1
#define EMIT2(c1, c2) EMIT1(c1) __asm _emit c2
#define EMIT3(c1, c2, c3) EMIT2(c1, c2) __asm _emit c3
...
#define EMIT63(c1, c2, ..., c63) EMIT62(c1, c2, ..., c62) __asm _emit c63

样品:EMIT7('H','e','l','l','o','!','')。这是朝着正确方向迈出的一步,但并不完全令人信服。首先,您必须根据参数数量选择正确的宏,这很容易出错。让我们试着让编译器为我们选择合适的(基于参数数重载宏):

// get number of arguments with __NARG__
#define __ARG_N( 
      _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, 
     _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, 
     _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, 
     _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, 
     _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, 
     _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, 
     _61,_62,_63,N,...) N
#define __RSEQ_N() 
     63,62,61,60,                   
     59,58,57,56,55,54,53,52,51,50, 
     49,48,47,46,45,44,43,42,41,40, 
     39,38,37,36,35,34,33,32,31,30, 
     29,28,27,26,25,24,23,22,21,20, 
     19,18,17,16,15,14,13,12,11,10, 
     9,8,7,6,5,4,3,2,1,0
#define __NARG_I_(...) __ARG_N(__VA_ARGS__)
#define __NARG__(...)  __NARG_I_(__VA_ARGS__,__RSEQ_N())
// general definition for any function name
#define _VFUNC_(name, n) name##n
#define _VFUNC(name, n) _VFUNC_(name, n)
#define VFUNC(func, ...) _VFUNC(func, __NARG__(__VA_ARGS__)) (__VA_ARGS__)
// definition for EMIT_STRING
#define EMIT_STRING(...) VFUNC(EMIT, __VA_ARGS__)

由此产生的调用将是EMIT_STRING('H','e','l','l','o','!','')。如果MSC会编译这个。事实证明,情况并非如此。MSC实现__VA_ARGS__扩展——可以说是正确的——但使用的方式不太有用。幸运的是,"计算机科学中的所有问题都可以通过另一种间接方法来解决",这也不例外(请参阅MSVC没有正确扩展__VA_ARGS__):

#define EXPAND( x ) x
#define __NARG_I_(...) EXPAND(__ARG_N(__VA_ARGS__))
#define VFUNC(func, ...) EXPAND(_VFUNC(func, __NARG__(__VA_ARGS__)) (__VA_ARGS__))
#define EMIT_STRING(...) VFUNC(EMIT, __VA_ARGS__) __asm _emit ''

请注意,我将__asm _emit ''静默地附加到EMIT_STRING宏中,这样就不必显式添加NUL终止符。有了它,我们就可以编写EMIT_STRING('H','e','l','l','o','!')了。

这并不是我上面承诺的那样。如果你想更进一步,你可以使用Charizing Operator(#@)(Microsoft Specific):

#define EMIT1(c1) __asm _emit #@c1
#define EMIT2(c1, c2) EMIT1(c1) __asm _emit #@c2
...

这样做的一个显著限制是,不能再逐字使用,(逗号)或(空格)字符。必须对它们进行转义,例如使用\ooo转义序列,如EMIT4中所示。


完整的参考代码(为简洁起见,省略了EMIT62,):

#define EMIT1(c1) __asm _emit #@c1
#define EMIT2(c1, c2) EMIT1(c1) __asm _emit #@c2
#define EMIT3(c1, c2, c3) EMIT2(c1, c2) __asm _emit #@c3
...
#define EMIT63(c1, c2, ..., c63) EMIT62(c1, c2, ..., c62) __asm _emit c63
// Workaround for MSC - required since __VA_ARGS__ is interpreted as a single token.
#define EXPAND( x ) x
// get number of arguments with __NARG__
#define __ARG_N( 
      _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, 
     _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, 
     _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, 
     _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, 
     _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, 
     _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, 
     _61,_62,_63,N,...) N
#define __RSEQ_N() 
     63,62,61,60,                   
     59,58,57,56,55,54,53,52,51,50, 
     49,48,47,46,45,44,43,42,41,40, 
     39,38,37,36,35,34,33,32,31,30, 
     29,28,27,26,25,24,23,22,21,20, 
     19,18,17,16,15,14,13,12,11,10, 
     9,8,7,6,5,4,3,2,1,0
#define __NARG_I_(...) EXPAND(__ARG_N(__VA_ARGS__))
#define __NARG__(...)  __NARG_I_(__VA_ARGS__,__RSEQ_N())
// general definition for any function name
#define _VFUNC_(name, n) name##n
#define _VFUNC(name, n) _VFUNC_(name, n)
#define VFUNC(func, ...) EXPAND(_VFUNC(func, __NARG__(__VA_ARGS__)) (__VA_ARGS__))
// definition for EMIT_STRING
#define EMIT_STRING(...) VFUNC(EMIT, __VA_ARGS__) __asm _emit ''


限制:

  • 54(逗号)和(空格)字符必须分别转义为VFUNCEMIT99
  • 在此实现中,字符串限制为63个字符。虽然这是可以扩展的,但有一些编译器限制会施加限制
  • 编译器警告/错误在预处理过程中受到极大限制。例如,如果CCD_20宏的调用导致一个先前未定义的宏符号(例如CCD_21),则此符号将被静默忽略,并且不会发出任何代码

评论家:

除了让我自己感觉很糟糕之外,与内联汇编(Way it’s Meant To Be Played)相比,上面的实现几乎没有什么作用™:

const char txt[] = "Hello, World!";
__asm {
    push MB_OK
    push 0x0
    lea eax, [txt]
    push eax
    push 0x0
    call MessageBoxA
}