C言語からPython3で作った関数を呼び出せない

投稿者: Anonymous

CからPython3の関数を呼び出そうとしていますがうまくコンパイルできません
pycall.cからpycall.pyのHiという関数を呼び出そうとしてしているのですが、リンクが失敗しているように見えます

どうすればいいのでしょうか?

コンパイル時のコマンド

clang -I`python3-config --prefix`/Headers -o pycall pycall.c

エラーメッセージ

pycall.c:4:36: warning: implicit declaration of function 'PyString_FromString' is invalid in C99 [-Wimplicit-function-declaration]
    PyObject *pycallModuleString = PyString_FromString((char*)"pycall");
                                   ^
pycall.c:4:15: warning: incompatible integer to pointer conversion initializing 'PyObject *' (aka 'struct _object *') with an expression of type 'int' [-Wint-conversion]
    PyObject *pycallModuleString = PyString_FromString((char*)"pycall");
              ^                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
Undefined symbols for architecture x86_64:
  "_PyFloat_AsDouble", referenced from:
      _main in pycall-4fd2bb.o
  "_PyImport_Import", referenced from:
      _main in pycall-4fd2bb.o
  "_PyObject_CallObject", referenced from:
      _main in pycall-4fd2bb.o
  "_PyObject_GetAttrString", referenced from:
      _main in pycall-4fd2bb.o
  "_PyString_FromString", referenced from:
      _main in pycall-4fd2bb.o
  "_Py_BuildValue", referenced from:
      _main in pycall-4fd2bb.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

pycall.c

#include <Python.h>

int main() {
    PyObject *pycallModuleString = PyString_FromString((char*)"pycall");
    PyObject *pycallModule = PyImport_Import(pycallModuleString);

    PyObject *HiFunction = PyObject_GetAttrString(pycallModule, (char*)"Hi");
    PyObject *args = Py_BuildValue("()");
    PyObject *myResult = PyObject_CallObject(HiFunction, args);
    double result = PyFloat_AsDouble(myResult);
    printf("%fn", result);
}

pycall.py

def Hi():
    print("Hi! I am Python3.6")
    return 3.6

解決

気付いた点は以下です。

  • 最初に Py_Initialize() を実行(python interpreter を初期化)する
  • Python 側の import path(sys.path 変数) に pycall.py があるディレクトリパスを追加
  • パス名を python object に変換する場合は PyUnicode_DecodeFSDefault() を使用

以下では pycall.pypycall が同じディレクトリに置かれていて、そのディレクトリで pycall を実行するものとしています。

pycall.c

#include <Python.h>

int main() {
  Py_Initialize();

  /* PYTHONPATH を使用する場合は不要 */
  PyObject* sysPath = PySys_GetObject("path");
  PyObject* dir = PyUnicode_DecodeFSDefault(".");
  PyList_Append(sysPath, dir);
  /* ここまで */

  PyObject *pycallModuleString = PyUnicode_DecodeFSDefault("pycall");
  PyObject *pycallModule = PyImport_Import(pycallModuleString);

  PyObject *HiFunction = PyObject_GetAttrString(pycallModule, "Hi");
  PyObject *args = Py_BuildValue("()");
  PyObject *myResult = PyObject_CallObject(HiFunction, args);
  double result = PyFloat_AsDouble(myResult);
  printf("%fn", result);
}

$ ls
pycall.c pycall.py

# こちらの環境には clang がインストールされていないので gcc を使います
$ gcc $(python3-config --includes) -o pycall pycall.c $(python3-config --ldflags)
$ ./pycall
Hi! I am Python3.6
3.600000

sys.path.(current directory)を追加していますが、環境変数 PYTHONPATHpycall.py のディレクトリパスを指定して実行する場合には不要になります。例えば、以下の様に指定して実行します。

$ PYTHONPATH='.' ./pycall
Hi! I am Python3.6
3.600000
回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *