C言語からXXを使う
ダーク・マターなら、宇宙に存在する暗黒物質って事で、現在宇宙物理学者達が血眼になって 探してるものだ。人間の知りたい欲望の極みになるかな。夢が有る話。
じゃ、『ダーク・マネー』はどうだ? 暗い金。それってマネロンの類の話? 俄かに 競馬で当てる方法やら株で儲ける方法を、この間からやってる機械学習の成果として 見出した。それで、先回りして、金隠しの方法でも勉強しようっての?
そういう誤解を招くような事を書くと、酷税庁に目を付けられますって。ましてやこの本の 出版社が、東洋経済新報社となると尚更です。
だから違うって。コーク兄弟と言う富豪が居て、そいつの音頭で、政府をわが物にしようって いう陰謀が企てられているのを暴いた本。
コークと言うと、あの砂糖水(を売ってて面白いかねとジョブズに言われた)の会社ではない。 第二次世界大戦前、石油精製の方法を発明したけど、政府と取り巻きの石油のメジャー会社に 発明を潰され、その技術を旧ソ連やヒトラー帝国に捧げ、大儲けしてのし上がってきた、コーク 一族。
2009年、民主党の小浜が就任式をやってる時、カリフォルニアの某高級リゾート地に、密に 大富豪達が集まってきた。どうやって、小浜に対抗しようかの相談。
富豪は持ってる金を有効に使って、金が金を生むようにしたい。それには、ごちゃごちゃ言う 政府を黙らせたい。ひそかに進む計画。
600ページ近い大著で、読み始めたばかりだけど、面白そう。 あれ、トランプって共和党だよな。おまけに大富豪。これって、陰謀が成功したって事かな。 富豪が儲かり、平民が搾取される国に作り替えられるのかな。
とんでもない失態
前回、自前でRndを書いた。その時シャッフルも含めたけど、それに文字列を渡すと エラーになった。その時は軽く考えていたけど、もう一度考えを整理しとく。
>>> s = "abcdefghijk" >>> s[3] = "D" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment >>> t = s[:] >>> t[3] = "D" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment >>> u = list(s) >>> u[3] = "D"
文字列は書き換え不能。だから、定義したsの一部を変更しようとすると、そんな事は サポートしてないと窘められる。ならば、コピーを取ればってんで、スライスtを作ってみた。 それでも、元が文字列なんで、やはりエラー。
ならば、文字列を分解しちゃえってんで、listにする。これでようやくOKになった。 listを思い出す前に、splitだろうとあたりを付けたけど、この場合には使えない。
>>> hex(id(s)) '0x7fe06b5c59e0' >>> hex(id(t)) '0x7fe06b5c59e0' >>> hex(id(u)) '0x7fe06ac46598'
スライスを作っても、同じ所を指しているのね。なお、部分的にスライスしてくると、そのidは 変わるけど、文字列って いう属性は継承されるので、やはり書き換え不可でした。(当たり前な事が理解出来たな)
>>> z = s[2:4] >>> hex(id(z)) '0x7fe06ac3cda8' >>> z 'cd' >>> z[1] = "D" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment
簡易クラスブラウザー
を一行コマンドで作ってみた。最初、pythonの事はpythonに任せよう。良いreの勉強になるかと 思っていたんだけど、そんなの一行野郎で十分だろうと思って、下記に落ち着いた。志有る人は こんなに怠けていないで、ちゃんとしたのを作ってください。
[cent Lib]$ egrep '(^class|^ *def)' random.py class Random(_random.Random): def __init__(self, x=None): def seed(self, a=None, version=2): def getstate(self): def setstate(self, state): : class SystemRandom(Random): def random(self): def getrandbits(self, k): def seed(self, *args, **kwds): def _notimplemented(self, *args, **kwds): def _test_generator(n, func, args): def _test(N=2000):
idleに付いてたパイソン・クラス・ブラウザーと結果を比較してみたら、同じだった。こんな 簡単なのでOKなのね。さすがパイソン。偏な書き方が出来ないように工夫されてるな。
気を良くしたオイラーはエイリアスに登録しとこうと思った。名前どうしよう。そのまま頭文字を 拾うと、pcbか。これだとプリント板とか昔出てきたプロセス・コントロール・ブロックと被るな。それと、pdbとも似てるし。。。
alias cb="egrep '(^class|^ *def)'"
結局、cbなんていう無難な名前で登録したよ。(cdと良く似てるじゃんてのは、却下です。)一応、どういう風に登録されたか確認。
[ob: ~]$ alias alias cb='egrep '\''(^class|^ *def)'\''' :
なんか、大変苦労されてました。
gaucheの場合
とあるページを見ていたら、 C言語からGaucheを使おう! (10) まとめなんてのに出会った。昔々に来た事が有った ような気がするけど、検証してみる。gaucheを入れてるのは、CentOSとFreeBSDだったりする ので、まずはCentOSで実験。
[cent tmp]$ make gcc -std=gnu99 -Ofast -Wall -Werror -o a.out `gauche-config -I` main.c `gauche-config -L` `gauche-config -l` [cent tmp]$ ./a.out ./a.out: error while loading shared libraries: libgauche-0.9.so.0.5: cannot open shared object file: No such file or directory
あれ、失敗したぞ。そんな馬鹿な! 気を取り直して、どんなになってるか確認。
[cent tmp]$ ldd ./a.out linux-vdso.so.1 => (0x00007ffe1342d000) libgauche-0.9.so.0.5 => not found libdl.so.2 => /lib64/libdl.so.2 (0x00007f0046bbf000) libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f0046988000) libutil.so.1 => /lib64/libutil.so.1 (0x00007f0046785000) librt.so.1 => /lib64/librt.so.1 (0x00007f004657c000) libm.so.6 => /lib64/libm.so.6 (0x00007f004627a000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f004605e000) libc.so.6 => /lib64/libc.so.6 (0x00007f0045c9c000) /lib64/ld-linux-x86-64.so.2 (0x00007f0046dd0000) libfreebl3.so => /lib64/libfreebl3.so (0x00007f0045a99000)
やっぱり無いと言ってる。でもgoshを入れた時はちゃんと動いていたぞ。
[cent tmp]$ ldd /usr/local/bin/gosh linux-vdso.so.1 => (0x00007ffcc7ef3000) libgauche-0.9.so.0.5 => /usr/local/lib/libgauche-0.9.so.0.5 (0x00007f4cb83cc000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f4cb81bc000) libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f4cb7f85000) libutil.so.1 => /lib64/libutil.so.1 (0x00007f4cb7d82000) librt.so.1 => /lib64/librt.so.1 (0x00007f4cb7b79000) libm.so.6 => /lib64/libm.so.6 (0x00007f4cb7877000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f4cb765b000) libc.so.6 => /lib64/libc.so.6 (0x00007f4cb7299000) /lib64/ld-linux-x86-64.so.2 (0x00007f4cb8b11000) libfreebl3.so => /lib64/libfreebl3.so (0x00007f4cb7096000)
こちらはちゃんと見つけているな。えーーーーと、長い経験で、こういう時は、
[cent tmp]$ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH [cent tmp]$ ./a.out 100
lddで確認しても、ちゃんと今度は見つけてくれている。もう一つ方法が有った事を思い出した。 /etc/ld.so.conf.d/の下に、適当なファイルを置き、その内容を/usr/local/libに設定。 それから、ldconfig -v とかやる方法。こちらの方がお勧めだったはず。
でも、goshの場合はちゃんとライブラリィーを見つけていたな。コンパイル時に魔法を かけていたのだろう。調べてみると、
とか
が、出てきた。そうか、調べてみる。
[cent tmp]$ readelf -a /usr/local/bin/gosh | grep /usr/local/lib 0x000000000000000f (RPATH) Library rpath: [/usr/local/lib] [cent tmp]$ readelf -a ./a.out | grep /usr/local/lib
そして、こちらは、gaucheをconfigureした時の、Makefile内容
[cent Gauche-0.9.5]$ find . -name Makefile | xargs grep rpath ./Makefile:# depend on gosh's rpath to point a valid version of libgauche.so. ./src/Makefile:RPATH_TMP = -Wl,--rpath=`pwd` ./src/Makefile:RPATH_REAL = -Wl,--rpath=$(LIB_INSTALL_DIR)
確かにrpathが埋め込まれているな。納得しましたよ。
今度は、FreeBSDでやってみる。
[fb11: gauche-c]$ make cc -std=gnu99 -Ofast -Wall -Werror -o a.out `gauche-config -I` `gauche-config -L` `gauche-config -l` /usr/lib/crt1.o: In function `_start': /usr/src/lib/csu/amd64/crt1.c:(.text+0x17b): undefined reference to `main' cc: error: linker command failed with exit code 1 (use -v to see invocation) *** Error code 1 Stop. make: stopped in /usr/home/sakae/src/gauche-c
ここにたどり着く前に、FreeBSDにはgccなんてのは無いと言われ、clangに切り替えたんですが まだ、何か有るようですよ。で、困った時には、gmakeってのを思い出したぞ。
[fb11: gauche-c]$ gmake cc -std=gnu99 -Ofast -Wall -Werror -o a.out `gauche-config -I` main.c `gauche-config -L` `gauche-config -l` [fb11: gauche-c]$ ./a.out 100
ちゃんと動いた。これは、makeの非互換性に入れておけば良いのかしらね? 分類に困る エラーだな。一見、リンカーのエラーのようにも見えるんだもん。
何にせよ、BSDは見捨てられる方向に動いているのかな。
[fb11: bin]$ readelf -a gosh | grep /usr/local 0x000000000000000f RPATH Library rpath: [/usr/local/lib] 0x000000000000001d RUNPATH Library runpath: [/usr/local/lib]
これも互換性の確保の為? 誰かに聞いてみたいぞ。
pythonの場合
gaucheだけでは片手落ちだと思うので、今度はC語からpythonを呼び出してみる。こういう 無駄?な事も、きちんと解説ページが用意されてるのが、おもてなしのpython流。
これだけではあれなので、市中に例を求めてみる。
C言語からpython3呼び出してHello Worldしてみた
Makefileは、上のgaucheのものを流用。こういうのをMakefileの継承とか言うんでしょうか? そして、ターゲットを、まだ登場してないDebian様と世間から忘れ去られそうなOpenBSDに してみます。
アラブの男性は、妻を4人まで娶ることが出来るそうです。但し、平等に扱わなければなりません。体力と資金力が無いと無理。オイラーには無理、せめて出来るのは4種のOSを操るぐらいです。平等にね。
[debian tmp]$ cat Makefile SRC = main.c OUT = a.out INCLUDE_DIR = `python3.6-config --cflags` LIBRARY_DIR = `python3.6-config --ldflags` $(OUT): $(SRC) cc -std=gnu99 -o $(OUT) $(INCLUDE_DIR) $^ $(LIBRARY_DIR) run: $(OUT) @./$(OUT) clean: rm -f $(OUT)
これは有り難くコピペしました。記して感謝致します。
[debian tmp]$ cat main.c #include <stdio.h> #include "Python.h" int main() { printf("C: Hello World!\n"); Py_Initialize(); PyRun_SimpleString("print('Python: Hello World!!')"); return 0; }
[debian tmp]$ make cc -std=gnu99 -o a.out `python3.6-config --cflags` main.c `python3.6-config --ldflags` main.c:3:5: warning: function declaration isn’t a prototype [-Wstrict-prototypes] int main() { ^ [debian tmp]$ ./a.out ./a.out: error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory
出ました! エラーが。軽くエラー除けしましょ。何が欲しいか、聞いてみる。
[debian tmp]$ python3.6-config --ldflags -L/home/sakae/conda3/lib -lpython3.6m -lpthread -ldl -lutil -lrt -lm -Xlinker -export-dynamic [debian tmp]$ export LD_LIBRARY_PATH=/home/sakae/conda3/lib:$LD_LIBRARY_PATH [debian tmp]$ ./a.out C: Hello World! Python: Hello World!!
手抜きの方法で、取り合えず動かした。で、python本体は、魔法がかかって、RPATHでも 設定してあるのかな?
[debian tmp]$ which python3.6 /home/sakae/conda3/bin/python3.6 [debian tmp]$ readelf -a /home/sakae/conda3/bin/python3.6 | grep home/sakae
そんなものは埋め込まれていませんねぇ。そもそも、anaconda提供なんで、本体が何処に 置かれるなんて分かりませんから、埋め込みは無理な相談。
分かるのは、起動したpythonの絶対PATH。このbinの所から、../lib を見つけ出して いるんだろうね。ちょいと調べてみる。
[debian tmp]$ ldd /home/sakae/conda3/bin/python3.6 linux-vdso.so.1 (0x00007ffe317ae000) libpython3.6m.so.1.0 => /home/sakae/conda3/bin/../lib/libpython3.6m.so.1.0 (0x00007fd8e59eb000) :
やっぱりね。そうだと思った。
なお、出来上がった a.outのファイルサイズは、約11kぐらい。やはり、libpythonはリンク されてた。だから小さい容量で済むんだな。
[debian tmp]$ ldd a.out linux-vdso.so.1 (0x00007ffceb385000) libpython3.6m.so.1.0 => /home/sakae/conda3/lib/libpython3.6m.so.1.0 (0x00007faac8a7e000) :
本体のpythonと、派生品のa.outで、libpythonの指示方法が微妙に違ってて面白いな。
debianにも自前でコンパイルしたdebug付きの pythonが入っている。それでコンパイルすると、libpythonは自前で抱えこんだものになった。 おかげで、出来上がったa.outは11Mを越えていたぞ。(多分、デバッグシンボルが大量に 入っているんでしょうけどね。)
そんじゃ、4人目の妻、じゃなかった、OpenBSDの元を訪れて機嫌を伺ってみます。
[ob: c-py]$ make cc -o a.out `python3.6-config --cflags` `python3.6-config --ldflags` /usr/lib/crt0.o: In function `_start': (.text+0x5e): undefined reference to `main' collect2: ld returned 1 exit status *** Error 1 in /home/sakae/c-py (Makefile:7 'a.out')
FreeBSDと同じエラーになった。これぞBSDグループ。GNUに頑なに抵抗していますよ。
[ob: c-py]$ gmake cc -o a.out `python3.6-config --cflags` main.c `python3.6-config --ldflags` main.c:3: warning: function declaration isn't a prototype /home/sakae/MINE/lib/python3.6/config-3.6dm/libpython3.6dm.a(bytesobject.o): In function `PyBytes_FromFormatV': ../Objects/bytesobject.c:271: warning: warning: sprintf() is often misused, please use snprintf() /home/sakae/MINE/lib/python3.6/config-3.6dm/libpython3.6dm.a(traceback.o): In function `_Py_FindSourceFile': ../Python/traceback.c:238: warning: warning: strcpy() is almost always misused, please use strlcpy() /home/sakae/MINE/lib/python3.6/config-3.6dm/libpython3.6dm.a(getpath.o): In function `ismodule': ../Modules/getpath.c:163: warning: warning: wcscat() is almost always misused, please use wcslcat() /home/sakae/MINE/lib/python3.6/config-3.6dm/libpython3.6dm.a(getpath.o): In function `copy_absolute': ../Modules/getpath.c:232: warning: warning: wcscpy() is almost always misused, please use wcslcpy()
gnuのmakeを使うと無事にコンパイル出来た。非互換性は、オイラーの知らないものかと思って、 "$^" を、"$(SRC)" に変更したら、互換になった。これって依存ファイルのリストを意味する らしいぞ。
[ob: c-py]$ ./a.out C: Hello World! Python: Hello World!! [ob: c-py]$ file a.out a.out: ELF 64-bit LSB shared object, x86-64, version 1 [ob: c-py]$ ls -l a.out -rwxr-xr-x 1 sakae sakae 7767564 Apr 10 07:48 a.out*
コードサイズがやけに大きいな。ひょっとしてpythonなんとかを抱え込んでいないかな?
[ob: c-py]$ ldd ./a.out ./a.out: Start End Type Open Ref GrpRef Name 0000030cb6300000 0000030cb6b32000 exe 2 0 0 ./a.out 0000030f04361000 0000030f0476f000 rlib 0 1 0 /usr/lib/libpthread.so.22.0 0000030fa207b000 0000030fa2487000 rlib 0 1 0 /usr/lib/libutil.so.12.1 0000030ede664000 0000030edea8c000 rlib 0 1 0 /usr/lib/libm.so.9.0 0000030f6ac31000 0000030f6b0fb000 rlib 0 1 0 /usr/lib/libc.so.88.0 0000030f81300000 0000030f81300000 rtld 0 1 0 /usr/libexec/ld.so
やっぱりリンクしてないね。
[ob: c-py]$ python3.6-config --ldflags -L/home/sakae/MINE/lib/python3.6/config-3.6dm -L/home/sakae/MINE/lib -lpython3.6dm -lpthread -lutil -lm -Wl,--export-dynamic
で、--export-dynamicの用法を調べてみると、全てのシンボルを動的シンボルテーブルに加えるものらしい。これは、dlopenする時に必要になるとな。
じゃ、証拠を掴みましょう。リナだとstraceとかするんだろうけど、BSDしかもOpenBSDでは
[ob: c-py]$ ktrace ./a.out C: Hello World! Python: Hello World!!
どんなファイルの名前解決を欲してるか、心のうちを推し量ってみます。これは、まあ、ほのかな期待なんでしょうかね。(そんなの、システムコールのopenなりを見ろってのが、リナ陣営の 言い分でしょうが、OpenBSDのopenは、セキュリティ上の理由からか、単なる数値データしか 返ってきません)
[ob: c-py]$ kdump | grep NAMI 13389 ktrace NAMI "./a.out" 13389 a.out NAMI "/usr/libexec/ld.so" 13389 a.out NAMI "/var/run/ld.so.hints" 13389 a.out NAMI "/usr/lib/libc.so.88.0" : 13389 a.out NAMI "/home/sakae/MINE/bin/python3.6" : 13389 a.out NAMI "/home/sakae/MINE/lib/python3.6/lib-dynload" :
gdbでdlopenに網を張ってみたけど、引っかからず。素直にopenを見ていく。
(gdb) b open Breakpoint 1 at 0x53310 (gdb) r Starting program: /home/sakae/c-py/a.out C: Hello World! Breakpoint 1, *_libc_open_cancel ( path=0x7f7ffffee590 "/usr/share/locale/UTF-8/LC_CTYPE", flags=65536) at /usr/src/lib/libc/sys/w_open.c:24 24 { (gdb) bt #0 *_libc_open_cancel ( path=0x7f7ffffee590 "/usr/share/locale/UTF-8/LC_CTYPE", flags=65536) at /usr/src/lib/libc/sys/w_open.c:24 #1 0x000013a282e79b62 in *_libc_fopen ( file=0x7f7ffffee590 "/usr/share/locale/UTF-8/LC_CTYPE", mode=<optimized out>) at /usr/src/lib/libc/stdio/fopen.c:54 #2 0x000013a282e78800 in _newrunelocale ( path=0x7f7ffffee590 "/usr/share/locale/UTF-8/LC_CTYPE") at /usr/src/lib/libc/locale/setrunelocale.c:140 #3 0x000013a282e789be in _xpg4_setrunelocale (locname=<optimized out>) at /usr/src/lib/libc/locale/setrunelocale.c:203 #4 0x000013a282e6eebc in load_locale_sub (locname=<optimized out>, category=<optimized out>) at /usr/src/lib/libc/locale/setlocale.c:237 #5 loadlocale (category=<optimized out>, locname=0x7f7ffffeea40 "ja_JP.UTF-8") at /usr/src/lib/libc/locale/setlocale.c:261 #6 0x000013a282e6f20c in *_libc_setlocale (category=2, locale=0x139f9d9042b0 "") at /usr/src/lib/libc/locale/setlocale.c:152 #7 0x0000139f9d553d7c in _Py_InitializeEx_Private (install_sigs=1, install_importlib=1) at ../Python/pylifecycle.c:322 #8 0x0000139f9d554426 in Py_InitializeEx (install_sigs=1) at ../Python/pylifecycle.c:474 #9 0x0000139f9d55443a in Py_Initialize () at ../Python/pylifecycle.c:480 #10 0x0000139f9d55367d in main () at main.c:5
イニシャライザーの中で呼んでますねえ。これは、もう少し丁寧に追った方が良さそうですよ。
そして、こんなのも有った。 pythonのモジュールをC言語から使うを試してみる。
#include <stdio.h> #include "Python.h" int main() { PyObject *pModule, *pTmp; char *sTmp; Py_Initialize(); pModule = PyImport_ImportModule("hoge"); pTmp = PyObject_CallMethod(pModule, "foo", NULL); PyArg_Parse(pTmp, "s", &sTmp); printf("%s\n", sTmp); Py_Finalize(); return 0; }
[ob: tmp]$ cat hoge.py def foo(): print('this printed by python') return 'byt, this printed by C'
[ob: tmp]$ ./a.out Segmentation fault (コアダンプ)
ありゃりゃ。こういう時は、セカンドオピニオンしてみましょ。一穴主義じゃなくて博愛精神ですよ。 (田中)角栄研究によると、妾だか2号さんだか、側室さんだかがいたそうな。おとーちゃん、おとーちゃんと慕われていたとか。
CentOSに行ってみる。
[cent tmp]$ ./a.out Segmentation fault (core dumped) [cent tmp]$ python -q >>> import hoge >>> hoge.foo() this printed by python 'byt, this printed by C'
用意したモジュールは正常。ならば、gdbさんに賭けるしか。
[cent tmp]$ gdb -q a.out Reading symbols from a.out...done. (gdb) b main Breakpoint 1 at 0x41ce1d: file main.c, line 4. (gdb) r Starting program: /tmp/a.out [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Breakpoint 1, main () at main.c:4 4 int main() { (gdb) n 8 Py_Initialize(); (gdb) n 9 pModule = PyImport_ImportModule("hoge"); (gdb) n 10 pTmp = PyObject_CallMethod(pModule, "foo", NULL); (gdb) p pModule $1 = (PyObject *) 0x0 (gdb) n 11 PyArg_Parse(pTmp, "s", &sTmp); (gdb) n 12 printf("%s\n", sTmp); (gdb) p sTmp $1 = 0x0 (gdb) n Program received signal SIGSEGV, Segmentation fault. 0x00007ffff717db71 in __strlen_sse2 () from /lib64/libc.so.6
作者さんも言ってたけど、チェックを省いた弊害が如実に出てますなあ。モジュールの読み込みに失敗したって事。長い長い旅が始まりそうですよ。
その前に、上記をやってみた。
[cent tmp]$ cat multiply def multiply(a,b): print("Will compute", a, "times", b) c = 0 for i in range(0, a): c = c + b return c [cent tmp]$ ./a.out multiply multiply 3 2 ModuleNotFoundError: No module named 'multiply' Failed to load "multiply"
やはり、エラーだよ。gdbで追ってみても、PyImport_Importが、NULLを返してくる。 普通のPythonからは、ちゃんと実行出来るんだけど。
[cent tmp]$ python -i multiply >>> multiply(3,5) Will compute 3 times 5 15
解決策見つけた。こういう穴が有るとは知らなかった。
[cent tmp]$ PYTHONPATH='.' ./a.out multiply multiply 3 2 Will compute 3 times 2 Result of call: 6
まあ、分かってしまえば極当たり前の事。C語のアプリと言えどもpython呼んでるんだから、 パスに倣うと言う事か。
C語の中で、イニシャライズが終わった後に
PyObject* sysPath = PySys_GetObject("path"); PyObject* dir = PyUnicode_DecodeFSDefault("."); PyList_Append(sysPath, dir);
を入れると良いらしい。
etc
これで、やたらアンダーバーが多いの克服出来るかな?
冒頭でさぼったアレのpython流解決方法。