2013-02-03 37 views
6

Python関数を呼び出すCコードがあります。このPython関数はアドレスを受け取り、最終的にPythonが呼び出せる関数に変換するためにWINFUNCTYPEを使います。 Python関数へのパラメータとして送信されるC関数は、最終的に別のPython関数を呼び出します。クラッシュを引き起こすのはこの最後のステップです。ですから、私はC - > Python - > C - > Pythonに行きます。最後のC - > Pythonはクラッシュします。私は問題を理解しようとしてきましたが、私はできませんでした。PythonをC言語に埋め込む:Pythonコードで呼び出されるCコールバックでPythonコードを呼び出そうとするとエラーが発生する

誰かが私の問題を指摘できますか?

Cコードは、Visual Studio 2010でコンパイルされ、引数で実行 "C:\ ... \ crash.py" と "関数func1":

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

#include <Python.h> 

PyObject* py_lib_mod_dict; //borrowed 

void __stdcall cfunc1() 
{ 
    PyObject* py_func; 
    PyObject* py_ret; 
    int size; 
    PyGILState_STATE gil_state; 

    gil_state = PyGILState_Ensure(); 
    printf("Hello from cfunc1!\n"); 

    size = PyDict_Size(py_lib_mod_dict); 
    printf("The dictionary has %d items!\n", size); 
    printf("Calling with GetItemString\n"); 
    py_func = PyDict_GetItemString(py_lib_mod_dict, "func2"); //fails here when cfunc1 is called via callback... will not even go to the next line! 
    printf("Done with GetItemString\n"); 
    py_ret = PyObject_CallFunction(py_func, 0); 

    if (py_ret) 
    { 
     printf("PyObject_CallFunction from cfunc1 was successful!\n"); 
     Py_DECREF(py_ret); 
    } 
    else 
     printf("PyObject_CallFunction from cfunc1 failed!\n"); 

    printf("Goodbye from cfunc1!\n"); 
    PyGILState_Release(gil_state); 
} 

int wmain(int argc, wchar_t** argv) 
{ 
    PyObject* py_imp_str; 
    PyObject* py_imp_handle; 
    PyObject* py_imp_dict; //borrowed 
    PyObject* py_imp_load_source; //borrowed 
    PyObject* py_dir; //stolen 
    PyObject* py_lib_name; //stolen 
    PyObject* py_args_tuple; 
    PyObject* py_lib_mod; 
    PyObject* py_func; 
    PyObject* py_ret; 

    Py_Initialize(); 

    //import our python script 
    py_dir = PyUnicode_FromWideChar(argv[1], wcslen(argv[1])); 
    py_imp_str = PyString_FromString("imp"); 
    py_imp_handle = PyImport_Import(py_imp_str); 
    py_imp_dict = PyModule_GetDict(py_imp_handle); //borrowed 
    py_imp_load_source = PyDict_GetItemString(py_imp_dict, "load_source"); //borrowed 
    py_lib_name = PyUnicode_FromWideChar(argv[2], wcslen(argv[2])); 

    py_args_tuple = PyTuple_New(2); 
    PyTuple_SetItem(py_args_tuple, 0, py_lib_name); //stolen 
    PyTuple_SetItem(py_args_tuple, 1, py_dir); //stolen 

    py_lib_mod = PyObject_CallObject(py_imp_load_source, py_args_tuple); 
    py_lib_mod_dict = PyModule_GetDict(py_lib_mod); //borrowed 

    printf("Calling cfunc1 from main!\n"); 
    cfunc1(); 

    py_func = PyDict_GetItem(py_lib_mod_dict, py_lib_name); 
    py_ret = PyObject_CallFunction(py_func, "(I)", &cfunc1); 

    if (py_ret) 
    { 
     printf("PyObject_CallFunction from wmain was successful!\n"); 
     Py_DECREF(py_ret); 
    } 
    else 
     printf("PyObject_CallFunction from wmain failed!\n"); 

    Py_DECREF(py_imp_str); 
    Py_DECREF(py_imp_handle); 
    Py_DECREF(py_args_tuple); 
    Py_DECREF(py_lib_mod); 

    Py_Finalize(); 

    fflush(stderr); 
    fflush(stdout); 
    return 0; 
} 

Pythonコード:

from ctypes import * 

def func1(cb): 
    print "Hello from func1!" 
    cb_proto = WINFUNCTYPE(None) 
    print "C callback: " + hex(cb) 
    call_me = cb_proto(cb) 
    print "Calling callback from func1." 
    call_me() 
    print "Goodbye from func1!" 

def func2(): 
    print "Hello and goodbye from func2!" 

出力:

Calling cfunc1 from main! 
Hello from cfunc1! 
The dictionary has 88 items! 
Calling with GetItemString 
Done with GetItemString 
Hello and goodbye from func2! 
PyObject_CallFunction from cfunc1 was successful! 
Goodbye from cfunc1! 
Hello from func1! 
C callback: 0x1051000 
Calling callback from func1. 
Hello from cfunc1! 
The dictionary has 88 items! 
Calling with GetItemString 
PyObject_CallFunction from wmain failed! 

私は最後までPyErr_Printを()を添加し、これは結果であった:

Traceback (most recent call last): 
    File "C:\Programming\crash.py", line 9, in func1 
    call_me() 
WindowsError: exception: access violation writing 0x0000000C 

編集:abarnertが指摘したバグを修正しました。出力は影響を受けません。
EDIT:バグを解決したコードに追加されました(cfunc1でGILロックを取得しました)。もう一度abarnertに感謝します。 the docsとして

py_func = PyDict_GetItemString(py_lib_mod_dict, "func2"); //fails here when cfunc1 is called via callback... will not even go to the next line! 
printf("Done with GetItemString\n"); 
py_ret = PyObject_CallFunction(py_func, 0); 

Py_DECREF(py_func); 

言う、借用参照を返しPyDict_GetItemString

+0

私が言ったように、私はPOSIXに移植するときに同じ問題がありました。そのPy_DECREFを取り除くだけで私の問題は解決しました...私の答えでも述べたGILの問題でしょうか? 'PyGILState_Ensure' /' Release'ペアを入れてみて、何が起こるか見てみましょう。その間、 'PyObject_Print'文の束を入れて、すべてが正しいものであることを確認してください。だから私は 'func1'がdecref'dであるという問題を発見しました。 – abarnert

+0

この問題は、シングルスレッド環境で発生します。 GILロックは、2つのスレッドが同じPythonインタプリタインスタンス内にある可能性のあるマルチスレッド環境でのみ必要です。 – Dennis

+0

(私は新しい行のためにenterを押し続けます...そして、readintヘルプの後でさえ、改行を行う方法を知らない、申し訳ありません)実際にはPyDict_GetItemStringで失敗点があります。これは、AVを引き起こすPythonの標準ライブラリです。標準のPython関数の中にプリントを置く方法がわかりません。次の行にもAVする必要がありますが、決してそこには到達しません。興味深いのは、このコードはLinuxでは失敗せず、Windowsで失敗するということです(途中で指摘していただきありがとうございます)。私はLinux VMをセットアップして、他に何かを理解できるかどうかを確認しています。 – Dennis

答えて

5

問題は、このコードです。だから、あなたがここに初めて電話をかけたときに、参照を借りてそれを減らし、それを破壊させます。あなたが次回に電話をしたときには、ゴミを回収して、それを呼び出そうとします。

したがって、修正するにはPy_DECREF(py_func)を削除するか、またはpyfunc =行の後にPy_INCREF(py_func)を追加してください。

あなたはかなり簡単にこれをテストすることができますので、実際には、あなたは通常、特別な「死んだ」オブジェクトを取り戻すます:py_func =行の後とPy_DECREF行の後PyObject_Print(py_func, stdout)を入れて、あなたはおそらく<function func2 at 0x10b9f1230>最初のようなものが表示されます時間は、<refcnt 0 at 0x10b9f1230> 2回目と3回目です(4回目は表示されません。なぜなら、そこに着く前にクラッシュするからです)。

私は、Windowsは便利なボックスが、wmainを変え、wchar_tPyUnicode_FromWideCharWINFUNCTYPE、などなどmaincharPyString_FromStringCFUNCTYPEに、私はあなたのコードをビルドして実行することができた、と私はありません同じ場所でクラッシュする...と修正プログラムが動作します。

また、GILを内部に保持しないでください。cfunc1?私はこのようなコードを書かないことが多いので、間違っているかもしれません。そして、コードをそのまま使用してもクラッシュすることはありません。明らかに、実行するスレッドを生成するcfunc1クラッシュし、PyGILState_Ensure/Releaseはクラッシュを解決しますが、シングルスレッドの場合は何も必要ではありません。多分これは関係ないかもしれません...しかし、最初のものを固定した後に別のクラッシュが起きた場合(スレッドケースの場合は、Fatal Python error: PyEval_SaveThread: NULL tstateのように見えます)、これを調べてください。

ところで、あなたがPythonの拡張と埋め込みを初めて使う人は:手作業のrefcountingエラーによって引き起こされる、このような多くの説明できないクラッシュがあります。それがboost::pythonなどの理由があります。プレーンなC APIを使用するのは不可能ではありません。間違ってしまうのはとても簡単です。このような問題をデバッグするのに慣れなければなりません。

+0

あなたは正しいですPyDic_GetItemString。私はそれを修正する必要があります。しかし、実際にPy_DECREFをすべて削除して問題が解決され、その結果が同じであることを確認しました。 – Dennis

+0

ちょうどGILの取得をテストするチャンスがあり、それは動作します。私を助けてくれてありがとう、ありがとう。私はまだGILを取得することが特にこの単一スレッドのインスタンスで動作することを知りたいと思います。私はWINFUNCTYPEがGILをリリースすることは知っていますが、これもまたシングルスレッドのインスタンスなので、私はそれをほとんど考えませんでした。私はトラブルシューティングをしようとしていたときにGILについていくつかのリサーチをあらかじめ行っていましたが、私が読んだドキュメンテーションからは必要ないようでした。私はPython <-> Cを定期的に行っている人々とこれについて議論してきましたが、GILは複数のスレッドしか必要としていないと述べました。 – Dennis

+0

@Dennis:私はここで理解していると思います。基本的なルールは、あなたはGILの中でインタープリタを実行することだけが許可されているということです。 Python <-> Cを実行しているだけの場合、GILを失う唯一の方法は、別のスレッドがそれを取得した場合です。マルチスレッドコードを作成しない限り、問題はありません。しかし、Pythonを実行している場合<-> C <-> Pythonでは、一部のCコードがシングルスレッドコードでも明示的にリリースされるため、GILを失うこともあります。 – abarnert

2

abarnertの答えは、私が戸惑いましたが、私は早く家に帰ってきて、もう少し詳しく調べました。

説明に入る前に、私がGILと言うとき、私は厳密に、グローバルインタープリタロックがスレッド同期を行うために使用するmutex、セマフォ、その他のものを意味します。これには、PythonがGILを取得し、解放した前後で行う他のハウスキーピングは含まれません。

PyEval_InitThreads()を呼び出さないため、シングルスレッドプログラムはGILを初期化しません。従って、GILは存在しない。ロックが行われていても、シングルスレッドなので問題にはなりません。しかし、GILを取得してリリースする関数は、GILを取得/解放するだけでなく、スレッド状態の混乱のような面白いものも行います。 WINFUNCTYPEオブジェクトに関するドキュメントでは、Cへのジャンプを行う前にGILを解放することが明示的に述べられています。したがって、PythonでCコールバックが呼び出されたとき、PyEval_SaveThread()が呼び出されたと思われます(スレッド内で呼び出される少なくとも私の理解からの操作)。これはGILを(もし存在すれば)解放し、スレッド状態をNULLに設定しますが、シングルスレッドのPythonプログラムにはGILはありません。したがって、実際にはスレッド状態はNULLに設定されます。これにより、CコールバックのPython関数の大半が失敗するようになります。

PyGILState_Ensure/Releaseを呼び出す唯一のメリットは、Pythonにスレッド状態を何か有効なものに設定してから実行することです。獲得するGILはありません(PyEval_InitThreads()を呼び出さなかったため初期化されません)。

私の理論をテストするには:主関数では、PyThreadState_Swap(NULL)を使用してスレッド状態オブジェクトのコピーを取得します。私はコールバック中にそれを復元し、すべて正常に動作します。スレッド状態をnullにしておくと、Python - > Cコールバックを実行しなくても同じアクセス違反が発生します。 cfunc1の内部でスレッドの状態を復元し、Python - > Cコールバック中にcfunc1自体に問題はなくなりました。

cfunc1がPythonコードに戻るときに問題がありますが、それはスレッド状態を混乱させ、WINFUNCTYPEオブジェクトがまったく異なる何かを期待しているためです。戻り時にnullに戻さずにスレッドを状態に保つと、Pythonはそこに座って何もしません。それをヌルに戻すとクラッシュします。しかし、cfunc1が正常に実行されるので、あまり気にしていません。

私は結局、Pythonのソースコードを100%確信しているかもしれませんが、十分満足できると確信しています。

関連する問題