2015-10-23 11 views
9

私はFILE *に出力を書き込むことによって、テキストを生成する(Cで書かれた)ライブラリ関数を持っています。私はこれをPython(2.7.x)で、一時ファイルまたはパイプを作成し、関数に渡し、ファイルから結果を読み取り、Python文字列として返すコードでラップしたいと思います。パスFILE *

/* Library function */ 
void write_numbers(FILE * f, int arg1, int arg2) 
{ 
    fprintf(f, "%d %d\n", arg1, arg2); 
} 

Pythonラッパー:

from ctypes import * 
mylib = CDLL('mylib.so') 


def write_numbers(a, b): 
    rd, wr = os.pipe() 

    write_fp = MAGIC_HERE(wr) 
    mylib.write_numbers(write_fp, a, b) 
    os.close(wr) 

    read_file = os.fdopen(rd) 
    res = read_file.read() 
    read_file.close() 

    return res 

#Should result in '1 2\n' being printed. 
print write_numbers(1,2) 

私は私の最善の策はMAGIC_HERE()何のためにあるのか思ったんだけど

は、ここで私は後だものを説明するために単純化した例です。

私は​​を使用して、Python c_void_tを返すlibc.fdopen()ラッパーを作成し、それをライブラリ関数に渡したいと思っています。私はそれが理論上安全であるべきだと思っています。このアプローチに問題があるのか​​、この問題を解決するための既存のPythonのアイムがあるのだろうかと思っています。

また、これは(単に、「永遠に」と仮定することができます)長時間実行プロセスになりますので、漏れたファイルディスクリプタは問題になるだろう。

+0

'OSをディスクリプタ/ファイルオブジェクトには、他のI/Oを保証しません.popen() 'が間違っています。これには少なくとも1つの引数が必要です。コマンドラインを呼び出してパイプを取得する必要があります。また、[docs](https://docs.python.org/2/library/os.html?highlight=os.popen#os.popen)のように、 'サブプロセス 'の方が非難されています。 –

+0

申し訳ありません、私は 'os.pipe()'を意味しました。更新しました。 –

+1

Cランタイムライブラリの不一致の可能性があるWindows上でこれを実行する予定がない限り、 'libc.fdopen'を呼び出して結果の' FILE'ポインタを渡す際に問題はないと思います。しかし 'c_void_p'を使う代わりに、' class FILE(Structure):pass'を作成し、 'libc.fdopen.restype = POINTER(FILE)'を設定します。これは整数の結果に変換されません。 OTOHでは、 'restype'としての' c_void_p'が整数に変換されるので、 'mylib.write_numbers.argtypes'も64ビットのポインタ値を切り捨てないように設定する必要があります。 – eryksun

答えて

3

まず、FILE*は、標準入出力、特定のエンティティであることに注意してください。システムレベルには存在しません。システムレベルで存在するものは、WindowsでUNIXでの(file.fileno()で取得)ディスクリプタ(os.pipe()戻りすでに平野記述子)とハンドル(msvcrt.get_osfhandle()で取得)です。 これは、実行中のCランタイムが複数ある可能性がある場合には、ライブラリ間の交換フォーマットとしては不適切です。あなたのライブラリがPythonのコピーとは別のCランタイムに対してコンパイルされていると、あなたは困っているでしょう:1)構造のバイナリレイアウトが異なるかもしれません(例えば、デバッグの目的で、 2)Windowsで、ファイル構造へのリンクは同様にC-固有のエンティティであるための記述子、およびそのテーブルは内部でCランタイム1によって維持されています。

また、Pythonの3で、I/Oはstdioからそれを解くために、オーバーホールしました。だから、FILE*はそのPythonフレーバに似ていません(おそらく、ほとんどの非Cフレーバもそうです)。今

、何が必要

    • に何とかその fdopen()(または同等)を呼び出すあなたが必要があるCランタイム推測、および
    • です。

    (すべての後にPythonのモットーの一つは、「ハード簡単に間違ったこと、正しいことを行う」です)


    最もクリーンな方法は、ライブラリがあると、正確なインスタンスを使用することです

    1番目の項目については、読み込まれた動的モジュールのメタデータを解析して調べることができるPythonモジュールが見つかりませんでした。(動的にリンクされていることを祈ってください。どのDLLs /そうそれはリンクされている(ちょうど名前または名前+バージョンでさえ、システム上のライブラリの複数のインスタンスが存在する可能性があるため、十分ではありません)。そのフォーマットに関する情報は広く入手可能であるため、間違いなく可能です。

    2番目の項目は、ctypes.cdll('path').fdopen(MSVCRTの場合は_fdopen)です。


    第二に、あなたはライブラリとして同じ(または互換性の保証)ランタイムに対してコンパイルされるだろうし、あなたのために、前述の記述/ハンドルからの変換を行うだろう小さなヘルパーモジュールを行うことができます。これは事実上、適切なライブラリを編集するための回避策です。


    最後に、最も簡単な(そして汚い)ctypes.pythonapiを介して利用可能なPythonのC APIを介してPythonのCランタイムインスタンスを(したがって、すべての上記の警告が完全に適用)を用いる方法があります。それは

    • を活用するPythonの2のファイルのようなオブジェクトがstdioFILE*上のラッパーであるという事実(Pythonの3年代ではない)ラップFILE*を返し
    • PyFile_AsFile API(it's missing from Python 3ことに注意してください)
        スタンドアロン fdため
      • (返すためにFILE*が存在することになるように;)、あなたは最初のファイルのようなオブジェクトを作成する必要があります)
    • オブジェクトのid()は、そのメモリアドレスであるという事実(CPythonと固有)2

      >>> open("test.txt") 
      <open file 'test.txt', mode 'r' at 0x017F8F40> 
      >>> f=_ 
      >>> f.fileno() 
      3 
      >>> ctypes.pythonapi 
      <PyDLL 'python dll', handle 1e000000 at 12808b0> 
      >>> api=_ 
      >>> api.PyFile_AsFile 
      <_FuncPtr object at 0x018557B0> 
      >>> api.PyFile_AsFile.restype=ctypes.c_void_p #as per ctypes docs, 
                   # pythonapi assumes all fns 
                   # to return int by default 
      >>> api.PyFile_AsFile.argtypes=(ctypes.c_void_p,) # as of 2.7.10, long integers are 
             #silently truncated to ints, see http://bugs.python.org/issue24747 
      >>> api.PyFile_AsFile(id(f)) 
      2019259400 
      

    fd SとCのポインタで、あなたは正しいことを確認する必要があることを念頭に置いておくん。手でオブジェクトの寿命を! os.fdopen()によって返さ

    • ファイルのようなオブジェクトは、ファイルオブジェクトを/ごみ収集
  • しばらく閉じられた後にあなたがそれらを必要とする場合os.dup().close()
    • ので、重複した記述子に記述子を閉じてくださいC構造体を操作して、対応するオブジェクトの参照カウントをPyFile_IncUseCount()/に調整します。
    • それはデータを台無しにするからである(例えば、これまでiter(f)/for l in fを呼び出しているので、内部キャッシュが行われていることがstdioのキャッシュから独立したのです)
  • +0

    別のCランタイム(主にWindowsの問題)を使用するライブラリについて心配している場合は、 'PyFile_AsFile'を使用すると何も解決されず、正当な理由でコードをPython 2に限定します。なぜCythonを議論に持ち込むのですか?それはランダムなセグである。 – eryksun

    +0

    また、 'id(f)'をポインタとして渡さないでください。 'py_object(f)'がPythonオブジェクトを渡すようにしたいとします。これはCPythonの 'PyObject *'と同じです。ベースアドレスを得るために 'id'を使うことはCPythonに特有であり、*引数としてPython整数を渡すこともデフォルトでは32ビットのCのint値として変換され、64ビットのポインタ値は切り捨てられます。 – eryksun

    +0

    「整数へのポインタを切り捨てる」という裏付けを見たいと思っています。 Pythonには長い整数の概念がありますが、 'c_void_p'を切り捨てる理由はまったくありません。 –