0

I am attempting to create a C++ DLL to export, and I need to export all functions of a class. So I came up with this idea to cut out the boilerplate:

#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__) )

With all this context out of the way, this is what my main macro looks like:

#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)
{ }

But the problem seems to be that the C++ compiler starts compiling even before all the macros have expanded, which throws out compile-time errors like crazy, showing something like:

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'

But when I use (Rider for Unreal Engine)'s "Substitute macro calls and all nested calls" function, the macro expands out just fine.

Can someone help me out with this, I don't know why this is happening and I can't seem to find any particular assistance online.

This DLL is meant to be used in Unity3D as a native plug-in and so as far as I know using C++/CLI is out of the question. I don't think COM interop is an option either because all the info I read about it implies that I need to create TLB files, and since Unity handles C# compilation, that seems out of the question too.

EDIT: I think I kind of figured out why this is causing issues.

// 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.

Could someone help me out with this?

Edit 2:

Solved the problem, switched to Clang. Also, there's an extra bracket in DECLARE_ARGUMENTS macro.

  • 1
    Most compilers have options to stop after preprocessing, letting you see exactly what the macros have expanded to. I suggest you try that to see what really happens. – Some programmer dude Feb 18 '21 at 06:28
  • @Someprogrammerdude I tried out what you said, and apparently, some of the macros expand to nothing. In this instance, it expands to: `extern "C" __declspec(dllexport) MacroTestObject* __stdcall CreateMacroTestObject WithTwoParams( ) { return new MacroTestObject ( ); } MacroTestObject::MacroTestObject ( ) { }`. And that is not at all how it is supposed to be. – wonttellusername Feb 18 '21 at 06:47
  • Hey @wonttellusername, I've taken the liberty to create a quick godbolt example of your problem: https://www.godbolt.org/z/o6oeW3 It would probably be useful to mention which compiler you use, and maybe include this godbolt example so that people can have an easier start playing with your problem. (Also have a lookt at the generated output, there def. seems to be something going wrong in the macro expansion) – hassec Feb 18 '21 at 08:09
  • @hassec I added an edit. I think I have a major part of the problem figured out. Basically, `__VA_ARGS__` when expanded is counted as one macro argument, and so `DECLARE_ARGUMENTS` always expands to `DECLARE_ARGUMENTS_0`. I'm not very familiar with non-gameplay programming, and so the best info I can provide is that I'm using MSVC with Visual Studio 2019. It's the one automatically used by Rider for Unreal Engine. – wonttellusername Feb 18 '21 at 08:14
  • See https://www.godbolt.org/z/r7n8r9, the last closing ")" in DECLARE_ARGUMENTS seems to be too much. After removing that Clang will produce the output you want. But MSVC is still wrong because as you said COUNT_PAIRS seems to be broken on MSVC. Unforuntately I don't really have any experience with that compiler :/ – hassec Feb 18 '21 at 08:31

1 Answers1

0

So I think this version should now work as you would want.

I've found two problems:

  1. The already mentioned additional ")" in DECLARE_ARGUMENT, that was an easy fix
  2. The fact that MSVC seems to trat __VA_ARGS__ differently than gcc and clang, it doesn't expand the arguments into themselves but treats it one. That's why e.g. COUNT_PAIRS didn't work correctly. The fix is taken from this SO Question

I suggest playing around with it a bit in godbolt to see how it works ;) Hope this solves your problem

EDIT: I also just saw here that Visual Studio has apparently updated their preprocessor, so depending on which version one is using it could be possible to avoid all the ugly EXPAND calls by using the compiler switch /Zc:preprocessor

hassec
  • 686
  • 4
  • 18
  • Marking this as the solution. Switched to CLang just a while ago, and thankfully Rider for Unreal Engine supports it without any tinkering, despite being just a public beta. – wonttellusername Feb 18 '21 at 09:42
  • The extra bracket thing was just a StackOverflow mistype though. It's written correctly in my code LoL. – wonttellusername Feb 18 '21 at 09:42