dlsym / dlopen с аргументами времени выполнения - PullRequest
6 голосов
/ 30 августа 2009

Я пытаюсь сделать что-то вроде следующего

  enum types {None, Bool, Short, Char, Integer, Double, Long, Ptr};
  int main(int argc, char ** args) {
     enum types params[10] = {0};
     void* triangle = dlopen("./foo.so", RTLD_LAZY);
     void * fun = dlsym(triangle, ars[1]);

     <<pseudo code>>
  }

Где псевдокод похож на

fun = {}
for param in params:
      if param == None:
         fun += void
      if param == Bool:
          fun += Boolean
      if param == Integer:
          fun += int
      ...
 returnVal = fun.pop()
 funSignature = returnval + " " + funName + "(" + Riffle(fun, ",") + ")"
 exec funSignature

Спасибо

Ответы [ 2 ]

21 голосов
/ 30 августа 2009

На самом деле, вы можете делать почти все, что вы хотите. В языке C (в отличие, например, от C ++) на функции в общих объектах ссылаются только их имена. Таким образом, чтобы найти - и, что самое важное, вызвать - правильную функцию, вам не нужна ее полная подпись. Вам нужно только его имя! Это и преимущество, и недостаток, но такова природа выбранного вами языка.

Позвольте мне продемонстрировать, как это работает.

#include <dlfcn.h>

typedef void* (*arbitrary)();
// do not mix this with   typedef void* (*arbitrary)(void); !!!

int main()
{
    arbitrary my_function;
    // Introduce already loaded functions to runtime linker's space
    void* handle = dlopen(0,RTLD_NOW|RTLD_GLOBAL);
    // Load the function to our pointer, which doesn't know how many arguments there sould be
    *(void**)(&my_function) = dlsym(handle,"something");
    // Call something via my_function
    (void)  my_function("I accept a string and an integer!\n",(int)(2*2));
    return 0;
}

Фактически, вы можете вызывать любую функцию таким способом. Однако есть один недостаток. На самом деле необходимо знать тип возвращаемого значения вашей функции во время компиляции . По умолчанию, если вы опустите void * в этом typedef, int предполагается как возвращаемый тип - и, да, это правильный C-код. Дело в том, что для правильной работы стека компилятору необходимо знать размер возвращаемого типа.

Вы можете обойти это, используя хитрости, например, заранее объявив несколько типов функций с разными размерами возвращаемых типов, а затем выбрав, какой из них вы на самом деле собираетесь вызывать. Но самое простое решение - требовать, чтобы функции в вашем плагине всегда возвращали void * или int; фактический результат возвращается через указатели, заданные в качестве аргументов.

Вы должны убедиться, что вы всегда вызываете функцию с точным числом и типами аргументов, которые она должна принимать. Обратите больше внимания на разницу между разными целочисленными типами (лучше всего было бы явно приводить аргументы к ним).

Несколько комментаторов сообщили, что приведенный выше код не гарантирует работу с переменными функциями (например, printf).

19 голосов
/ 30 августа 2009

То, что возвращает dlsym(), обычно является указателем функции - замаскированным как void *. (Если вы спросите его об имени глобальной переменной, он также вернет вам указатель на эту глобальную переменную.)

Затем вы вызываете эту функцию так же, как и любой другой указатель на функцию:

int (*fun)(int, char *) = (int (*)(int, char *))dlsym(triangle, "function");

(*fun)(1, "abc");    # Old school - pre-C89 standard, but explicit
fun(1, "abc");       # New school - C89/C99 standard, but implicit

Я старая школа; Я предпочитаю явную запись, чтобы читатель знал, что fun - это указатель на функцию без необходимости видеть ее объявление. С новой школьной нотацией вы должны не забыть искать переменную 'fun', прежде чем пытаться найти функцию с именем 'fun()'.

Обратите внимание, что вы не можете строить вызов функции динамически, как вы это делаете - или вообще не. Для этого требуется гораздо больше работы. Вы должны заранее знать, что ожидает указатель функции в виде аргументов и что он возвращает, и как все это интерпретировать.

Системы, которые управляют более динамическими вызовами функций, такие как Perl, имеют специальные правила о том, как функции вызываются и передаются аргументы, и не вызывают (возможно, не могут вызвать) функции с произвольными сигнатурами. Они могут вызывать только функции с подписями, о которых известно заранее. Один механизм (не используемый Perl) - это поместить аргументы в стек, а затем вызвать функцию, которая знает, как собирать значения из стека. Но даже если вызываемая функция манипулирует этими значениями и затем вызывает произвольную другую функцию, вызываемая функция обеспечивает правильную последовательность вызова для произвольной другой функции.

Отражение в Си сложно - очень тяжело. Это не подлежит отмене, но для этого требуется инфраструктура, которая поддерживает его, и дисциплина для его использования, и он может вызывать только те функции, которые поддерживают правила инфраструктуры.

...