【问题标题】:Auto generate function forwarders with c-preprocessor使用 c 预处理器自动生成函数转发器
【发布时间】:2012-10-26 21:02:38
【问题描述】:

我需要编写一个宏来自动生成一个函数,该函数将所有参数转发给另一个(成员)函数。

如果你想知道我为什么需要它,我需要简化 JNI 胶水的编写。 我将省略需要以这种方式完成的其他原因,我只是提到我不能使用 boost(尽管我可能会撕掉需要的部分并将 boost 转换为我自己的宏);我还检查了其他一些库(jace 等),但没有找到适合我需要的任何东西。

简而言之,这是一个 JNI 函数的示例:

class TestClass
{
    void nativeTest(JNIEnv *, jobject, jint, jboolean)
    {
        ...
    }

    static TestClass* getPeer(JNIEnv *, jobject obj)
    {
        ...
    }
}

JNIEXPORT void JNICALL Java_com_noname_media_TestClass_nativeTest(
    JNIEnv *env, jobject obj, jint i, jboolean b
)
{
    TestClass* peer = TestClass::getPeer(env, obj, i, b);
    if(peer)
        return peer->nativeTest(env, obj, i, b);
    return;
}

现在,我想编写一些 JNI_FUNCTION 宏来自动生成所有 Java_com_noname_media_TestClass_nativeTest。经过一番思考,我想我可以这样做:

#define JNI_FUNCTION(functionName, functionReturn, functionArgs) \
JNIEXPORT functionReturn JNICALL                                 \
  Java_com_noname_media_TestClass##functionName(**WTF**)         \
{
        TestClass* peer = TestClass::getPeer(**WTF**);
        if(peer)
            return peer->functionName(**WTF**);
        return;
}

然后,要使用JNI_FUNCTION,我可以这样做:

JNI_FUNCTION(nativeTest, void, (JNIEnv *, jobject, jint, jboolean));

问题是我不知道如何“破解”函数参数,因为我需要为functionArgs 列表中的每个条目添加自动编号的参数名称。

其他陷阱:返回类型可以是某种类型或 void,但对于 void 情况,我可能有单独的JNI_VOID_FUNCTION,以防使用常规方式无法轻松完成。在我的例子中,所有 jni 函数在functionArgs 的列表中总是至少有两个 args,例如不能为空列表()。 我不必将 functionArgs 用作包含多个参数的单个参数,我也可以这样:

#define JNI_FUNCTION(functionName, functionReturn, ...)

无论什么工作...也许我需要某种宏来允许我在某个位置提取一些宏,例如 ARG_1(...) 等,但到目前为止,我无法将其全部包含在我的大脑中去做吧。

PS。我记得一些关于使用 c 预处理器的超酷示例,在 SO 中有很好的解释,但现在找不到它们,如果你有书签,也许我只需要查看它们。

编辑: 基本上,诀窍是为每个参数添加自动编号的名称,然后将它们按原样传递给成员函数。我需要这样做的原因是因为除此之外,我还使用预处理器完成了其他一些自动生成。简而言之,这个宏实际上会用在一组类似的宏中(类似于 ATL/WTL 中的东西):

JNI_TABLE_BEGIN(ClassName)
  JNI_FUNCTION(native1, void, (JNIEnv *, jobject, jint))
  JNI_FUNCTION(native2, void, (JNIEnv *, jobject, jint))
  JNI_FUNCTION(native3, jint, (JNIEnv *, jobject))
JNI_TABLE_END()

【问题讨论】:

  • 你确定这应该被标记为 C 吗?那个 JNI 函数是用 Java 还是 C++ 编写的?
  • 哦,好吧,乔纳森,你是拥有最酷的 c 预处理器样本的人,我马上就认出了你的名字 :) 我会检查你的回复,也许我会在那里找到我需要的东西.是的,我的帖子只有 c/c++ 的东西,这里没有涉及 java。
  • 我这个问题的意义在于代码class TestClass { ... }不能是C;它只能是 C++。因此,您可能应该将您的问题标记为 C++。另一个标签很好。在决定可以做什么和不可以做什么之前,您应该查看 Boost 预处理器代码。
  • 你不能用预处理器生成转发函数。
  • @n.m.我很确定你错了......它看起来不会超级好看,但可以做一些很好的事情。

标签: c++ c c-preprocessor


【解决方案1】:

这是使用 Boost.Preprocessor Sequences 的解决方案:

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/list/append.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/control/expr_if.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/facilities/expand.hpp>

#define RET_TYPE_void 1)(1
#define IS_NOT_VOID(type) BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE((BOOST_PP_CAT(RET_TYPE_,type))),1)

#define JNI_PARAMS(r, data, i, elem) (elem p##i)
#define JNI_ARGS_PASS(r, data, i, elem) (p##i)

#define JNI_FUNCTION(functionName, functionReturn, PARAMS ) \
JNIEXPORT functionReturn JNICALL \
    Java_com_noname_media_TestClass##functionName(BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(JNI_PARAMS,_,PARAMS)))  \
{ \
    TestClass* peer = TestClass::getPeer(BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(JNI_ARGS_PASS,_,PARAMS))); \
    if(peer) \
        BOOST_PP_EXPR_IF(IS_NOT_VOID(functionReturn),return) peer->functionName(BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(JNI_ARGS_PASS,_,PARAMS))); \
    BOOST_PP_EXPR_IF(IS_NOT_VOID(functionReturn),return functionReturn();) \
} \
/**/

