简体   繁体   中英

C99: Dynamic dispatch in a static library

Say I am developing a Math library and I want it to be such that it will detect whether or not user's machine support SSE (and which version) and based on that, separate internal functions will be called for the same API function. I can think of three ways to implement that:

  1. Have global function pointers in the library and let user call mathInit() in their source. When they do, figure out the hardware details and assign the function pointers to different functions.

  2. Same, except instead of having global function pointers, put them in a struct which is returned by mathInit(). This way, user will have to call math.vec3Add(...) or similar.

  3. Same as 1, but instead of having global pointers, make mathInit() a macro so that the function pointers will have local scope in user's main() function (and require mathInit() to be called from main()). It will be in a header, of course.

Is any of these methods preferable? Is there some other, better way?

This is largely opinion-based, IMHO.

And my opinion is, a math library should expose the least possible amount of details about its internal workings and should not require tricky function pointers or data structures or even macros to work with to the user code, if possible.

I'd go with (1) and assume you would completely hide the function pointers in your library, ie call them through an indirection in library code.

(3) is definitely the worst option, because it puts restrictions on the user code that are not directly obvious. It might also create non-obvious problems/observations when debugging user code.

(2) Is a pretty uncommon way to present a library and requires at least intermediate C fluency, and might put off non-expert C users.

You could also expose a hasSSE function along with SSE and non-SSE functions and leave the decision what to use to the user code. Not sure that would have any benefits over (1), though.

My suggestion will be compiling a separate unit for each instruction set (eg Xnosse.o Xsse3.o Xsse4.o, etc.) and use an automatic dispatcher for those. The user needs to get the best performance for his PC, and not care about the inside detailing.

Since you wrote your code runs in a library, you can make the dispatch decision on load time automatically by using an init function that will be called on library load. You can also make this decision run only the firsts time a function is actually called, this is for lazy binding.

Here is a code example (gcc only!)

Compilation units:

//Xnosse.c
void do_some_math_stuff_no_sse(int x, int y)
{
    ...do some sophisticated math stuff with no sse support
}
void do_some_other_math_stuff_no_sse(int x, int y)
{
    ...do some other sophisticated math stuff with no sse support
}

//Xsse3.c
void do_some_math_stuff_sse3(int x, int y)
{
    ...do some sophisticated math stuff with sse3 support
}
void do_some_other_math_stuff_sse3(int x, int y)
{
    ...do some other sophisticated math stuff with sse3 support
}

//Xsse4.c
void do_some_math_stuff_sse4(int x, int y)
{
    ...do some sophisticated math stuff with sse4 support
}
void do_some_other_math_stuff_sse4(int x, int y)
{
    ...do some other sophisticated math stuff with sse4 support
}

Now to the library:

//my_math.h
/* Following definitions are in my_math.c */
extern void (*do_some_math_stuff)(int x, int, y);
extern void (*do_some_other_math_stuff)(int x, int y);

//my_math.c
void not_set(int x, int y)
{
    // If you don't want to use the constructor for any reason,
    // say you want lazy binding, this will do the trick as our
    // functions do_math_stuff and do_other_math_stuff are initialized
    // to this one
    setup();
}

void (*do_some_math_stuff)(int x, int, y) = not_set;
void (*do_some_other_math_stuff)(int x, int y) = not_set;

int detect_sse()
{
    ..Do runtime detection of sse version
}

/* The following function will be called when your library loads */
void __attribute__ ((constructor)) setup(void) 
{
    if (detect_sse() == 0)
    {
        do_some_math_stuff = do_some_math_stuff_no_sse;
        do_some_other_math_stuff = do_some_other_math_stuff_no_sse;
    }
else if (detect_sse() == 3)
    {
        do_some_math_stuff = do_some_math_stuff_sse3;
        do_some_other_math_stuff = do_some_other_math_stuff_sse3;
    }
else if (detect_sse() == 4)
    {
        do_some_math_stuff = do_some_math_stuff_sse4;
        do_some_other_math_stuff = do_some_other_math_stuff_sse4;
    }
}

If you want lazy binding, remove constructor decorater from setup and compile with:

gcc -Wall -shared -fPIC -o libmy_math.so my_math.c Xnosse.c Xsse3.c Xsse4.c

If you want the dynamic dispatcher to run when the library loads use the following additional parameters to gcc:

gcc -Wall -shared -Wl,-init,setup -fPIC -o libmy_math.so my_math.c Xnosse.c Xsse3.c Xsse4.c

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM