2017-08-12 8 views
1

私はcコードをcythonに公開しようとしていますが、別のcythonモジュールのcファイルで定義された関数を使用しようとすると "未定義シンボル"
マニュアルラッパーを使用している私のhファイルおよび関数で定義された関数は問題なく動作します。Cラッパーを使用したCythonの未定義シンボル

this questionと基本的に同じですが、解決策(ライブラリとのリンク)は私にとっては満足できません。
setup.pyスクリプトに何かがないと思いますか?


私の例 最小化例:

がfoo.h

int source_func(void); 

inline int header_func(void){ 
    return 1; 
} 

foo.cの

#include "foo.h" 

int source_func(void){ 
    return 2; 
} 


foo_w rapper.pxd

cdef extern from "foo.h": 
    int source_func() 
    int header_func() 

cdef source_func_wrapper() 

foo_wrapper.pyx

cdef source_func_wrapper(): 
    return source_func() 


私は関数を使用したいcythonモジュール:
test_lib.pyx

cimport foo_wrapper 

def do_it(): 
    print "header func" 
    print foo_wrapper.header_func() # ok 
    print "source func wrapped" 
    print foo_wrapper.source_func_wrapper() # ok  
    print "source func" 
    print foo_wrapper.source_func() # undefined symbol: source_func 


setup.pyビルドの両方 foo_wrappertest_lib

from distutils.core import setup 
from distutils.extension import Extension 
from Cython.Build import cythonize 

# setup wrapper 
setup(
    ext_modules = cythonize([ 
     Extension("foo_wrapper", ["foo_wrapper.pyx", "foo.c"]) 
    ]) 
) 

# setup test module 
setup(
    ext_modules = cythonize([ 
     Extension("test_lib", ["test_lib.pyx"]) 
    ]) 
) 

答えて

3

機能の異なる3種類がfoo_wrapperにあります

  1. source_func_wrapperのpython-機能とPythonのランタイムが呼び出しを処理していますこの機能の
  2. header_funcは、コンパイル時に使用されるインライン関数なので、後でその定義/マシンコードは必要ありません。
  3. source_funcは静的(これはfoo_wrapperの場合)または動的(私はこれがtest_libのあなたの希望であると仮定します)リンカーによって処理されなければなりません。私はセットアップが箱から出して動作しないしない理由を、説明しようが、拳私は(少なくとも私の意見では)2を紹介したいと思いますダウン

さらに最良の選択肢:

