【问题标题】:Compilation seems to start before Preprocessor macro expansion编译似乎在预处理器宏扩展之前开始
【发布时间】:2021-02-18 06:19:53
【问题描述】:

我正在尝试创建一个要导出的 C++ DLL,并且我需要导出一个类的所有函数。所以我想出了这个想法来删减样板:

#define CONCATENATE_WRAPPED(arg1, arg2) CONCATENATE_WRAPPED_1(arg1, arg2)
#define CONCATENATE_WRAPPED_1(arg1, arg2) CONCATENATE_WRAPPED_2(arg1, arg2)
#define CONCATENATE_WRAPPED_2(arg1, arg2) CONCATENATE_WRAPPED_3(arg1, arg2)
#define CONCATENATE_WRAPPED_3(arg1, arg2) CONCATENATE_WRAPPED_4(arg1, arg2)
#define CONCATENATE_WRAPPED_4(arg1, arg2) arg1##arg2

// Counts the number of pairs:
#define PAIR_SEQUENCER(_1, _1x, _2, _2x, _3, _3x, _4, _4x, N, ...) N
#define COUNT_PAIRS(...) PAIR_SEQUENCER(__VA_ARGS__, 4, x, 3, x, 2, x, 1, 0, 0, x)

// Internal for declaring arguments:
#define DECLARE_ARGUMENTS_0(...)

#define DECLARE_ARGUMENTS_1(typeName, varName, ...)\
    typeName varName

#define DECLARE_ARGUMENTS_2(typeName, varName, ...)\
    typeName varName,\
    DECLARE_ARGUMENTS_1(__VA_ARGS__)

#define DECLARE_ARGUMENTS_3(typeName, varName, ...)\
    typeName varName,\
    DECLARE_ARGUMENTS_2(__VA_ARGS__)

// Internal for passing arguments:
#define PASS_ARGUMENTS_0(...)

#define PASS_ARGUMENTS_1(typeName, varName, ...)\
    varName\
    PASS_ARGUMENTS_0(__VA_ARGS__)

#define PASS_ARGUMENTS_2(typeName, varName, ...)\
    varName, \
    PASS_ARGUMENTS_1(__VA_ARGS__)

#define PASS_ARGUMENTS_3(typeName, varName, ...)\
    varName, \
    PASS_ARGUMENTS_2(__VA_ARGS__)

// Macro to call when declaring parameters and will adjust depending on the number of params, so that
// DECLARE_ARGUMENTS(int, a, float, b) expands to (int a, float b) for up to three pairs.
#define DECLARE_ARGUMENTS(...) ( CONCATENATE_WRAPPED(DECLARE_ARGUMENTS_, COUNT_PAIRS(__VA_ARGS__))(__VA_ARGS__) ))

// Macro to call when passing parameters and will adjust depending on the number of params, so that
// PASS_ARGUMENTS(int, a, float, b) expands to (a, b) for up to three pairs.
#define PASS_ARGUMENTS(...) ( CONCATENATE_WRAPPED(PASS_ARGUMENTS_, COUNT_PAIRS(__VA_ARGS__))(__VA_ARGS__) )

排除了所有这些上下文,这就是我的主要宏的样子:

#define IMPLEMENT_CONSTRUCTOR(typeName, functionName, ...) \
    extern "C" __declspec(dllexport) typeName* __stdcall functionName DECLARE_ARGUMENTS(__VA_ARGS__) \
    { \
        return new typeName PASS_ARGUMENTS(__VA_ARGS__); \
    } \
    typeName::typeName DECLARE_ARGUMENTS(__VA_ARGS__)

// the intention is so that
IMPLEMENT_CONSTRUCTOR(MacroTestObject, CreateMacroTestObjectWithTwoParams, int, TwoParamFirst, float, TwoParamSecond)
{ }
//expands to
extern "C" __declspec(dllexport) MacroTestObject* __stdcall CreateMacroTestObjectWithTwoParams(int TwoParamFirst, float TwoParamSecond)
{
    return new MacroTestObject(TwoParamFirst, TwoParamSecond);
}

MacroTestObject::MacroTestObject(int TwoParamFirst, float TwoParamSecond)
{ }

但问题似乎是 C++ 编译器甚至在所有宏展开之前就开始编译,这会疯狂地抛出编译时错误,显示如下:

