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 即可完成動態庫的呼叫。