首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

PHP 擴展開發實戰:用 C 語言編寫 PostgreSQL 驅動模組

     編輯:關於C語言

為什麼要寫 PHP 擴展?

  • **效能**:核心邏輯下沉到 C,避免 PHP 解釋開銷。
  • **整合既有 C 庫**:如自研資料庫、硬體驅動、圖像處理庫。
  • **封裝系統呼叫**:提供 PHP 無法直接存取的底層能力。

PHP 原始碼結構與骨架生成


$ cd php-4.4.2/ext
$ ./ext_skel --extname=mypg
Creating directory mypg
Creating basic files: config.m4, config.w32, php_mypg.h, mypg.c, ...

生成的關鍵檔案:

  • config.m4:Unix 建置腳本(phpize && ./configure --enable-mypg && make)。
  • php_mypg.h:對外 API 宣告(PHP_FUNCTION、模組入口)。
  • mypg.c:核心實現(MINIT/RINIT、資源管理、函式體)。

關鍵檔案修改詳解

1. config.m4 — 啟用擴展、連結 libpq


PHP_ARG_ENABLE(mypg, whether to enable mypg support,
[  --enable-mypg   Enable mypg support])

if test "$PHP_MYPG" != "no"; then
  # 尋找 PostgreSQL 安裝路徑
  SEARCH_PATH="/usr/local /usr"
  SEARCH_FOR="include/libpq-fe.h"
  for i in $SEARCH_PATH; do
    if test -r $i/$SEARCH_FOR; then
      PGSQL_DIR=$i
      break
    fi
  done
  if test -z "$PGSQL_DIR"; then
    AC_MSG_ERROR([PostgreSQL headers not found. Install libpq-dev or specify --with-pgsql=DIR])
  fi

  PHP_ADD_INCLUDE($PGSQL_DIR/include)
  PHP_ADD_LIBRARY_WITH_PATH(pq, $PGSQL_DIR/lib, MYPG_SHARED_LIBADD)
  PHP_SUBST(MYPG_SHARED_LIBADD)

  PHP_NEW_EXTENSION(mypg, mypg.c, $ext_shared)
fi

2. php_mypg.h — 公開 API


#ifndef PHP_MYPG_H
#define PHP_MYPG_H

extern zend_module_entry mypg_module_entry;
#define phpext_mypg_ptr &mypg_module_entry

#ifdef PHP_WIN32
# define PHP_MYPG_API __declspec(dllexport)
#else
# define PHP_MYPG_API
#endif

PHP_MINIT_FUNCTION(mypg);
PHP_FUNCTION(mypg_connect);
PHP_FUNCTION(mypg_query);
PHP_FUNCTION(mypg_fetch_assoc);
PHP_FUNCTION(mypg_close);

#ifdef ZTS
#include "TSRM.h"
#endif
#endif

3. mypg.c — 核心實現重點


#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_mypg.h"
#include "libpq-fe.h"   // PostgreSQL C API

// 資源類型編號
static int le_pgsql_link;

// 連結資源析構器:PHP 自動呼叫 (請求結束或 unset)
static void _close_pgsql_link(zend_resource *rsrc) {
    PGconn *conn = (PGconn*)rsrc->ptr;
    if (conn) PQfinish(conn);
}

PHP_MINIT_FUNCTION(mypg) {
    // 註冊資源析構器
    le_pgsql_link = zend_register_list_destructors_ex(_close_pgsql_link, NULL, "pgsql link", module_number);
    return SUCCESS;
}

// 連線函式:mypg_connect("host=localhost dbname=test user=postgres")
PHP_FUNCTION(mypg_connect) {
    char *conninfo;
    size_t conninfo_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &conninfo, &conninfo_len) == FAILURE) return;

    PGconn *conn = PQconnectdb(conninfo);
    if (PQstatus(conn) != CONNECTION_OK) {
        php_error_docref(NULL, E_WARNING, "Connection failed: %s", PQerrorMessage(conn));
        PQfinish(conn);
        RETURN_FALSE;
    }

    // 建立 zend_resource 並返回 resource ID
    RETURN_RES(zend_register_resource(conn, le_pgsql_link));
}

// 查詢執行
PHP_FUNCTION(mypg_query) {
    zval *link_res;
    char *sql; size_t sql_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &link_res, &sql, &sql_len) == FAILURE) return;

    PGconn *conn = (PGconn*)zend_fetch_resource(Z_RES_P(link_res), "pgsql link", le_pgsql_link);
    if (!conn) { RETURN_FALSE; }

    PGresult *res = PQexec(conn, sql);
    ExecStatusType status = PQresultStatus(res);
    if (status != PGRES_TUPLES_OK && status != PGRES_COMMAND_OK) {
        php_error_docref(NULL, E_WARNING, "Query failed: %s", PQerrorMessage(conn));
        PQclear(res);
        RETURN_FALSE;
    }
    // 將 PGresult 也註冊為資源,供 fetch 使用
    RETURN_RES(zend_register_resource(res, le_pgsql_result));
}

// 釋放結果集資源
static void _free_pgsql_result(zend_resource *rsrc) {
    PGresult *res = (PGresult*)rsrc->ptr;
    if (res) PQclear(res);
}

// 模組入口
zend_module_entry mypg_module_entry = {
    STANDARD_MODULE_HEADER,
    "mypg",
    mypg_functions,           // function_entry 陣列
    PHP_MINIT(mypg), NULL, NULL, NULL, NULL,
    "0.1.0",
    STANDARD_MODULE_PROPERTIES
};

ZEND_GET_MODULE(mypg)

建置與安裝流程


$ cd php-4.4.2/ext/mypg
$ phpize
$ ./configure --enable-mypg --with-pgsql=/usr/local/pgsql
$ make && sudo make install
# php.ini 加入 extension=mypg.so
$ php -m | grep mypg

PHP 端使用範例


$conn = mypg_connect("host=localhost dbname=test user=postgres password=secret");
if (!$conn) die("Connect failed");

$res = mypg_query($conn, "SELECT id, name FROM users WHERE active=1");
while ($row = mypg_fetch_assoc($res)) {
    echo "User #{$row['id']}: {$row['name']}\n";
}
mypg_close($conn);

進階主題:預處理語句、參數綁定、非同步通知

  • 使用 PQprepare/PQexecPrepared 實現 Prepared Statement,防 SQL 注入。
  • 利用 PQsetNoticeProcessor 捕獲 NOTICE/LISTEN/NOTIFY。
  • 資源管理:請求結束自動釋放,長連線場景建議加入持久連線支援 (pconnect)。

避坑清單

  1. 記憶體洩漏:每個 PQexec 必須配對 PQclear;資源析構器不可漏註冊。
  2. 執行緒安全:PHP 7+ ZTS 模式下,全域變數需用 TSRM 宏存取;libpq 本身非執行緒安全,單連線單執行緒使用。
  3. 錯誤處理:php_error_docref 回報 PHP 警告,嚴重錯誤 RETURN_FALSE 而非 exit。
  4. 編碼:PQsetClientEncoding(conn, "UTF8") 確保中文正常。

總結

PHP 擴展開發核心流程:ext_skel 生成骨架 → 修改 config.m4 連結外部庫 → 實現 MINIT/資源管理/函式體 → phpize && configure && make install。掌握 Zend API 資源機制(zend_register_resource/zend_fetch_resource/析構器),即可將任意 C 庫無縫暴露給 PHP,獲得數量級效能提升。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved