最近做一個網站,需要頻繁使用遠端資料,資料介面已經做好。在做轉換的時候遇到了效能上的問題:開始打算用php來實現轉換,苦苦查了數天,都沒有找到直接操作位元組的方法。雖然可以使用 pack() 方法將各個資料壓入結構中,但是在解壓的時候卻不能通過 unpack() 簡單的解出來,需要通過
//php code
for( $i = 0; $i < $length; $i+=2 ){
$tempstr = $tempstr.chr( hexdec(substr($array["data"], $i, 2)) );
}
$array["data"] = $tempstr;
這類方法進行解碼。頻繁的使用各種字串操作,無疑將對效能造成很大的影響。經過研究,發現有以下方法可以實現對位元組的操作:
- 使用stream進行讀寫
- 使用socket進行讀寫
- 使用COM dll,將資料在C++ dll中進行轉換
由於網上找不到相關的文檔(其實是沒好好找),stream和socket先後被PASS掉了。為了編譯COM的dll,還專門下載了VC++ 6.0(為啥不裝2005?硬碟太小,裝不下,沒辦法啊)。經過無窮無盡的Google(全是php調用VB寫的dll的資訊,沒多大協助)和編譯/調試,終於成功的把結果傳遞到php中。
下面簡單介紹一下步驟和注意事項:
- 在VC++ 6.0中,File -> New... 選擇Projects中的"ATL COM AppWizard",填寫工程名稱等。本例中,工程名為"ATLtest"。
- 在"ATL COM AppWizard - Step 1 on 1"對話方塊中,"Server Type" 選擇"Dynamic Link Library (DLL)",之後Finish。
- 在"ClassView"中,右擊"ATLtest",選擇"New ATL Object...",在"ATL Object Wizard"中,選擇預設的"Simple Object",之後"Next"。
- 在"ATL Object Wizard 屬性"中,在"Short Name"輸入介面的名稱。本例中,介面名稱為"test"。之後,"Names"選項卡中的所有textBox都自動填好了預設的值。注意兩個地方:一個"Prog ID"(本例中為"ATLtest.test"),一個"Interface"(本例中為"Itest")。
- 完成之後,在"ClassView"中,"ATLtest classes"下產生了"Ctest"類,並且實現了"Itest"介面。
- 右擊"Itest"介面,選擇"Add Method..."。
- 在"Add Method to Interface"中,填寫方法的名稱和參數。注意:傳回值一定是HRESULT型,真正的傳回值是最後一個參數。比如//C++ code
BSTR Encode(unsigned int msgType, unsigned int msgLength, BSTR message)這個函數,要寫成
//C++ code
STDMETHODIMP Ctest::Encode(
unsigned int msgType,
unsigned int msgLength,
BSTR message,
BSTR *result
)這樣的形式。還有就是傳回值只接受簡單的類型(不知道為什麼,char**不能用)和指標,BSTR沒法直接使用。
- 完成這個函數。當然,為了簡單起見,這裡就是給結果隨便賦了一個值,用來說明參數成功的傳遞出來了。沒有考慮任何記憶體流失問題。//C++ code
STDMETHODIMP Ctest::Encode(
unsigned int msgType,
unsigned int msgLength,
BSTR message,
BSTR *result
)
{
BSTR temp = ::SysAllocString(L"asdfasdf");
*result = temp; return S_OK;
}
- 編譯,將得到的ATLtest.dll使用regsvr32進行註冊,之後才能使用COM進行調用。
- 之後書寫這樣的php代碼://php code
$com = new COM("ATLtest.test") or die("無法建立COM組件");
$result = "未處理的字串";
echo '$result = "'.$result.'"<br />';$result = $com->Encode(1,1,"11");
echo '$result = "'.$result.'"<br />';
$com = null;
- 注意這裡的"ATLtest.test"是剛才(4)中的"Prog ID",並且使用Encode() 的方法和聲明的也不一樣。沒有關係!
當然,由於完全沒有用到三個輸入參數,這裡的1,1,"11"只是為了滿足輸入參數的數量。
- 這個php的輸出是什麼樣的呢?//HTML 結果
$result = "未處理的字串"
$result = "asdfasdf"可見,$result 成功的改變成了dll中賦的值,說明 Encode() 方法成功的返回了值。
幾點疑問
- 為什麼 Encode() 中返回的是 BSTR* ,但是到了php中,就變成了字串(BSTR) 呢?這個自動的轉換是ATL進行的,還是php進行的呢?
- C++代碼中通過SysAllocString()為BSTR分配的空間在何時進行垃圾收集?收集工作由哪裡負責?會不會導致記憶體流失?
- 完惡的C++ 6.0 編譯器,為什麼傳回值不支援 char** 這種簡單的類型呢(使用char**直接編譯出無數錯誤)? BSTR本質上就是指標嘛,也不支援(提示說只支援簡單類型和指標),只好用一個不倫不類的BSTR*來寫。嗯,下一步嘗試改用CCOMBSTR或者_bstr_t,試試哪個更好用。
- 對於傳入的BSTR* result,需要使用 SysFreeString() 進行處理嗎?在C++中看來,無疑是需要釋放的;但是php在背後做了哪些工作呢?有沒有對未被引用的常量"未處理的字串"進行垃圾收集呢?
//////////////////////////////////////////////////////////////////////////
php調用dll項目需要用到php調用dll,論壇發帖得到思路,用DynamicWrapper方法調用。下載DynamicWrapper.dll到php ext下與windows system32下, $dw = new COM("DynamicWrapper"); $dw->Register("EbUsbApi.dll", "EbCreateDataFile", 'i=sls', "f=s", "r=l"); $ch = $dw->EbCreateDataFile("222",11,"22"); dll其中一個函數 HANDLE EbCreateDataFile(LPCTSTR lpFileName, DWORD dwCreationDisposition, LPCTSTR lpPassword) 得到結果0.成功///////////////////////////////////////////////////1。查看CPU荷載:
CODE:
<?php
$wmi = new COM('winmgmts://');
$processor = $wmi->ExecQuery("SELECT * FROM Win32_Processor");
foreach($processor as $obj){
$cpu_load_time = $obj->LoadPercentage;
}
echo $cpu_load_time;
?>
2。調用自定的dll組件:
1) 建立ActiveX dll組件 --
CODE:
Public Function hello() As String
hello = "Hello World!"
End Function
並存為"test.dll" 檔案
2) 用regsvr32.exe註冊此組件
regsvr32 test.dll
3) 在PHP內調用此dll組件:
CODE:
<?php
$obj = new COM("test.dll");
$output=$obj->hello(); // Call the "hello()" 方法
echo $output; // 顯示Hello World! (so this comes from the dll!)
?>
/////////////////////////////////
今天要用工商銀的介面做一個線上支付,提供的是兩個dll檔案和一個說明文檔,另外還電子認證。
PHP調用COM組件,從網上找了半天也沒找到個說得明白的,是不是用這個的人太少了又或是太簡單所
以沒有人寫。
什麼是COM?
COM(Component Object Model)元件物件模型,是一種跨應用和語言共用二進位代碼的方法。是
位於DCE RPC上部的對象指向層(關聯服務)定義公用的調用協定以允許用不同語言編寫的代碼調用,
並允許其它語言代碼進行互動操作(前題是代碼是COM明白的),COM可以作為DLL被本機程式載入也可
以通過DCOM被遠程進程調用。
準備工作
比如我作了一個COM組件,建立一個VB6工程,ActiveX Dll將工程命名為P_test,類名為c_test ,類的檔案內容如下:
Option Explicit
Private MyScriptingContext As ScriptingContext
Private MyApplication As Application
Private MyRequest As Request Private MyResponse As Response
Private MyServer As Server
Private MySession As Session Public
Sub OnStartPage(PassedScriptingContext As ScriptingContext)
Set MyScriptingContext = PassedScriptingContext
Set MyApplication = MyScriptingContext.Application
Set MyRequest = MyScriptingContext.Request
Set MyResponse = MyScriptingContext.Response
Set MyServer = MyScriptingContext.Server
Set MySession = MyScriptingContext.Session
End Sub
Public Sub OnEndPage()
Set MyScriptingContext = Nothing
Set MyApplication = Nothing
Set MyRequest = Nothing
Set MyResponse = Nothing
Set MyServer = Nothing
Set MySession = Nothing
End Sub
Public Function Test_Number(num) As Variant
If num < 0 Then Get_Number_Attrib = -1
If num > 0 Then Get_Number_Attrib = 1
If num = 0 Then Get_Number_Attrib = 0
End Function
編譯產生p_test.dll檔案
第一步,做為一個COM組件,這個DLL要被系統識別就要先到系統來報到
regsvr32 [路徑]/[組件檔案名稱]
regsvr32 C:/WINDOWS/system32/p_test.dll
放在系統檔案夾system32下不容易出現許可權問題
這時候這個檔案就不能移動位置了,系統會在用到它時到這個目錄來找,如果改目錄就得先刪除註冊
再重新註冊
regsvr32 /u [路徑]/[組件檔案名稱]
系統會顯示視窗表示成功,大意是 組件Dllregister成功或是Dllunregister成功
第二步就可以直接調用它了
<?
$b=new COM("p_test.c_test"); //一般前邊是它的主檔案名後邊是它的類名從註冊表裡找這個文
件可以找到
這樣就產生了一個叫b的對象,我們就可以用它的屬性和方法來操作了
$a=$b->Test_Number(-454);
echo $a;
?>
可能遇到的問題是,編譯工程時通不過,要將
Microsoft Active Server Pages Object Library
引用進來,具體實現"Project->References"找到改庫,並勾上 。