2011年3月12日 星期六

libffi 的範例

在 C/C++ 當中,函式的型別必需在編譯期固定下來才可以正確的呼叫。所以 Function Pointer 都要記錄 Function 的 Signature,簡單地說下面二個 Function Pointer 是不同的型別:

void (*func0)();
void (*func1)(int);

然而在很多情況下,我們只能在執行期決定我們要呼叫的函數。最常見的例子就是有提供 Native Function Call 的 Interpreter。例如:Python (C API)、Java (JNI) 等等。然而這種功能如果只能使用 C 語言實作,會變得非常龐雜、沒效率而且失去一般性。所以有了 libffi 的產生。

libffi 的全名是 Foreign Function Interface,他本身是用組合語言撰寫,不過他提供了一個 Portable 的 API。我們只要呼叫這些 API 就可以進行動態的函式呼叫(這和 Function Pointer 不同。如前所述,Function Pointer 必需在 Compiler Type 決定函式的型別。而 libffi 可以在執行期決定 Callee 的型別)。許多有名的計劃都是 libffi 的使用者,例如:CPythonOpenJDKDalvik VM 等等。

以下是簡單的範例程式:

#include <stdio.h>
#include <stdlib.h>

/* libffi 的 header */
#include <ffi.h>

/* 一些亂七八糟的函式 */
int func0() {
  printf("%s()\n", __func__);
  return 0;
}

int func1(int p0) {
  printf("%s(%d)\n", __func__, p0);
  return p0;
}

int func2(int p0, int p1) {
  printf("%s(%d, %d)\n", __func__, p0, p1);
  return p0 + p1;
}

/* 函式查詢表。也可以使用 hash table、binary tree 來實作,
   不過作為一個簡單的範例直接使用 array 就好了。 */
void *func_table[] = {
  (void *)func0, (void *)func1, (void *)func2,
};

int main(int argc, char **argv) {
  int i;

  /* 決定我們要使用哪一個函式 */
  if (argc < 2) {
    fprintf(stderr, "USAGE: %s parameter-count\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  int parameter_count = atoi(argv[1]);

  if (parameter_count > 2) {
    fprintf(stderr, "WARNING: Parameter count is greater than 2.\n");
    parameter_count = 2;
  }

  /* 讀取函式的參數 */
  int values[2] = { 0 , 0 };

  for (i = 0; i < parameter_count; ++i) {
    if (scanf("%d", &values[i]) != 1) {
      static char const *const postfix[] = {
        "th", "st", "nd", "rd", "th",
        "th", "th", "th", "th", "th"
      };

      fprintf(stderr, "Unable to read %d%s parameter\n",
              i + 1, postfix[(i + 1) % 10]);
      exit(EXIT_FAILURE);
    }
  }

  /* 使用 libffi 呼叫指定的函式 */
  ffi_cif cif;
  ffi_type *args[] = { &ffi_type_sint, &ffi_type_sint };
  void *ptr_values[] = { &values[0], &values[1] };

  /* 準備 ffi 所需的資料結構 */
  if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, parameter_count,
                   &ffi_type_sint, args) == FFI_OK) {
    int sum = 0;

    /* 執行函式呼叫 */
    ffi_call(&cif, func_table[parameter_count], &sum, ptr_values);

    printf("return value: %d\n", sum);
  } else {
    fprintf(stderr, "Unable to initialize FFI structure.\n");
    exit(EXIT_FAILURE);
  }

  return EXIT_SUCCESS;
}