A:この問題は完全に回避してください。 foo_wrapperは、foo.hのc関数をラップします。つまり、他のすべてのモジュールでこれらのラッパー関数を使用する必要があります。誰もが機能に直接アクセスできる場合は、ラッパーの種類全体が廃止されます。あなた `PYXファイルにfoo.hインターフェースを隠す:

#foo_wrapper.pdx 
cdef source_func_wrapper() 
cdef header_func_wrapper() 


#foo_wrapper.pyx 
cdef extern from "foo.h": 
    int source_func() 
    int header_func() 

cdef source_func_wrapper(): 
    return source_func() 
cdef header_func_wrapper(): 

B: C関数経由で直接FOO-機能を使用するために有効かもしれません。この場合、stdc++ -library:foo.cppのcythonと同じ戦略を共有ライブラリにする必要があります。必要な場合はcimport経由でインポートできるfoo.pdx-file(pyxはありません)のみが必要です。さらに、libfoo.soは、foo_wrappertest_libの両方に依存して追加する必要があります。

しかし、アプローチBは、より多くの喧騒を意味します - あなたは、ダイナミックローダがそれを見つけることができるどこかlibfoo.soを配置する必要があり...


他の代替:

我々が見るように、 foo_wrapper + test_libを動作させる方法はたくさんあります。まず、動的ライブラリの読み込みがPythonでどのように機能するかを詳しく見てみましょう。

我々は手でtest_lib.so見てみることから始め:

>>> nm test_lib.so --undefined 
.... 
    U PyXXXXX 
    U source_func 

Pyで開始し、実行時にPythonの実行によって提供されるほとんどが、未定義のシンボルが多いです。しかし、また、私たちの悪党 - source_funcがあります。

は今、私たちは

LD_DEBUG=libs,files,symbols python 

経由でのpythonを起動して、import test_libを経由して私たちの拡張モジュールを読み込みます。 dlopen経由

>>>>: file=./test_lib.so [0]; dynamically loaded by python [0] 

Pythonの負荷test_lib.soとルックアップ/ test_lib.soから未定義シンボルを解決するために開始します:トリガーデバッグ-traceでは、以下を参照してくださいすることができます

>>>>: symbol=PyExc_RuntimeError; lookup in file=python [0] 
>>>>: symbol=PyExc_TypeError; lookup in file=python [0] 

これらのpythonシンボルが検出されました非常に迅速に - それらはすべてPython実行可能ファイルで定義されています - 動的リンカーが最初に参照する場所です(この実行ファイルが-Wl,-export-dynamicとリンクされている場合)。しかし、それはsource_funcと異なっている:

>>>>: symbol=source_func; lookup in file=python [0] 
>>>>: symbol=source_func; lookup in file=/lib/x86_64-linux-gnu/libpthread.so.0 [0] 
    ... 
>>>>: symbol=source_func; lookup in file=/lib64/ld-linux-x86-64.so.2 [0] 
>>>>: ./test_lib.so: error: symbol lookup error: undefined symbol: source_func (fatal) 

だから、ロードされたすべての共有ライブラリを検索した後、シンボルが見つからない場合、我々は中止しなければなりません。楽しい事実は、foo_wrapperがまだロードされていないので、source_funcはそこで参照できません(次のステップでは、test_libの依存関係としてPythonでロードされます)。

プリロードされたfoo_wrapper.soでPythonを起動するとどうなりますか?foo_wrapperをプリロードするので、成功import test_libを呼び出す

LD_DEBUG=libs,files,symbols LD_PRELOAD=$(pwd)/foo_wrapper.so python 

この時間は、ダイナミックローダーは、シンボルを検索し、最初の場所である(後のpython-実行可能):

>>>>: symbol=source_func; lookup in file=python [0] 
    >>>>: symbol=source_func; lookup in file=/home/ed/python_stuff/cython/two/foo_wrapper.so [0] 

しかし、それは、ときに動作しない方法foo_wrapper.soはプリロードされていませんか?

ext_modules = cythonize([ 
    Extension("test_lib", ["test_lib.pyx"], 
       libraries=[':foo_wrapper.so'], 
       library_dirs=['.'], 
    )]) 

これは、以下のリンカコマンドにつながる:まずはtest_libの私達の設定にライブラリとしてfoo_wrapper.soを追加してみましょう

gcc ... test_lib.o -L. -l:foo_wrapper.so -o test_lib.so 

を私たちは今の記号を見ると、私たちは何の違いを見ていない:

>>> nm test_lib.so --undefined 
.... 
    U PyXXXXX 
    U source_func 

source_funcは未定義です。共有ライブラリとリンクすることの利点は何ですか?違いはtest_lib.soためで、必要に応じて、今foo_wrapper.soが表示されていること、である:

>>>> readelf -d test_lib.so| grep NEEDED 
0x0000000000000001 (NEEDED)    Shared library: [foo_wrapper.so] 
0x0000000000000001 (NEEDED)    Shared library: [libpthread.so.0] 
0x0000000000000001 (NEEDED)    Shared library: [libc.so.6] 

ld、リンクこれは動的リンカの仕事ですが、それは予行演習を行い、注目することによって、ダイナミックリンカを助ける、ということはありませんシンボルを解決するにはfoo_wrapper.soが必要です。したがって、シンボルの検索が開始される前にロードする必要があります。しかし、それはシンボルsource_funcfoo_wrapper.soで調べられなければならないと明示的には言わない - 我々は実際にそれを見つけ出してどこでも使うことができる。

が再びのpythonを起動することができます、プリロードせずにこの時間:

>>>> LD_DEBUG=libs,files,symbols python 
    >>>> import test_lib 
    .... 
    >>>> file=./test_lib.so [0]; dynamically loaded by python [0].... 
    >>>> file=foo_wrapper.so [0]; needed by ./test_lib.so [0] 
    >>>> find library=foo_wrapper.so [0]; searching 
    >>>> search cache=/etc/ld.so.cache 
    ..... 
    >>>> `foo_wrapper.so: cannot open shared object file: No such file or directory. 

OK]をクリックして、今、ダイナミックリンカが知っている、それはfoo_wrapper.soを見つけるために持っているが、それはパスにどこにもありませんので、我々は、エラーメッセージが表示されます。

ダイナミックリンカーに共有ライブラリを探す場所を伝えなければなりません。多くの方法があり、そのうちの一つは、設定することですLD_DYNAMIC_LIBRARY

LD_DEBUG=libs,symbols,files LD_LIBRARY_PATH=. python 
>>>> import test_lib 
.... 
>>>> find library=foo_wrapper.so [0]; searching 
>>>> search path=./tls/x86_64:./tls:./x86_64:.  (LD_LIBRARY_PATH) 
>>>> ... 
>>>> trying file=./foo_wrapper.so 
>>>> file=foo_wrapper.so [0]; generating link map 
今回 foo_wrapper.soが発見された

(ダイナミックローダがLD_LIBRARY_PATHでほのめかした場所を見て)、ロードされた後、test_lib.soに未定義シンボルを解決するために使用。

runtime_library_dirs -setup引数を使用した場合の違いは何ですか?

ext_modules = cythonize([ 
    Extension("test_lib", ["test_lib.pyx"], 
       libraries=[':foo_wrapper.so'], 
       library_dirs=['.'],    
       runtime_library_dirs=['.'] 
      ) 
]) 

となりましLD_DYNAMIC_LIBRARY経由で設定されていない場合でもそうRPATH呼ば上で発見された

LD_DEBUG=libs,symbols,files python 
>>>> import test_lib 
.... 
>>>> file=foo_wrapper.so [0]; needed by ./test_lib.so [0] 
>>>> find library=foo_wrapper.so [0]; searching 
>>>> search path=./tls/x86_64:./tls:./x86_64:.  (RPATH from file ./test_lib.so) 
>>>>  trying file=./foo_wrapper.so 
>>>> file=foo_wrapper.so [0]; generating link map 

foo_wrapper.soを呼び出します。しかし、これが欲しかっれるものではないほとんどの時間である現在の作業ディレクトリへの相対パスである

>>>> readelf -d test_lib.so | grep RPATH 
     0x000000000000000f (RPATH)    Library rpath: [.] 

:私たちは、このRPATHは、静的なリンカによって挿入されて見ることができます。一つは、今示す絶対パスを渡すか

ext_modules = cythonize([ 
       Extension("test_lib", ["test_lib.pyx"], 
       libraries=[':foo_wrapper.so'], 
       library_dirs=['.'],     
       extra_link_args=["-Wl,-rpath=$ORIGIN/."] #rather than runtime_library_dirs 
      ) 
]) 

が得shared library. readelfの(移動/コピーを通して、例えば変えることができる)は、現在位置からの相対パスを作るために使用する必要があります

>>>> readelf -d test_lib.so | grep RPATH 
    0x000000000000000f (RPATH)    Library rpath: [$ORIGIN/.] 

を意味します必要な共有ライブラリは、ロードされた共有ライブラリのパス、つまりtest_lib.soに対して相対的に検索されます。

私が主張していないfoo_wrapper.soのシンボルを再利用したい場合は、これも設定方法です。


ただし、既に作成したライブラリを使用する可能性があります。

元の設定に戻りましょう。最初にfoo_wrapper(プリロードの一種として)をインポートしてからtest_libをインポートするとどうなりますか?私:

>>>> import foo_wrapper 
>>>>> import test_lib 

これはデフォルトでは機能しません。しかし、なぜ?明らかに、foo_wrapperのロードされたシンボルは他のライブラリには見えません。 Pythonはdlopenを共有ライブラリの動的ロードに使用し、this good articleで説明されているように、いくつかの異なる戦略が可能です。どのフラグが設定されているかを確認するには、

>>>> import sys 
>>>> sys.getdlopenflags() 
>>>> 2 

を使用できます。 2RTLD_NOWを意味します。つまり、シンボルは共有ライブラリのロード時に直接解決されます。動的にロードされたライブラリの外部/外部にシンボルを表示するには、RTLD_GLOBAL=256にORフラグを付ける必要があります。

>>> import sys; import ctypes; 
>>> sys.setdlopenflags(sys.getdlopenflags()| ctypes.RTLD_GLOBAL) 
>>> import foo_wrapper 
>>> import test_lib 

、それは動作しますが、私たちのデバッグトレースを示しています

>>> symbol=source_func; lookup in file=./foo_wrapper.so [0] 
>>> file=./foo_wrapper.so [0]; needed by ./test_lib.so [0] (relocation dependency) 

もう一つの興味深い詳細:Pythonは輸入foo_wrapperを経由して二回モジュールをロードしないのでfoo_wrapper.soは、一度ロードされます。しかし、たとえそれが2回開かれても、メモリ内に1回だけ存在します(2回目の読取りでは共有ライブラリの参照カウントが増加します)。

しかし、今勝った洞察力で、我々はさらに行くことができる:

>>>> import sys; 
>>>> sys.setdlopenflags(1|256)#RTLD_LAZY+RTLD_GLOBAL 
>>>> import test_lib 
>>>> test_lib.do_it() 
>>>> ... it works! .... 

なぜこれを? RTLD_LAZYは、シンボルがロード時に直接ではなく、最初に使用されたときに解決されることを意味します。しかし、最初の使用(test_lib.do_it())の前にfoo_wrapperがロードされ(test_libモジュール内にインポートされます)、RTLD_GLOBALのために、後で解決するためにシンボルを使用することができます。

RTLD_GLOBALを使用しない場合、この場合、foo_wrapperの必要なシンボルがグローバルに表示されないため、test_lib.do_it()を呼び出すとエラーが発生します。thisを参照して、シングルトン:ちょうど両方のモジュールfoo_wrapperfoo.cppに対するtest_libをリンクするためにこのような素晴らしいアイデアではない理由を疑問に


+0

RE:「Everyone Everyoneだけが機能に直接アクセスできれば、これでラッパー全体が廃止されます」:これは私がやろうとしていることです。 pyとcythonの一般的なケースのcdefedクラスですが、必要に応じてcythonのコアlib関数を使用することができます。 IMHOは有効なユースケースです。 – SleepProgger

+0

私はこのようにtbhをしなければならないと少し苛立ちます。 Cythonは私の 'foo.c'コードを組み込みますが、なぜ私はcdefed関数(pxdで宣言されています - > .hに変換され、pyxで定義された - > .cファイルに変換される) 。基本的にまったく同じシナリオ、または何が私はここで行方不明ですか? – SleepProgger

+0

@SleepProggerはおそらく「廃止」であり、「muddy design」はあまりにも強い言語であるため、ラッパーを介して関数を呼び出す際にオーバーヘッドが発生し、直接Cコールが必要な場合があります。 – ead

関連する問題