3

I'm writing an interface layer to a shared library we are opening via dlopen/dlsym. The parameters to the functions may change depending on whether the library is compiled for system A vs system B.

I had a thought that instead of doing a bunch of typedefs, I could try to create an array of all of the function pointers returned from dlsym and then eventually use VA_ARGS to pass in the data. This would also allow me to deal with extensibility-- if the library parameters change, we'd just need to update the calling functions, instead of the typedefs below, too. Basically, I'm looking to change something like this:

typedef int* (*foo_t)(int, int);
typedef int* (*bar_t)(int, int, char*, int);
typedef void (*baz_t)(void);

void GetStuff()
{
  void *handle = dlopen("/my/cool/libLibrary.so", RTLD_LAZY);
  foo_t foo = dlsym(handle, "TheFooFunction");
  bar_t bar = dlsym(handle, "TheBarFunction");
  baz_t baz = dlsym(handle, "TheBazFunction");

  foo(1, 2);
  bar(3, 4, "cool", 5);
  baz();
}

into this:

enum funcs
{
  FUNC_FOO = 0,
  FUNC_BAR = 1,
  FUNC_BAZ = 2,
  FUNC_MAX
};

void funcArray[FUNC_MAX]; // Not legal - looking for a way to do something like this...

char *functionNames[FUNC_MAX] =
{
   [FUNC_FOO] = "TheFooFunction",
   [FUNC_BAR] = "TheBarFunction",
   [FUNC_BAZ] = "TheBazFunction"
};

void GetStuff()
{
  void *handle = dlopen("/my/cool/libLibrary.so", RTLD_LAZY);
  for (int i = 0; i < FUNC_MAX; i++)
  {
    funcArray[i] = dlsym(handle, functionNames[i]);
  }

  funcArray[FUNC_FOO](1, 2);
  funcArray[FUNC_BAR](3, 4, "cool", 5);
  funcArray[FUNC_BAZ]();
}

Is this possible? I know the array of voids is illegal. I can't use uintptr_t in it's place, since those are for data (not function) pointers. Are there any other options other than creating big #define's for each configuration and doing it the original way?

Josiah
  • 207
  • 3
  • 13
Maxthecat
  • 1,250
  • 17
  • 37
  • Does this answer your question? [How can I use an array of function pointers?](https://stackoverflow.com/questions/252748/how-can-i-use-an-array-of-function-pointers) – Sean Bright Feb 25 '20 at 20:00
  • Unfortunately, no. Those function pointers all have the same signature, which is what allows you to use them in an array like that. This question is for dissimilar function signatures. – Maxthecat Feb 25 '20 at 20:15
  • Answers from [here](https://stackoverflow.com/questions/12572575/i-can-call-a-function-imported-with-dlsym-with-a-wrong-signature-why) might be usefull. – Tarek Dakhran Feb 25 '20 at 20:26
  • 1
    The problem you are trying to solve is not trivial. You must provide a means for marshaling parameters and return values. If there's just the two fixed sets of functions, you're better off #define'ing them in your header files. If any of them change, you have to recompile anyway. – jwdonahue Feb 25 '20 at 20:34
  • 1
    The C standard does not have anything called `VA_ARGS`. Are you thinking of the `__VA_ARGS__` preproessor feature or the `va_arg` variable-argument-list feature? What role do you envision it playing? – Eric Postpischil Feb 25 '20 at 20:41
  • 3
    The C standard does not have any function-pointer equivalent for the “universal” object-pointer `void *`—except that **any** function-pointer type may serve as a universal function pointer type. All pointer-to-function types may be converted to other pointer-to-function types, and converting back to the original type produces the original pointer (or equivalent). So you can use an array of any function-pointer type of your choosing and manage the pointers with casts. The real question is how you intend to use those pointers—how will you call the functions with the correct arguments. – Eric Postpischil Feb 25 '20 at 20:43
  • Can you modify the shared library API to conform to a standard interface? Something like 'Result* func(Params*)` perhaps? Then `Result` and `Params` can be structs that have just enough metadata to determine the correct struct type to cast them to. Could be as simple as a version field and an unsized array. – jwdonahue Feb 25 '20 at 22:00

2 Answers2

2

Declare the array as an array of function pointers. Actually the type of function pointer does not matter.

void (*funcArray[FUNC_MAX])();

Then right before you call you have to convert your function pointers to the correct type:

((int *(*)(int, int))funcArray[FUNC_FOO])(1, 2);
((int *(*)(int, int, char*, int))funcArray[FUNC_BAR])(3, 4, "cool", 5);
((void (*)(void))funcArray[FUNC_BAZ])();
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
1

There are two main ways to achieve run-time selection of the type signature of a function being called.

  1. Have a switch statement somewhere which switches on an integer code (designed by you) indicating the function type. The switch cases are all calls which cast the function pointer differentlly and call it with different arguments.

  2. Use a library for constructing funcction calls dynamically, such as libffi (MIT-like license) or libffcall, (GPL-licensed GNU software).

Option 2 requires more demanding coding and may be overly general for a situation like this. The purpose of these libraries is to support dynamic languages being able to call C libraries. A dynamic language can use libffi or libffcall as a back end for translating the description of a function into an object which can be used to call it.

Option 2 hints at an option 3: Write your program, or a portion of it, in a higher level language that has good FFI, and leverage that to achieve the flexible calls into the shared libraries.

Kaz
  • 55,781
  • 9
  • 100
  • 149