JNI_FUNCTION(nativeTest, void, (jobject)(jint)(jboolean))

扩展到:

JNIEXPORT void JNICALL
Java_com_noname_media_TestClassnativeTest(jobject p0, jint p1, jboolean p2)
{
    TestClass* peer = TestClass::getPeer(p0, p1, p2);
    if(peer)
        peer->nativeTest(p0, p1, p2);
}

因此,您的示例将变为:

#define JNI_TABLE_BEGIN(name) class name { public:
#define JNI_TABLE_END() };

JNI_TABLE_BEGIN(ClassName)
  JNI_FUNCTION(native1, void, (JNIEnv *)(jobject)(jint) )
  JNI_FUNCTION(native2, void, (JNIEnv *)(jobject)(jint) )
  JNI_FUNCTION(native3, jint, (JNIEnv *)(jobject) )
JNI_TABLE_END()

它扩展为:

class ClassName
{
public:
    JNIEXPORT void JNICALL
    Java_com_noname_media_TestClassnative1(JNIEnv * p0, jobject p1, jint p2)
    {
        TestClass* peer = TestClass::getPeer(p0, p1, p2);
        if(peer)
            peer->native1(p0, p1, p2);
    }
    JNIEXPORT void JNICALL
    Java_com_noname_media_TestClassnative2(JNIEnv * p0, jobject p1, jint p2)
    {
        TestClass* peer = TestClass::getPeer(p0, p1, p2);
        if(peer)
            peer->native2(p0, p1, p2);
    }
    JNIEXPORT jint JNICALL
    Java_com_noname_media_TestClassnative3(JNIEnv * p0, jobject p1)
    {
        TestClass* peer = TestClass::getPeer(p0, p1);
        if(peer)
            return peer->native3(p0, p1);
        return jint();
    }
};

【讨论】:

  • 这是非常好的结果。但看起来我在不使用 boost 的情况下变得更好了。检查我自己的回复并附上解释。唯一需要解决的问题是退货;当它实际上必须是 return jint(); 时不好但是处理 void 返回类型有问题。
  • 您使用可变参数宏,这不是 C++98/C++03 的标准,而 Boost.Preprocessor 序列是可移植的。此外,我还为 void 返回类型添加了预处理器条件。
【解决方案2】:

到目前为止,我有一个可能适合作为解决方案的想法。浏览 SO 答案我 found an example on how to count number of arguments。 使用这个宏,我可以连接来自functionArgs 的 args 计数并调用一些预定义的宏,例如。 JNI_FUNCTION_5 在 args 列表中采用 5 个 args。我所需要的只是能够从 VA_ARGS 列表中提取一些参数。像 __VA_ARG_N(num) 这样的宏。

这是一种从__VA_ARGS__ 中提取参数的方法:

#define ARG_REST(arg, ...) __VA_ARGS__
#define ARG0(arg0, ...) arg0
#define ARG1(...) ARG0(ARG_REST(__VA_ARGS__))
#define ARG2(...) ARG1(ARG_REST(__VA_ARGS__))
... etc.

然后我编写了一个特殊的宏来生成列表参数类型对,或者只生成参数。

所以,最终,我设法做到了:

JNI_FUNCTION(void, nativeSetOrientation, (JNIEnv *, jobject, jint, jboolean));
JNI_FUNCTION(void, nativeStartRecording, (JNIEnv *, jobject, jstring, jint));

唯一可以解决的问题是为 void returnType 添加特殊处理,如下所示:

    if(peer)
        IS_NOT_VOID(returnType, return) peer->functionName(**WTF**);
    IS_NOT_VOID(returnType, return returnType();)

IS_NOT_VOID 应该有这个动作的地方:

#define IS_NOT_VOID(type, expr) if(type == void) expr

那是

IS_NOT_VOID(void, return void();) -> expands to nothing
IS_NOT_VOID(int, return int();) -> expands to return int();

知道如何正确地做到这一点吗? 除了迭代所有可能的类型并为所有可以传递给 JNI 函数的类型创建 30 个定义的明显解决方案。像这样的:

#define _IF_NOT_VOID(type, expr) _IF_NOT_VOID##type(expr)
#define _IF_NOT_VOIDvoid(expr) //void type...
#define _IF_NOT_VOIDjboolean(expr) expr
#define _IF_NOT_VOIDjbyte(expr) expr
#define _IF_NOT_VOIDjchar(expr) expr

【讨论】:

  • 这是 MSVC2008 ideone.com/u1SNFw 的解决方案。简而言之 - 想法是将无效案例扩展到(1,1,0),但将其他案例扩展到(某事,0)。之后我们可以使用扩展为它的第二个参数的可变参数宏......等等......我相信其他编译器也可以这样做。顺便说一句,您为该项目使用哪些编译器?一般来说,预处理器技巧是不可移植的——一些编译器有一个错误,一些——其他的,这就是我更喜欢使用 Boost.Preprocessor 的原因——它有很多 ifdef 使得这些东西可以移植。
  • 这里是 gcc-4.3.4 ideone.com/RvjBFH 的版本 - 你可以在底部看到 Ideone 输出。
  • 使用 Boost 的便携版 - ideone.com/IFdllR 。适用于 MSVC 和 GCC
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-19
  • 2011-07-18
  • 2014-11-27
  • 2023-03-08
  • 1970-01-01
  • 2010-11-18
相关资源
最近更新 更多