windows上用c寫php擴充(轉)

來源:互聯網
上載者:User
windows下用c寫php擴充(轉)

windows下用c寫php擴充(加密解密php原始碼)


首先用hello world試手一下。
下載php源碼包,ext目錄就是擴充目錄了,裡面有2個重要的檔案是ext_skel以及ext_skel_win32.php。
下載cygwin,有了這個就可以方便的在windows下建立php擴充了。
下載中……
下載完後用php ext_skel_win32.php --extname=hello來編譯產生我們的擴充開發目錄hello,然後開始寫測試程式hello world。在hello.c檔案裡添加函數定義以及函數註冊語句:
函數註冊語句:

const zend_function_entry hello_functions[] = { PHP_FE(confirm_hello_compiled, NULL)      PHP_FE(sayHello,NULL)//這句是我們手動添加的 {NULL, NULL, NULL} };

?

函數定義:

PHP_FUNCTION(sayHello){ php_printf("Hello C extension");}

?

好像說是一定要以PHP_FUNCTION出現的宏形式,因為如果直接裸寫c代碼可能會發生命名衝突或是其他的衝突。然後在php_hello.h裡面添加函式宣告語句:
PHP_FUNCTION(sayHello);
寫好測試程式,編譯結果出現:
../main/config.w32.h': No such file or directory
網上查了一下,好像是要下載額外的包。。。麻煩啊!
http://www.php.net/extra/bindlib_w32.zip
http://www.php.net/extra/win32build.zip
將這兩個包放在一起,我把它們放在win32/build目錄下,然後執行php源碼包根目錄下的buildconf.bat(最好在命令列運行,不然顯示結果會一閃而過)。
然後把bison.exe(在剛下載的包裡面)所在的目錄設定為環境變數,再運行configure.bat。完後就產生/main/config.w32.h這個檔案了。
然後再次編譯剛才hello項目結果出現一大推錯誤。
形如:
..\..\main\streams/php_stream_transport.h(85) : error C2143: syntax error : missing ')' before '*'
..\..\main\streams/php_stream_transport.h(85) : error C2081: 'socklen_t' : name in formal parameter list illegal

網上說是由於找不到宏定義才會這樣,那應該是socklen_t這個宏沒定義了,但是它具體的宏定義應該是怎麼樣的呢,總不能隨便寫一個吧,所以開啟
\main\streams/php_stream_transport.h
發現應該是個類型別名,而且是個int ,因為有socklen_t addrlen; addrlen 按字面應該是儲存長度的值。
所以在這個檔案中添加
typedef int socklen_t;
並儲存,再編譯剛才的項目,錯誤少了很多,但是還有7個,經檢查發現是輸入了中文符號。改正再編譯。。。還有一個錯誤:
LINK : fatal error LNK1181: cannot open input file "php5ts.lib"
於是將尋找php5ts.lib這個檔案並將它放到項目目錄下或是VC6 lib檔案預設搜尋目錄下也可以。找啊找啊找。。。
tmd,用windows搜尋找了好久都沒找到。百度是說在php二進位程式碼封裝裡面。所以先下個同版本的二進位程式碼封裝(應該就是平時寫php所必須下載的那個包吧)
這裡先說下環境吧windows+vc6+php5.3.5(二進位程式碼封裝和源碼包)+cygwin
下載完畢,找到,複製 ,編譯,成功!
但是出現了一個很嚴重的問題,dll檔案沒出來,,,我哭!建立出來的是php_hello.exp和php_hello.lib。怎麼會是靜態呢??
其實已經產生了,只是不在本目錄下的Release_TS目錄下,而是在ext上級的Release_TS目錄下。
然後測試。哈哈,說是未定義函數,有沒可能是測試的php版本和我擴充的php版本不一樣的關係呢?
測試了一下也不是,只要在ini裡一設定載入php_hello.dll重啟apache就會出現記憶體不能讀的錯誤。
總覺得代碼沒有問題,應該還是前面配置編譯的時候有問題。
經過一番又一番的測試,發現時php二進位包下錯了我下了vc9的應該下vc6的。

接下來要開始寫加密和解密了。加密解密演算法本身不是這裡的重點,重點是如何在zend層用zend本身的介面結合c來編程,在zend編譯源檔案之前將檔案解密(當然檔案之前要是有加過密的)。
為了使用的方便。我的想法是像php_screw一樣產生dll的同時,產生一個加密的可執行檔,這加密的可執行檔由我們手動執行,傳入目錄參數,能夠對該目錄下的所有檔案進行加密。
在網上找了一堆資料後,再看看php_screw的代碼,還有有些吃力,所以決定根據自己的思路來寫。當然有些地方時會借鑒php_screw的代碼
首先是先寫一個對檔案進行解密的函數。這個函數利用我們現成的解密演算法對檔案內容進行解密。
這個函數應該有一個參數用來接收當前請求檔案的控制代碼(貌似是zend_compile_file這個東西,百度之,確認一下,幾篇文章說是函數指標,我看了下原始碼確實是函數指標)

extern ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);

?

那麼要如何得到當前請求檔案的檔案指標呢?或許和zend_compile_file函數指標所指函數的作用有關。在原始碼中發現了:
zend_compile_file = compile_file;
該函數的定義在zend_language_scanner.c,不過要看懂該函數有點難度,網上的說法是:
-------------
zend_compile_file負責將要執行的指令檔編譯成由ZE的基本指令序列構成的op codes 。
PHP執行這段代碼會經過如下4個步驟:
1. Scanning (Lexing) ,將PHP代碼轉換為語言片段(Tokens)
2. Parsing , 將Tokens轉換成簡單而有意義的運算式
3. Compilation , 將運算式編譯成Opocdes
4. Execution , 順次執行Opcodes,每次一條,從而實現PHP指令碼的功能。
-------------
所以我們應該要在這個四個步驟之前將檔案解密。
想法是重寫一個函數 myCompile 判斷在檔案被compile之前先將它解密,然後再調用預設的complie函數。定義好 myCompile 後,應該在請求初始化的時候將 myCompile 傳遞給函數指標zend_compile_file

PHP_MINIT_FUNCTION(dencrypt){ old_comlie_file = zend_complie_file;//保留預設的compile,以便等一下調用 zend_complie_file = myCompile; return SUCCESS;}ZEND_API zend_op_array *myCompile(zend_file_handle *file_handle,int type TSRMLS_DC){  //這裡的TSRMLS_DC是一個宏類似於,...(宏的定義暫時找不到)總之是跟多線程環境下全域變數的安全執行緒有關係的,以後再深究  //這裡是解密代碼。。。  old_comlie_file(file_handle);  ....}

?
但是問題還是沒有解決,因為我們還是不知道如何擷取到檔案指標。我看php_screw裡面的解密步驟挺長的,參考之。。。找到了:

fp = fopen(file_handle->filename, "r");

?原來file_handle裡面有檔案名稱的資訊(其實如果找到file_handle的結構體定義語句也就知道了)。但是php_screw裡面還有這麼一段:

char fname[32];memset(fname, 0, sizeof fname);if (zend_is_executing(TSRMLS_C)) {  //TSRMLS_C獲得全域變數    //擷取當前調用函數的名字(當前調用函數?哪裡的函數?php函數?想想當然是zend裡面的函數。而不是php層的函數,因為現在都還沒編譯,更沒執行呢)  if (get_active_function_name(TSRMLS_C)) {    strncpy(fname, get_active_function_name(TSRMLS_C), sizeof fname - 2);  }}if (fname[0]) {  if ( strcasecmp(fname, "show_source") == 0  //也就是說如果當前是這兩個函數則不解密也不編譯了。恩,看樣子沒錯。    || strcasecmp(fname, "highlight_file") == 0) {   return NULL;  }}

?

所以這段還是必要的,不然遇到以上兩個函數就沒法實現了,compile_file函數裡面應該也有這步才對。所以這裡主要是不要讓它被解密,而是直接顯示密文。
還有這麼一段:

fp = fopen(file_handle->filename, "r");if (!fp) { //如果開啟失敗則直接調用預設compile函數  return org_compile_file(file_handle, type);}fread(buf, PM9SCREW_LEN, 1, fp); //一下5句的作用是:如果發現時未加密的檔案則不進行解密。if (memcmp(buf, PM9SCREW, PM9SCREW_LEN) != 0) {  fclose(fp);  return org_compile_file(file_handle, type);}if (file_handle->type == ZEND_HANDLE_FP) fclose(file_handle->handle.fp); //判斷檔案控制代碼類型,應用相應的關閉函數。if (file_handle->type == ZEND_HANDLE_FD) close(file_handle->handle.fd);file_handle->handle.fp = pm9screw_ext_fopen(fp);                         //調用解密檔案的函數,並用file_handle裡面的fp接收函數返回結果file_handle->type = ZEND_HANDLE_FP;                                      //將控制代碼類型設定為檔案指標類型file_handle->opened_path = expand_filepath(file_handle->filename, NULL TSRMLS_CC);   

?

上面這句有點不懂,猜想是不是接受當前檔案的路徑呢?一路追蹤找到了expand_filepath_ex這個函數這個函數的最後一句是return real_path;看樣子應該就是返回當前要編譯檔案的路徑吧。但是為什麼要有上面那兩步呢?就是設定類型和路徑這兩部。如果我們不做解密操作就不用這兩步,我的猜想是因為fclose(file_handle->handle.fp)這裡改變了file_handle的狀態,所以才需要重新設定吧!(但是有一個疑問是,這樣做是Compilation之前解密,還是在Scanning之前解密?我初步認為從語義上來講是在Compilation之前解密,但是從實際情況來看應該是要在Scanning之前解密,這邊後期可以驗證,事實證明後者是對的,因為compile_file函數裡有對open_file_for_scanning函數的調用,也就是說在compile_file函數裡執行了前面所說的123步)。
進入點有了(還有4分之3的問題需要解決)。

?

接下來開始寫解密檔案的函數。
需要考慮的問題有,解密檔案後的明文代碼,並不需要寫入檔案,那麼如何取得這些明文的檔案指標呢?用臨時檔案來做?但是如果每個請求都用不同的臨時檔案那會產生很多臨時檔案(顯然不可行),如果對同一個php檔案的不同請求只用一個臨時檔案,也會出現資源等待的問題(顯然也不可行)。能不能直接在記憶體中指定一個檔案指標呢?先看看php_screw的做法,調用tmpfile()產生臨時檔案,但是在程式退出時便會自動刪除!其實我認為的在記憶體中的檔案也是這樣實現的。
直接下來把之前用php寫的可逆加解密演算法用c來實現,可是c並沒有現成了md5和base64函數,看來只能拋棄這2個了。
寫好後就開始編譯了,遇到了一些錯誤也都改過來了。
然後連結的時候出現了錯誤:
error LNK2001: unresolved external symbol _zend_compile_file
搜尋了一下,貌似是因為函數編譯方式的不一樣所導致的找不到函數,加入:

BEGIN_EXTERN_C()ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);END_EXTERN_C()

?

即可。
產生dll檔案成功,但是不知道如何產生用於加密的可執行檔,當然最簡單的解決方案就是另外建一個項目。看來只好先這樣了,因為對vc的一些編譯機制還有參數設定也不是很熟悉(以後再研究程式員的自我修鍊好像有講這個的)。
之後又遇到c的指標傳遞問題(很久沒弄,一些基礎的都忘了,最後用二級指標來解決指標作為參數回傳的問題)。
又遇到了連結時找不到_zend_compile_file符號的問題(跟zend_api宏有關dllimport\dllexport)
然後又遇到讀寫檔案的問題,其實沒錯,只是我把寫入檔案的密文手動複製內容到另一個檔案中去測試,結果會出錯,如果直接複製檔案,或直接操作該原始檔案則正確。
最後又遇到了記憶體不能讀的錯誤。。。
最後是發現是由於檔案讀取方式的問題,以二進位讀取就可以了,如果以文本方式讀寫,會將某些特殊字元進行處理。
...最後測試功能是可以了,不過如果真的要應用的話要改進一下,比如我加密的時候是在原來目錄下直接產生加密後的檔案,更好的做法應該是要把原來的php檔案打包起來,方便管理。
接下來開始搞zf架構了,都快忘光了,而且當初還有還多東西沒用到的。

?

?

===============================

原文地址:

http://blog.sina.com.cn/s/blog_4d06da1f0100pgmj.html

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.