1. 程式人生 > >Windows下php擴充套件開發c++動態庫

Windows下php擴充套件開發c++動態庫

PHP擴充套件開發,從零瞭解到初步完成一個小專案,經過三天的仔細研究,現整理如下

一、需求介紹

PHP擴充套件開發,呼叫自己之前的c++動態庫,完成功能

二、專案之前

系統:windows xp 

開發工具:vs 2008

web環境:apache2.4  PHP5.3.29-VC9-ts-x86 aphach和PHP 環境之前已經搭建完成

PHP原始碼:去官網 下載穩定版本的php原始碼包(因為要編譯擴充套件庫,必須要php的原始碼才能編譯),將原始碼解壓到如:d:\php_src 目錄下。本示例用的是PHP5.3.29。下載二進位制包(如果已經安裝了php環境,就可以不用下載),這裡主要用到php二進位制包中的php5ts.lib,該檔案位於php的dev目錄。本示例使用的是php-5.3.29-Win32-VC9-x86二進位制包。


配置原始碼:將原始碼中php_src/win32/build/config.w32.h.in檔案拷貝一份到php_src/main/下,並重命名為:config.w32.h。

三、建立專案

1、建立一個空的win32專案(注意:是Win32的 dll 專案工程,)。
2、配置工程屬性:
(1)新增附加包含目錄:在C/C++的選項中,新增附加包含目錄。包含php原始碼中的幾個目錄。
如:D:\php_src;D:\php_src\main;D:\php_src\Zend;D:\php_src\TSRM;D:\php_src\win32;
(2)新增前處理器:ZEND_DEBUG=0;ZTS=1;ZEND_WIN32;PHP_WIN32;

(3)新增附加庫:php5ts.lib(該庫位於php二進位制文間包中的dev目錄)

四、編寫原始碼示例

1、新增原始檔如:Main.cpp和原始檔的標頭檔案Main.h。其中檔案的內容主要參考了在linux下編寫php擴充套件庫,自動生成的檔案的內容(cygwin 可以幫助實現搭建好擴充套件的骨架,我沒試驗過,不過沒有cygwin也沒關係,直接拷貝下面的程式碼

Main.h檔案內容:


#ifndef PHP_TEST_MAIN_H
#define PHP_TEST_MAIN_H

extern zend_module_entry PHPTest_module_entry;          // PHPTest  是該示例的工程名字, PHPTest_module_entry是php擴充套件庫的入口宣告
#define phpext_PHPTest_ptr &PHPTest_module_entry

#ifdef PHP_WIN32
#define PHP_PHPTest_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#define PHP_PHPTest_API __attribute__ ((visibility("default")))
#else
#define PHP_PHPTest_API
#endif

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

PHP_MINIT_FUNCTION(PHPTest);
PHP_MSHUTDOWN_FUNCTION(PHPTest);
PHP_RINIT_FUNCTION(PHPTest);
PHP_RSHUTDOWN_FUNCTION(PHPTest);
PHP_MINFO_FUNCTION(PHPTest);

// PHP_FUNCTION  用於定義要匯出給php呼叫的函式名稱,這裡我們定義了3個函式:init_module,test_module, close_module
// PHP_FUNCTION  只用來宣告函式的名稱,置於函式的引數將在cpp中定義

PHP_FUNCTION(init_module);
PHP_FUNCTION(test_module);
PHP_FUNCTION(close_module);


/* 
  Declare any global variables you may need between the BEGIN
and END macros here:     
ZEND_BEGIN_MODULE_GLOBALS(CSVirusAnalyse)
long  global_value;
char *global_string;
ZEND_END_MODULE_GLOBALS(CSVirusAnalyse)
*/

/* In every utility function you add that needs to use variables 
   in php_CSVirusAnalyse_globals, call TSRMLS_FETCH(); after declaring other 
   variables used by that function, or better yet, pass in TSRMLS_CC
   after the last function argument and declare your utility function
   with TSRMLS_DC after the last declared argument.  Always refer to
   the globals in your function as CSGAVIRUSANALYSIS_G(variable).  You are 
   encouraged to rename these macros something shorter, see
   examples in any other php module directory.
*/

#ifdef ZTS
#define PHPTEST_G(v) TSRMG(PHPTest_globals_id, zend_PHPTest_globals *, v)
#else
#define PHPTEST_G(v) (PHPTest_globals.v)
#endif

#endif/* PHP_TEST_MAIN_H*/





編譯Mian.cpp檔案:



// 宣告以下的巨集定義解決在編譯過程中會發生:error C2466: 不能分配常量大小為0 的陣列的錯誤。
#ifdef PHP_WIN32  
#define _STATIC_ASSERT(expr) typedef char __static_assert_t[ (expr)?(expr):1 ]  
#else  
#define _STATIC_ASSERT(expr) typedef char __static_assert_t[ (expr) ]  
#endif  

// #include "XXXXX.h" 在以下包含標頭檔案的前面包含要用到的c++ 的stl的標頭檔案,或者你自己寫的C++的標頭檔案。
#include <string>
using namespace std;

extern "C"{
#include "zend_config.w32.h"
#include "php.h"
#include "ext/standard/info.h"
#include "Main.h"
}


// 聲明瞭擴充套件庫的匯出函式列表
zend_function_entry PHPTest_functions[] = {    
    PHP_FE(init_module, NULL) 
    PHP_FE(test_module, NULL) 
    PHP_FE(close_module, NULL) 
    PHP_FE_END
}; 

zend_module_entry PHPTest_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "PHPTest",
    PHPTest_functions,
    PHP_MINIT(PHPTest),
    PHP_MSHUTDOWN(PHPTest),
    PHP_RINIT(PHPTest), /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(PHPTest), /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(PHPTest),
#if ZEND_MODULE_API_NO >= 20010901
    "0.1", /* Replace with version number for your extension */
#endif
    STANDARD_MODULE_PROPERTIES
};


ZEND_GET_MODULE(PHPTest); 

PHP_MINIT_FUNCTION(PHPTest)
{
    /* If you have INI entries, uncomment these lines 
    REGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(PHPTest)
{
    /* uncomment this line if you have INI entries
    UNREGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}

PHP_RINIT_FUNCTION(PHPTest)
{
    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(PHPTest)
{
    return SUCCESS;
}

PHP_MINFO_FUNCTION(PHPTest)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "PHPTest support", "enabled");
    php_info_print_table_end();


    /* Remove comments if you have entries in php.ini
    DISPLAY_INI_ENTRIES();
    */
}

// 以下是php匯出函式的實現,比如string init_module(string content)
PHP_FUNCTION(init_module)
{
    char *content = NULL;   // 
    int argc = ZEND_NUM_ARGS();
    int content_len;
    // 這句話便是匯出傳入引數 
    if (zend_parse_parameters(argc TSRMLS_CC, "s", &content, &content_len) == FAILURE) 
        return;
    if(content)
    {
        //  這裡只是為了測試,直接把傳入值返回去。
        string strRet = content;
        // 返回值
        RETURN_STRING((char*)strRet.c_str(), 1);
    }
    else
    {
        php_error(E_WARNING, "init_module: content is NULL");
    }
}

// 以下是int test_module(string content)函式的實現
PHP_FUNCTION(test_module)
{
    char *content = NULL;
    int argc = ZEND_NUM_ARGS();
    int content_len;


    if (zend_parse_parameters(argc TSRMLS_CC, "s", &content, &content_len) == FAILURE) 
        return;
    if(content)
    {
        int nRet = content_len;
        RETURN_LONG(nRet);
    }
    else
    {
        php_error(E_WARNING, "test_module: &content is NULL");
    }


}

// 以下是 void close_module()函式的實現
PHP_FUNCTION(close_module)
{
    if (zend_parse_parameters_none() == FAILURE) {
        return;
    }
    php_printf("close_module successfully\n"); 
}

  ok,編寫完以上的檔案後,編譯一下,將生成的dll檔案,拷貝到正常工作的php 的ext資料夾下,並在php.ini上配置,在extension=php_zip.dll後面新增extension=PHPTest.dll。然後重啟Apache。

編寫php測試 程式碼

<?php
echo init_module('test init');
echo'<br>';
//輸出: test init
echo test_module('test_module');
echo'<br>';
close_module();
?>
出現問題

由於生成的PHPTest.dll 與PHP安裝環境不一致導致,解決方法(非常重要)

為了解決這個問題走了很多彎路,開始以為是PHP原始碼版本的問題,下載了很多個版本都沒成功,浪費了很多時間

解決很簡單:在php_src\main\config.w32.h檔案中增加 #define PHP_COMPILER_ID "VC9"用VC9編譯

執行結果:

test init
11
close_module successfully 


五、注意

1、注意你的標頭檔案的包含的順序。

將你的標頭檔案以及Windows和C++的標頭檔案包含在php標頭檔案的前面

#include "xxxx.h"  //  你的標頭檔案
extern "C"{
#include "zend_config.w32.h"
#include "php.h"
#include "ext/standard/info.h"
#include "Main.h"
}

2.可能遇到error C2466: 不能分配常量大小為0 的陣列
解決方法:

在vc的 c:\program files\microsoft visual studio 8\vc\include\malloc.h 檔案中找到:
#define _STATIC_ASSERT(expr) typedef char __static_assert_t[ (expr) ]
將這一行改為:
#ifdef PHP_WIN32
#define _STATIC_ASSERT(expr) typedef char __static_assert_t[ (expr)?(expr):1 ]
#else
#define _STATIC_ASSERT(expr) typedef char __static_assert_t[ (expr) ]
#endif
或者直接在你的cpp檔案中定義也可以。


2. 如果遇到2019連線錯誤,那麼通常是沒有刪除預處理定義中的巨集LIBZEND_EXPORTS,


六、遇到問題

提示找不到phptest.dll,是因為在這個是專案中,在編輯PHPTest.dll的main.cpp檔案中呼叫了之前自己寫的動態庫,如何方法之前的identify.dll庫也成了一個問題,首先在PHPtest工程中新增動態連結庫identify.lib,新增方法同php5ts.lib

然後把identify.dll拷貝到aphach安裝目錄下bin資料夾內C:\Apache24\bin 即可完成動態庫的呼叫。