天天看點

[翻譯][php擴充開發和嵌入式]第17章-php源代碼的配置和連結

全部翻譯内容pdf文檔下載下傳位址: http://download.csdn.net/detail/lgg201/5107012

本書目前在github上由laruence(http://www.laruence.com)和walu(http://www.walu.cc)兩位大牛組織翻譯. 該翻譯項目位址為: https://github.com/walu/phpbook

本書在github上的位址: https://github.com/goosman-lei/php-eae

未來本書将可能部分合并到phpbook項目中, 同時保留一份獨立版本.

原書名: <Extending and Embedding PHP>

原作者: Sara Golemon

譯者: goosman.lei(雷果國)

譯者Email: [email protected]

譯者Blog: http://blog.csdn.net/lgg201

權利聲明

此譯本在不獲利的情況下, 可以無限制自由傳播.

配置和連結

所有前面示例中的代碼, 都是你曾經在php使用者空間編寫過代碼的C語言的獨立版本. 如果你做的項目需要和php擴充進行粘合, 那麼你就至少需要連結一個外部庫.

autoconf

在一個簡單的應用中, 你可能已經在你的Makefile中增加了下面這樣的CFLAGS和LDFLAGS.

CFLAGS = ${CFLAGS} -I/usr/local/foobar/include
LDFLAGS = ${LDFLAGS} -lfoobar -L/usr/local/foobar/lib
           

想要建構你的應用卻沒有libfoobar的人, 或将libfoobar安裝到其他位置的人, 将會得到一個處理過的錯誤消息, 用于幫助他找到錯誤原因.

在過去十年開發的多數開發源代碼軟體(OSS)以及PHP都利用了一個實用工具autoconf, 通過一些簡單的宏來生成複雜的configure腳本. 這個産生的腳本會執行查找依賴庫已經頭檔案是否安裝的工作. 基于這些資訊, 一個包可以自定義建構代碼行, 或在編譯的時間被浪費之前提供一個有意義的錯誤消息.

在建構php擴充時, 無論你是否計劃公開釋出, 都需要利用這個autoconf機制. 即便你對autoconf已經很熟悉了, 也請花幾分鐘時間閱讀本章, php中引入了一些一般安裝的autoconf沒有的自定義宏.

和傳統的autoconf步驟(集中的configure.in檔案包含了包的所有配置宏)不同, php隻是用configure.in管理許多位域源碼樹下小的config.m4腳本的協調, 包括各個擴充, SAPI, 核心自身, 以及ZendEngine.

你已經在前面的章節看到了一個簡單版本的config.m4. 接下來, 我們将在這個檔案中增加其他的autoconf文法, 讓你的擴充可以收集到更多的配置時資訊.

庫的查找

config.m4腳本最多是用于檢查依賴庫是否已安裝. 擴充比如mysql, ldap, gmp以及其他設計為php使用者空間和c庫實作的其他功能之間的粘合層的擴充. 如果它們的依賴庫沒有安裝, 或者安裝的版本太舊, 要麼會編譯錯誤, 要麼會導緻産生的二進制無法運作.

頭檔案掃描

對依賴庫掃描中最簡單的一步就是檢查你的腳本中的包含檔案, 它們将在連結時使用. 下面的代碼嘗試在一些常見位置查找zlib.h:

PHP_ARG_WITH(zlib,[for zlib Support]
[  with-zlib              Include ZLIB Support])

if test "$PHP_ZLIB" != "no"; then
  for i in /usr /usr/local /opt; do
    if test -f $i/include/zlib/zlib.h; then
      ZLIB_DIR=$i
    fi
  done

  if test -z "$ZLIB_DIR"; then
    AC_MSG_ERROR([zlib not installed (http://www.zlib.org)])
  fi

  PHP_ADD_LIBRARY_WITH_PATH(z,$ZLIB_DIR/lib, ZLIB_SHARED_LIBADD)
  PHP_ADD_INCLUDE($ZLIB_DIR/include)

  AC_MSG_RESULT([found in $ZLIB_DIR])
  AC_DEFINE(HAVE_ZLIB,1,[libz found and included])

  PHP_NEW_EXTENSION(zlib, zlib.c, $ext_shared)
  PHP_SUBST(ZLIB_SHARED_LIBADD)
fi
           

config.m4檔案很明顯比你迄今為止使用的要大. 幸運的是, 它的文法非常的簡單易懂并且如果你熟悉bash腳本, 對它也就不會陌生.

檔案和第5章"你的第一個擴充"中第一次出現的一樣, 都是以PHP_ARG_WITH()宏開始. 這個宏的行為和你用過的PHP_ARG_ENABLE()宏一樣, 不過它将導緻./configure時的選項是--with-extname/--without-extname而不再是--enable-extname/--disable-extname.

回顧這些宏, 它們的功能是等同的, 不同僅在于是否讓終端使用者給你的包一些暗示. 你可以在自己建立的私有擴充上使用任意一種方式. 不過, 如果你計劃公開釋出, 那就應該知道php正式的編碼标準, 它指出enable/disable用于哪些不需要連結外部庫的擴充, with/without則反之.

由于我們這裡假設的擴充将連結zlib庫, 是以你的config.m4腳本要以查找擴充源代碼中将包含的zlib.h頭檔案. 這通過檢查一些标準位置/usr, /usr/local, /opt中include/zlib目錄下的zlib.h完成對其下兩個目錄的定位. 

如果找到了zlib.h, 則将基路徑設定到臨時變量ZLIB_DIR中. 一旦循環完成, config.m4腳本檢查ZLIB_DIR是否包含内容來确定是否找到了zlib.h. 如果沒有找到, 則産生一個有意義的錯誤讓使用者知道./configure不能繼續.

此刻, 腳本假設頭檔案存在, 對應的庫也必須存在, 是以在下面的兩行使用它修改建構環境, 最終增加-lz -L$ZLIB_DIR/lib到LDFLAGS以及-I$ZLIB_DIR/include到CFLAGS.

最終, 輸出一個确認消息訓示zlib安裝已經找到, 并且在編譯期間使用它的路徑. config.m4的其他部分從前面部分的學習中你應該已經熟悉了. 為config.h定義一個#define, 定義擴充并指定它的源代碼檔案, 同時辨別一個變量完成将擴充附加到建構系統的工作.

功能測試

迄今為止, 這個config.m4示例訓示查找了需要的頭檔案. 盡管這已經夠用了, 但它仍然不能確定産生的二進制正确的進行連結, 因為可能不存在比對的庫檔案, 或者版本不正确.

最簡單的檢查zlib.h對應的libz.so庫檔案是否存在的方式就是檢查檔案是否存在:

if ! test -f $ZLIB_DIR/lib/libz.so; then
  AC_MSG_ERROR([zlib.h found, but libz.so not present!])
fi
           

當然, 這僅僅是問題的一面. 如果安裝了其他的同名庫但和你要查找的庫不相容怎麼辦呢? 確定你的擴充可以成功編譯的最好方式是測試找到的庫實際編譯所需的内容. 要這樣做就需要在config.m4中PHP_ADD_LIBRARY_WITH_PATH調用之前加入下面代碼:

PHP_CHECK_LIBRARY(z, deflateInit,,[
  AC_MSG_ERROR([Invalid zlib extension, gzInit() not found])
],-L$ZLIB_DIR/lib)
           

這個工具宏将展開輸出一個完整的程式, ./configure将嘗試編譯它. 如果編譯成功, 表示第二個參數定義的符号在第一個參數指定的庫中存在. 成功後, 第三個參數中指定的autoconf腳本将會執行; 失敗後, 第四個參數中指定的autoconf腳本将執行. 在這個例子中, 第三個參數為空, 因為沒有消息就是最好的消息(譯注: 應該是unix哲學之一), 第五個參數也就是左後一個參數, 用于指定額外的編譯器和連結器标記, 這裡, 使用-L緻命了一個額外的用于查找庫的路徑.

可選功能

那麼現在你已經有正确的庫和頭檔案了, 但依賴的是所安裝庫的哪個版本呢? 你可能需要某些功能或排斥某些功能. 由于這種類型的變更通常涉及到某些特定入口點的增加或删除, 是以可以重用PHP_CHECK_LIBRARY()宏來檢查庫的某些能力.

PHP_CHECK_LIBRARY(z, gzgets,[
  AC_DEFINE(HAVE_ZLIB_GETS,1,[Having gzgets indicates zlib >= 1.0.9])
],[
  AC_MSG_WARN([zlib < 1.0.9 installed, gzgets() will not be available])
],-L$ZLIB_DIR/lib)
           

測試實際行為

可能知道某個符号存在也還不能確定你的代碼正确編譯; 某些庫的特定版本可能存在bug需要運作一些測試代碼進行檢查.

AC_TRY_RUN()宏可以編譯一個小的源代碼檔案為可執行程式并執行. 依賴于傳回給./configure的傳回代碼, 你的腳本可以設定可選的#define語句或直接輸出消息(比如如果bug導緻不能工作則提示更新)安全退出. 考慮下面的代碼(摘自ext/standard/config.m4):

AC_TRY_RUN([
#include <math.h>

double somefn(double n) {
  return floor(n*pow(10,2) + 0.5);
}
int main() {
  return somefn(0.045)/10.0 != 0.5;
}
],[
  PHP_ROUND_FUZZ=0.5
  AC_MSG_RESULT(yes)
],[
  PHP_ROUND_FUZZ=0.50000000001
  AC_MSG_RESULT(no)
],[
  PHP_ROUND_FUZZ=0.50000000001
  AC_MSG_RESULT(cross compile)
])
AC_DEFINE_UNQUOTED(PHP_ROUND_FUZZ, $PHP_ROUND_FUZZ,
                    [Is double precision imprecise?])
           

你可以看到, AC_TRY_RUN()的第一個參數是一塊C語言代碼, 它将被編譯執行. 如果這段代碼的退出代碼是0, 位于第二個參數的autoconf腳本将被執行, 這種情況辨別round()函數和期望一樣工作, 傳回0.5.

如果代碼塊傳回非0值, 位域第三個參數的autoconf腳本将被執行. 第四個參數(最後一個)在php交叉編譯時使用. 這種情況下, 嘗試運作示例代碼是沒有意義的, 因為目标平台不同于擴充編譯時使用的平台.

強制子產品依賴

在php 5.1中, 擴充之間的内部依賴是可以強制性的. 由于擴充可以靜态建構到php中, 也可以建構為共享對象動态加載, 是以強制依賴需要在兩個地方實作.

配置時子產品依賴

第一個位置是你在本章課程中剛剛看到的config.m4檔案中. 你可以使用PHP_ADD_EXTENSION_DEP(extname, depname[ , optional])宏辨別extname這個擴充依賴于depname這個擴充. 當extname以靜态方式建構到php中時, ./configure腳本将使用這一行代碼确認depname必須首先初始化. optional參數是一個标記, 用來辨別depname如果也是靜态建構的, 應該在extname之前加載, 不過它并不是必須的依賴.

這個宏的一個使用示例是pdo驅動, 比如pdo_mysql是可預知依賴于pdo擴充的:

ifdef([PHP_ADD_EXTENDION_DEP],
[
  PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo)
])
           

要注意PHP_ADD_EXTENSION_DEP()宏被包裹到一個ifdef()結構中. 這是因為pdo和它的驅動在編譯大于或等于5.0版本的php時都是存在的, 然而PHP_ADD_EXTENSION_DEP()宏是直到5.1.0版本才出現的.

運作時子產品依賴

另外一個你需要注冊依賴的地方是zend_module_entry結構體中. 考慮下面第5章中你定義的zend_module_entry結構體:

zend_module_entry sample_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_SAMPLE_EXTNAME,
    php_sample_functions,
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_SAMPLE_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};
           

增加運作時子產品依賴資訊就需要對STANDARD_MOUDLE_HEADER部分進行一些小修改:

zend_module_entry sample_module_entry = {
#if ZEND_MODULE_API_NO >= 220050617
    STANDARD_MODULE_HEADER_EX, NULL,
    php_sample_deps,
#elif ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_SAMPLE_EXTNAME,
    php_sample_functions,

    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_SAMPLE_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};
           

現在, 如果ZEND_MODULE_API_NO高于php 5.1.0 beta釋出版, 則STANDARD_MODULE_HEADER(譯注: 這裡原著筆誤為STANDARD_MODULE_PROPERTIES)将被替換為略微複雜的結構, 它将包含一個指向子產品依賴資訊的引用.

這個目标結構體可以在你的zend_module_entry結構體上面定義如下:

#if ZEND_MODULE_API_NO >= 220050617
static zend_module_dep php_sample_deps[] = {
    ZEND_MODULE_REQUIRED("zlib")
    {NULL,NULL,NULL}
};
#endif
           

和zend_function_entry向量類似, 這個清單可以有多項依賴, 按照順序進行檢查. 如果嘗試加載某個依賴子產品未滿足, Zend将會中斷加載, 報告不滿足依賴的名字, 這樣, 終端使用者就可以通過首先加載其他子產品來解決問題.

Windows方言

由于譯者對windows環境不熟悉, 是以略過本節.

小結

如果你的擴充将在未知或不可控制的環境建構, 讓它足夠聰明以應付奇怪的環境就非常重要. 使用php提供的unix和windows上強有力的腳本能力, 你應該可以檢測到麻煩并在未知的管理者需要電話求助之前給于她一個解決方案. 

現在你已經有使用php api從頭建立php擴充的基礎能力了, 你可以準備學習一下使用php提供的擴充開發工具把自己從繁重的重複勞動中解放出來了, 使用它們可以快速, 準确的建立新擴充的原型.

目錄 上一章: 有趣的流 下一章: php擴充的自動生成