4.外部ライブラリ

4.1.想定するシナリオ

 C のソースの中身に入っていく前に、ちょっと寄り道をします。PHP Extension を開発する必要性が生じるケースとして代表的な、外部ライブラリの利用をシナリオとして想定します。このために、別途ダミーの C ライブラリを用意しました。以下の手順でビルドしてください。

~/php/ext/my_ext$ cd
~$ git clone https://github.com/hotta/my_lib.git
~$ cd my_lib
~/my_lib$ make

 これにより、~/my_lib 配下に libmy_lib.so という共有ライブラリファイルが作られます。mylib_test という名前のテスト用プログラムも用意していますので、試しに使ってみてください。

~/my_lib$ ./mylib_test
my_echo_int(123)
my_echo_double(123.456000)
my_echo_string("hello world")
my_add_return_int(30, 40) = 70
my_add_return_str("hello", "world") = "helloworld"
~/my_lib$ sudo tail -1 /var/log/messages
Jun  7 11:45:14 php-reform MY_EXT: MY_LOGGER_TEST

 mylib_test コマンドは libmy_lib.so にある API 関数を一つずつ呼び出します。mylib_test のソース test.c は以下の通りです。

my_lib/test/test.c:

#include  <stdio.h>
#include  <malloc.h>
#include  "my_lib.h"

#define SYSLOG_MSG  "MY_LOGGER_TEST"

int main(int argc, char **argv)
{
  my_echo_int(123);
  my_echo_double(123.456);
  my_echo_string("hello world");
  printf("my_add_return_int(30, 40) = %d\n", my_add_return_int(30, 40));

  char *add;
  add = my_add_return_str("hello", "world");
  printf("my_add_return_str(\"hello\", \"world\") = \"%s\"\n", add);
  free(add);

  my_logger(SYSLOG_MSG);
  return  0;
}

 my_ で始まる関数の実体は components ディレクトリの中にあります。C から呼ぶ場合は上記の通りですが、今回はこれらの関数を PHP から呼ぶための PHP Extension を作成してみます。

4.2.ライブラリの構成

 C で書かれた外部ライブラリの機能を呼び出すためには、以下のことが前提となります。

  • 参照したい共有ライブラリが、外部に関数名を公開(エクスポート)していること。
  • 共有ライブラリが公開している関数のインターフェイス(呼び出し方法)が、C のヘッダファイルとして提供されていること。

 ライブラリが公開している関数シンボルは、objdump コマンドで確認できます。

~/my_lib$ objdump -T libmy_lib.so

libmy_lib.so:     ファイル形式 elf64-x86-64

DYNAMIC SYMBOL TABLE:
00000000000007b0 l    d  .init  0000000000000000              .init
0000000000000000  w   D  *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 strcpy
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 strlen
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 printf
0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 malloc
0000000000000000  w   D  *UND*  0000000000000000              _Jv_RegisterClasses
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 vsyslog
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 openlog
0000000000000000  w   D  *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000  w   DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_finalize
0000000000201058 g    D  .got.plt       0000000000000000  Base        _edata
0000000000201060 g    D  .bss   0000000000000000  Base        _end
0000000000000995 g    DF .text  0000000000000026  Base        my_echo_string
0000000000000a80 g    DF .text  00000000000000cd  Base        my_logger
00000000000009ed g    DF .text  0000000000000014  Base        my_add_return_int
00000000000009bb g    DF .text  0000000000000032  Base        my_add_int
0000000000000945 g    DF .text  0000000000000023  Base        my_echo_int
0000000000201058 g    D  .bss   0000000000000000  Base        __bss_start
00000000000007b0 g    DF .init  0000000000000000  Base        _init
0000000000000b50 g    DF .fini  0000000000000000  Base        _fini
0000000000000968 g    DF .text  000000000000002d  Base        my_echo_double
0000000000000a01 g    DF .text  000000000000007f  Base        my_add_return_str

 第2カラムが g になっているのが公開されているグルーバルシンボルです。また第4カラムが .text になっているのは、このシンボルがプログラム部分にある(≒関数名である)ことを示します。test.c で呼び出している関数群が公開されているのがわかります。

 ちなみに第4カラムが *UND* (undefined) のものは、この(バイナリの)中では定義されていない外部シンボルへの参照を表しており、GLIBC に含まれる strcpy(3) や printf(3) といった関数が内部で使われ(外部参照され)ているのがわかります。

 なお、関数のインターフェイスを提示するヘッダファイルの中身は、以下のようになっています。

