ふにゃるんv2

もとは、http://d.hatena.ne.jp/Wacky/

C言語アプリケーションに Pythonを組み込む

ネットを ぼ〜っと回っていると、↓こんなのを見つけた。


そういや、似たようなのを公式ドキュメントでも見たなぁ、と思い出す。あった、あった。


国内で、参考になる資料は無いかな?と思って ぐぐると MLのが見つかった。

ビルド方法

先のコードを参考に、コードを書いてみる。

#include   <stdio.h>
#include    <Python.h>

int main(void)
{
    Py_Initialize();
    PyRun_SimpleString("print 'hello python'");
    Py_Finalize();
    return 0;
}


早速ビルドしてみよう。まず、Python 2.4 for Cygwinから。

$ gcc test.cpp -I/usr/include/python2.4 -L/usr/lib/python2.4/config -lpython2.4
$ ls
a.exe            test.cpp
$ ./a.exe
hello python

結構簡単ですな。

んじゃ次、Python 2.4 for Windowsね。

$ call "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\vcvars32.bat"

$ "C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\vsvars32.bat"
Setting environment for using Microsoft Visual Studio .NET 2003 tools.
(If you have another version of Visual Studio or Visual C++ installed and wish
to use its tools from the command line, run vcvars32.bat for that version.)

$ cl test.cpp /GX /IC:\tool2\Python24\include C:\tool2\Python24\libs\python24.lib
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj
C:\tool2\Python24\libs\python24.lib

$ test
hello python

おぉ〜、簡単に動いちゃうよ。


Python 2.4 for Windowsで、VC.NET 2003を使っているのは、Python 2.4のトップディレクトリにある ランタイムライブラリが、msvcr71.dll と msvcp71.dll の、VC.NET 2003のランタイムライブラリだから。
多分、VS2005 Expressでもビルドできると思う。
VC++6.0はどうかな?下位互換性あったっけ?何かオプション付けてライブラリをビルドしてないとダメじゃなかったけな?

実行は、Dependency Walkerに喰わせた所、msvcr71.dllが最低限必要っぽい。
Python & C++
Python & C++ posted from フォト蔵

pythonからアプリ内のC言語関数を呼び出す

C言語アプリケーションとPythonの組み合わせって、アプリケーションのマクロ言語にPythonを使うケースを想定してんだと思うんですよ。
つか、自分がC言語アプリとPythonの組み合わせって言われたら、そんな用途しか思いつかないし。


となると、C言語関数を Pythonから呼び出せないと片手落ちだよね?
早速試してみる。資料としては、「5.4 埋め込まれた Python の拡張」が参考になると思う。

#include    <stdio.h>
#include    <Python.h>

static int numargs=0;

/* アプリケーションのコマンドライン引数の個数を返す */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return Py_BuildValue("i", numargs);
}

static PyMethodDef EmbMethods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};


int main(int argc, char* argv[])
{
    Py_Initialize();
    
    numargs = argc;
    Py_InitModule("emb", EmbMethods);
    
    static const char*  CODES =
        "import emb\n"
        "print 'Number of arguments', emb.numargs()\n"
        ;
    
    PyRun_SimpleString(CODES);
    Py_Finalize();
    return 0;
}

Python 2.4 for Cygwinね。

$ gcc test.cpp -I/usr/include/python2.4 -L/usr/lib/python2.4/config -lpython2.4
$ ./a.exe 1 2 3 4 5 6
Number of arguments 7 

次、Python 2.4 for Windowsね。

$ cl test.cpp /GX /IC:\tool2\Python24\include C:\tool2\Python24\libs\python24.lib
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj
C:\tool2\Python24\libs\python24.lib

$ test 1 2 3 4 5 6
Number of arguments 7

ほんま、簡単ですなぁ。
ちなみに、CODESん所に、"help(emb)"を突っ込むと、↓こんなのが表示される。

Help on module emb:

NAME
    emb

FILE
    (built-in)

FUNCTIONS
    numargs(...)
        Return the number of arguments received by the process. 

なるほど、なるほど。

boost.pythonで簡単に記述する

一応、C言語アプリとPythonのコラボが実現できた訳なんだけど、C言語側のコードが煩雑なんですよね。
例えば、「5.3 純粋な埋め込み」に示されている、Pythonコードを呼び出す C言語コード。えらい長いじゃん?
見ると、オブジェクトの生成と破棄、リファレンスカウンタの管理と異常処理に手間喰っているみたいなんですわ。