0><Project>\MacroTestObject.cpp(4,1): Error C2512 : 'MacroTestObject::MacroTestObject': no appropriate default constructor available
0><Project>\MacroTestObject.cpp(5,1): Error C2511 : 'MacroTestObject::MacroTestObject(void)': overloaded member function not found in 'MacroTestObject'

但是当我使用 (Rider for Unreal Engine) 的“替换宏调用和所有嵌套调用”功能时,宏展开得很好。

谁能帮我解决这个问题,我不知道为什么会这样,我似乎无法在网上找到任何特别的帮助。

这个 DLL 旨在作为原生插件在 Unity3D 中使用,据我所知,使用 C++/CLI 是不可能的。我也不认为 COM 互操作是一个选项,因为我读到的所有关于它的信息都暗示我需要创建 TLB 文件,而且由于 Unity 处理 C# 编译,这似乎也不可能。

编辑: 我想我有点明白为什么这会导致问题。

// Something like
COUNT_PAIRS(a, b, c, d, e, f)
// always expands to
0
// because __VA_ARGS__ is considered as a single argument
// and a recount is not triggered. I tried using CONCATENATE_WRAPPED
// in different places but unfortunately it has been no help.

有人可以帮我解决这个问题吗?

编辑 2:

解决了问题,切换到 Clang。此外,DECLARE_ARGUMENTS 宏中还有一个额外的括号。

【问题讨论】:

  • 大多数编译器都有在预处理后停止的选项,让您可以准确地看到宏扩展为什么。我建议你尝试一下,看看会发生什么。
  • @Someprogrammerdude 我试过你所说的,显然,一些宏扩展为空。在本例中,它扩展为:extern "C" __declspec(dllexport) MacroTestObject* __stdcall CreateMacroTestObject WithTwoParams( ) { return new MacroTestObject ( ); } MacroTestObject::MacroTestObject ( ) { }。这根本不是它应该的样子。
  • 嘿@wonttellusername,我冒昧地为您的问题创建了一个快速的godbolt示例:godbolt.org/z/o6oeW3提及您使用的编译器可能会很有用,并且可能包括这个godbolt示例所以人们可以更轻松地开始解决您的问题。 (也看看生成的输出,def.似乎在宏扩展中出了点问题)
  • @hassec 我添加了一个编辑。我想我已经解决了问题的主要部分。基本上,__VA_ARGS__ 在展开时被算作一个宏参数,所以DECLARE_ARGUMENTS 总是展开为DECLARE_ARGUMENTS_0。我对非游戏性编程不是很熟悉,因此我能提供的最佳信息是我正在将 MSVC 与 Visual Studio 2019 结合使用。它是 Rider 自动用于 Unreal Engine 的一个。
  • godbolt.org/z/r7n8r9,DECLARE_ARGUMENTS 中最后一个关闭“)”似乎太多了。删除该 Clang 后将产生您想要的输出。但是 MSVC 仍然是错误的,因为正如您所说, COUNT_PAIRS 似乎在 MSVC 上被破坏了。不幸的是,我对那个编译器没有任何经验:/

标签: c++ dll macros


【解决方案1】:

所以我认为this version 现在应该可以按您的意愿工作了。

我发现了两个问题:

  1. DECLARE_ARGUMENT 中已经提到的附加“)”,这很容易解决
  2. 事实上,MSVC 似乎对__VA_ARGS__ 的处理方式与 gcc 和 clang 不同,它不会将参数扩展为自身,而是将其视为一个。这就是为什么例如COUNT_PAIRS 无法正常工作。修复取自SO Question

我建议在 Godbolt 中尝试一下,看看它是如何工作的;) 希望这能解决你的问题

编辑:我也刚刚看到here Visual Studio 显然已经更新了他们的预处理器,因此根据使用的版本,可以通过使用编译器开关/Zc:preprocessor 来避免所有丑陋的EXPAND 调用

【讨论】:

  • 将此标记为解决方案。不久前切换到 CLang,幸好 Rider for Unreal Engine 无需任何修补即可支持它,尽管它只是一个公开测试版。
  • 额外的括号只是 StackOverflow 的错误输入。它在我的代码 LoL 中正确编写。
猜你喜欢
  • 1970-01-01
  • 2018-01-04
  • 1970-01-01
  • 2020-10-18
  • 1970-01-01
  • 1970-01-01
  • 2012-09-20
  • 1970-01-01
相关资源
最近更新 更多