~/my_lib$ cat my_lib.h
extern  void  my_echo_int(int arg);
extern  void  my_echo_double(double arg);
extern  void  my_echo_string(char *arg);
extern  void  my_add_int(int a, int b);
extern  int   my_add_return_int(int a, int b);
extern  char *my_add_return_str(char *a, char *b);
extern  void  my_logger(const char *fmt, ...);

4.3.外部ライブラリへの依存を追加

 PHP のソースツリーから見ると ~/my_lib/{my_lib.h,libmy_lib.so} は知らない存在なので、これをコンパイラやリンカに教えてやる必要があります。config.m4 を以下のように修正します。

~/php/ext/my_ext$ diff /tmp/config.m4 config.m4
43,44c43,45
<   dnl # --with-my_ext -> add include path
<   dnl PHP_ADD_INCLUDE($MY_EXT_DIR/include)
---
>   MY_LIB_DIR=/home/vagrant/my_lib
>   INCLUDE_DIR=$MY_LIB_DIR
>   PHP_LIBDIR=$MY_LIB_DIR
46,48c47,51
<   dnl # --with-my_ext -> check for lib and symbol presence
<   dnl LIBNAME=my_ext # you may want to change this
<   dnl LIBSYMBOL=my_ext # you most likely want to change this
---
>   AC_CHECK_HEADER([$INCLUDE_DIR/my_lib.h],
>     [],
>     [AC_MSG_ERROR(["$INCLUDE_DIR/my_lib.h" が見つかりません])]
>   )
>   PHP_ADD_INCLUDE($INCLUDE_DIR)
50,60c53,70
<   dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
<   dnl [
<   dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $MY_EXT_DIR/$PHP_LIBDIR, MY_EXT_SHARED_LIBADD)
<   dnl   AC_DEFINE(HAVE_MY_EXTLIB,1,[ ])
<   dnl ],[
<   dnl   AC_MSG_ERROR([wrong my_ext lib version or lib not found])
<   dnl ],[
<   dnl   -L$MY_EXT_DIR/$PHP_LIBDIR -lm
<   dnl ])
<   dnl
<   dnl PHP_SUBST(MY_EXT_SHARED_LIBADD)
---
>   # --with-my_ext -> add include path
>   PHP_ADD_INCLUDE($MY_EXT_DIR/include)
>
>   # --with-my_ext -> check for lib and symbol presence
>   LIBNAME=my_lib
>   LIBSYMBOL=my_echo_int
>
>   PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
>   [
>     PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $MY_EXT_DIR/$PHP_LIBDIR, MY_EXT_SHARED_LIBADD)
>     AC_DEFINE(HAVE_MY_EXTLIB,1,[ ])
>   ],[
>     AC_MSG_ERROR([libmy_lib.so が見つからないか、バージョンが誤っています])
>   ],[
>     -L$MY_EXT_DIR/$PHP_LIBDIR -lm
>   ])
>
>   PHP_SUBST(MY_EXT_SHARED_LIBADD)

 前半の変更は my_lib.h を見つけるためです。後半の変更は、libmy_lib.so を見つけ、その中でさらに my_echo_int 関数の存在を確認しています。[1]

 再度 phpize からやり直します。

~/php/ext/my_ext$ phpize
Configuring for:
PHP Api Version:         20160303
Zend Module Api No:      20160303
Zend Extension Api No:   320160303
~/php/ext/my_ext$ ./configure --enable-my_ext
(中略)
configure: creating ./config.status
config.status: creating config.h

 これで作成された config.h は、my_ext.c でインクルードして使用します。

[1]ここで使われているマクロ群は、PHP 公式マニュアルの UNIX 用のビルドシステム: config.m4 に一部記載があります。ここに記載のない AC_CHECK_HEADER などは、 GNU Autoconf/Automake/Libtool(でびあんぐる監訳) が詳しいです。