…そういうのって、C++使えば解消しますよね?(そういうクラスライブラリを用意すれば、だけど)
gcc / VC++の両方共、C++ が使えるんだから、そういう面倒な処理は、言語&ライブラリ(道具)に やらせるべきだと思うんですよ。別の新しい事に挑戦してみませんか?旦那。
2011/09/11 追記:表現がキツかったようですので、勝手ながら手直しさせてもらいました。


という訳で、調べてみると 我らが boostに 解決してくれる便利なライブラリがありました。

これを元に、コードを書いてみませぅ。

#include    <stdio.h>
#include    <Python.h>
#include    <boost/python.hpp>

using namespace boost::python;

int main(int argc, char* argv[])
{
    Py_Initialize();
    
    handle<> main_module(borrowed( PyImport_AddModule("__main__") ));
    handle<> main_namespace(borrowed( PyModule_GetDict(main_module.get()) ));
    
    static const char*  CODES = 
        "print 'hello boost::python'\n"
        ;
    handle<>( PyRun_String(CODES, Py_file_input, main_namespace.get(), main_namespace.get()) );
    
    Py_Finalize();
    
    return 0;
}

早速コンパイルPython 2.4 for Windowsね。

$ cl test.cpp /GX /IC:\tool2\Python24\include C:\tool2\Python24\libs\python24.lib /IC:\Boost\include\boost-1_33 C:\Boost\lib\boost_python-vc71-mt.lib
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj
C:\tool2\Python24\libs\python24.lib
C:\Boost\lib\boost_python-vc71-mt.lib

$ test
hello boost::python

なお、boost.pythonは、ヘッダをインクルードするだけじゃダメで、bjam使ってライブラリをビルドする必要があります。
後、実行パスに、boost_python-vc71-mt-1_33.dll を置いておく必要があるよ。(静的リンクすればDLLを組み込む必要ないけどね)
(Cygwin版なんだけど、セットアップパッケージに boost と boost-devel があるんだけど、これ入れるだけじゃ boost_python が入ってくれない。何か設定悪いのかな?)

boost.pythonで、アプリ内部の関数を呼び出す

更に、アプリ内部の関数を呼び出してみよう。

#include    <stdio.h>
#include    <Python.h>
#include    <boost/python.hpp>

using namespace boost::python;

class HelloCls {
public:
    HelloCls(){ puts("ctor HelloCls"); }
    char const* greet(){
        return "hello python";
    }
};

BOOST_PYTHON_MODULE(Hello)
{
    class_<HelloCls>("HelloCls")
        .def("greet", &HelloCls::greet)
        ;
}

int main(int argc, char* argv[])
{
    if(PyImport_AppendInittab("Hello", initHello) == -1)
        puts("helloモジュールのセットアップに失敗");
    
    Py_Initialize();
    
    handle<> main_module(borrowed( PyImport_AddModule("__main__") ));
    handle<> main_namespace(borrowed( PyModule_GetDict(main_module.get()) ));
    
    static const char*  CODES = 
        "import Hello\n"
        "o = Hello.HelloCls()\n"
        "print o.greet()\n"
        ;
    handle<>( PyRun_String(CODES, Py_file_input, main_namespace.get(), main_namespace.get()) );
    
    Py_Finalize();
    
    return 0;
}

早速実行。

$ cl test.cpp /GX /IC:\tool2\Python24\include C:\tool2\Python24\libs\python24.lib /IC:\Boost\include\boost-1_33 C:\Boost\lib\boost_python-vc71-mt.lib
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj
C:\tool2\Python24\libs\python24.lib
C:\Boost\lib\boost_python-vc71-mt.lib

$ test
ctor HelloCls
hello python

お〜いえ〜。
まぁ、このように、boost.pythonを使えば、PythonC++の境界を適当にラップしてくれるので非常に便利。


ところで、最初、グローバルな関数を BOOST_PYTHON_MODULE に突っ込んでいたのだけど、うまくいかなかったので、色々ぐぐるってみた。


国内では、サンプルコードは見つからなかった。
唯一、id:eto3dcgさんが、リンク集を紹介していた。

結論を言いますと…

つまりですね、

  • PythonからC/C++で作ったモジュールを使いたい→swigでモジュールを作ればOK
  • C/C++からPythonのコードを呼び出したい→boost.pythonでコードをラップすればOK

という直交最強コンボが、Pythonには用意されている訳ですよ旦那。

余談

boost.pythonでビルドする際の話なんだけど、実行時こんなエラーが出る場合がある。

Fatal Python error: Interpreter not initialized (version mismatch?)

事例を挙げると、

  1. boost.pythonのライブラリを作るのに、python2.3で作った
  2. 次に、boost.pythonのライブラリとリンクするのに、python2.4でリンクした

というアンマッチな使い方をすると出ます。はぁい。