# 庫的查找
config.m4腳本最多是用于檢查依賴庫是否已安裝. 擴展比如mysql, ldap, gmp以及其他設計為php用戶空間和c庫實現的其他功能之間的粘合層的擴展. 如果它們的依賴庫沒有安裝, 或者安裝的版本太舊, 要么會編譯錯誤, 要么會導致產生的二進制無法運行.
## 頭文件掃描
對依賴庫掃描中最簡單的一步就是檢查你的腳本中的包含文件, 它們將在鏈接時使用. 下面的代碼嘗試在一些常見位置查找zlib.h:
```c
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庫文件是否存在的方式就是檢查文件是否存在:
```c
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調用之前加入下面代碼:
```c
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()宏來檢查庫的某些能力.
```c
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):
```c
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交叉編譯時使用. 這種情況下, 嘗試運行示例代碼是沒有意義的, 因為目標平臺不同于擴展編譯時使用的平臺.
## links
* [目錄](<preface.md>)
* 17.1 [autoconf](<17.1.md>)
* 17.3 [強制模塊依賴](<17.3.md>)
- about
- 開始閱讀
- 目錄
- 1 PHP的生命周期
- 1.讓我們從SAPI開始
- 2.PHP的啟動與終止
- 3.PHP的生命周期
- 4.線程安全
- 5.小結
- 2 PHP變量在內核中的實現
- 1. 變量的類型
- 2. 變量的值
- 3. 創建PHP變量
- 4. 變量的存儲方式
- 5. 變量的檢索
- 6. 類型轉換
- 7. 小結
- 3 內存管理
- 1. 內存管理
- 2. 引用計數
- 3. 總結
- 4 動手編譯PHP
- 1. 編譯前的準備
- 2. PHP編譯前的config配置
- 3. Unix/Linux平臺下的編譯
- 4. 在Win32平臺上編譯PHP
- 5. 小結
- 5 Your First Extension
- 1. 一個擴展的基本結構
- 2. 編譯我們的擴展
- 3. 靜態編譯
- 4. 編寫函數
- 5. 小結
- 6 函數返回值
- 1. 一個特殊的參數:return_value
- 2. 引用與函數的執行結果
- 3. 小結
- 7 函數的參數
- 1. zend_parse_parameters
- 2. Arg Info 與類型綁定
- 3. 小結
- 8 使用HashTable與{數組}
- 1. 數組(C中的)與鏈表
- 2. 操作HashTable的API
- 3. 在內核中操作PHP語言中數組
- 4. 小結
- 9 PHP中的資源類型
- 1. 復合類型的數據——{資源}
- 2. Persistent Resources
- 3. {資源}自有的引用計數
- 4. 小結
- 10 PHP中的面向對象(一)
- 1. zend_class_entry
- 2. 定義一個類
- 3. 定義一個接口
- 4. 類的繼承與接口的實現
- 5. 小結
- 11 PHP中的面向對象(二)
- 1. 生成對象的實例與調用方法
- 2. 讀寫對象的屬性
- 3. 小結
- 12 啟動與終止的那點事
- 2. 小結
- 1. 關于生命周期
- 2. MINFO與phpinfo
- 3. 常量
- 4. PHP擴展中的全局變量
- 5. PHP語言中的超級全局變量
- 6. 小結
- 13 INI設置
- 1. 聲明和訪問ini設置
- 2. 小結
- 2. 小結
- 14 流式訪問
- 1. 概覽
- 2. 打開流
- 3. 訪問流
- 4. 靜態資源操作
- 5. 小結
- 15 流的實現
- 1. php流的表象之下
- 2. 包裝器操作
- 3. 實現一個包裝器
- 4. 操縱
- 5. 檢查
- 6. 小結
- 16 有趣的流
- 1. 上下文
- 2. 過濾器
- 3. 小結
- 17 配置和鏈接
- 1. autoconf
- 2. 庫的查找
- 3. 強制模塊依賴
- 4. Windows方言
- 5. 小結
- 18 擴展生成
- 1. ext_skel
- 2. PECL_Gen
- 3. 小結
- 19 設置宿主環境
- 1. 嵌入式SAPI
- 2. 構建并編譯一個宿主應用
- 3. 通過嵌入包裝重新創建cli
- 4. 老技術新用
- 5. 小結
- 20 高級嵌入式
- 1. 回調到php中
- 2. 錯誤處理
- 3. 初始化php
- 4. 覆寫INI_SYSTEM和INI_PERDIR選項
- 5. 捕獲輸出
- 6. 同時擴展和嵌入
- 7. 小結
- 約定