1
votes

I need to write a macro that auto-generates a function that forwards all the arguments to another (member) function.

I need in to simplify writing JNI glue in case if you need to know why I need it. I'll omit other reason why I need it done this way, I simply mention that I cannot use boost (although, I may rip off needed pieces and translate from boost to my own macros); I also checked some other libs (jace etc), but didn't find anything that would fit my needs.

In short, here's example of a JNI function:

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;
}

Now, I want to write some JNI_FUNCTION macro that would auto-generate all that Java_com_noname_media_TestClass_nativeTest. After some thought, I think I can do it something like this:

#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;
}

Then, to use the JNI_FUNCTION I could do something like this:

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

The problem is that I don't know how to "crack" the function parameters because I need to add auto-numbered argument names for each entry in the list of functionArgs.

Other gotchas: return type can be some type or void, but for void case I may have separate JNI_VOID_FUNCTION in case it cannot be done easily using regular way. All jni functions in my case will always have at least two args in the list of functionArgs, e.g. it cannot be empty list (). I don't have to use functionArgs as a single argument that contains multiple arguments, I'm ok with this way as well:

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

Whatever works... perhaps I need some kind of macro that allows me to extract some macro at some position, like ARG_1(...) etc, but so far I can't wrap it all that in my brain how to do it.

PS. I recall some super cool examples on usage of c-preprocessor with very good explanation here in SO, but can't find them now, if you have it bookmarked, maybe all I need is to look into them.

EDIT: Basically, the trick is to add auto-numbered names to each argument and then pass them as-is to a member function. The reason I need it done this way is because on top of all that I have some other autogeneration done with preprocessor. In short, this macro will actually be used in a group of similar macro (something like in 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()
2
Are you sure this should be tagged C? Is that JNI function in Java or C++?Jonathan Leffler
Oh, ok, Jonathan, you are the guy who had coolest c-preprocessor samples, I recognized your name right away :) I'll check your replies, perhaps I'll find what I need there. And yes, my post has only c/c++ stuff, no java involved here.Pavel P
The significance of my question is that the code class TestClass { ... } cannot be C; it can only be C++. So, you should probably tag your question as C++. The other tag is fine. You should look up the Boost Preprocessor code before deciding what can and cannot be done.Jonathan Leffler
You cannot generate forwarding functions with the preprocessor.n. 1.8e9-where's-my-share m.
@n.m. I'm pretty sure that you are wrong... it's not going to be super nice looking, but it's possible to do something that works fine.Pavel P

2 Answers

3
votes

Here is solution using 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))

Expands to:

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);
}

So, your example would became:

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

And it expands to:

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();
    }
};
0
votes

So far I have one idea that might be suitable as a solution. Looking through SO answers I found an example on how to count number of arguments. Using this macro I can concatenate count of args from functionArgs and call some predefined macro, eg. JNI_FUNCTION_5 that takes 5 args in the list of args. All I needed is to be able to extract some argument from list of VA_ARGS. Some macro like __VA_ARG_N(num).

Here's a way to extract some argument from __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.

Then I wrote special macro that generates list argumet-type pairs, or arguments only.

So, eventually, I managed to do this:

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

The only problem that would be nice to fix is to add special handling for void returnType, something like this:

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

Where IS_NOT_VOID should have this action:

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

that is

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

Any idea how to do it properly? except the obvious solution to iterate all possible types and create 30 defines for all types that can be passed to JNI functions. Something like this:

#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