copy from :http://leon-s-kennedy.iteye.com/blog/1545888
本文將研究以下幾個方面:
1.整型數組傳參
2.字符串參數,字符串返回值
3.修改傳入字符串內容
4.數組參數
5.IDispatch接口介紹
6.修改輸入數組內容
7.增加數組內容
8.以數組傳參方式,JS調用S4Execute()
(一)整型參數
1. 整型參數,可直接傳遞。整型返回值需要以 [retVal] Long *方式聲明
2. COM中c++接口定義
STDMETHODIMP CJsAtl::IntSum(LONG a, LONG b, LONG* retVal) { *retVal = a + b; return S_OK; }
3. Js中調用
<object id="obj" classid="CLSID:AD694878-......"> </object>
function test_int() { var a = 1; var b = 2; try { var obj = document.getElementByIdx_xx_x("obj"); var retVal = obj.IntSum(a, b); Alert("RetVal: " + retVal); } catch (e) { Alert( "Js error: " + e.message); } }
(二)字符串參數,字符串返回值
1. COM中,字符串使用BSTR表示,BSTR實際是UNICODE 字符數組(WCHAR[])
2. COM字符串傳參規范中規定:
a) 生成字符串變量時,需要SysAllocString/SysAllocStringByteLen分配空間。
b) 函數結束前,分配的空間需要釋放,SysFreeString。
c) 若函數中分配的空間作為返回值,則不釋放。而由外部調用者負責釋放。
3. COM中c++函數定義
STDMETHODIMP CJsAtl::StringAdd(BSTR str1, BSTR str2, BSTR* retVal) { int len = SysStringLen(str1); len += SysStringLen(str2); len += 4; // 保證有'\0'結尾 BSTR result = SysAllocStringLen(NULL, len); memset(result, 0, len * 2); // 雙字節字符 StrCat(result, str1); StrCat(result, str2); *retVal = result; // 設置返回值指針。注:不釋放內存 return S_OK; }
4. JS中調用
function test_str_cat() { var a = "123"; var b = "abc"; try { var obj = document.getElementByIdx_xx_x("obj"); var retVal = obj.StringAdd(a, b); alert("RetVal: " + retVal); } catch (e) { alert("JS ERROR: " + e.message); } }
(三)修改傳入字符串內容
1. 原則上,不應修改傳入字符串的內存數據,否則可能破壞數據,造成js端異常。
2. 使用中,可通過修改傳入字符串緩沖區內容的方法,實現參數傳遞。
3. 不能使用SysFreeString破壞傳入的BSTR參數,否則會破壞js內存結構
4. COM中C++定義
STDMETHODIMP CJsAtl::StrModify(BSTR str) { int len = SysStringLen(str); // 注:此方法修改BSTR,不能破壞原占用內存,不能越界訪問 for (int i = 0; i < len; i++) str[i] = '0' + i; return S_OK; }
5. JS調用
function test_str_modify() { var str = "abcdefghijklmn"; try { var obj = document.getElementByIdx_xx_x("obj"); obj.StrModify(str); alert("After modify: " + str); } catch (e) { alert("JS ERROR: " + e.message); } }
6. 測試執行
原字符串: abcdefghijklmn
調用后: 0123456789:;<=
(四)數組參數
1. 在使用時,有時需要使用數組傳參,如S4Execute()的inBuff/ outBuff。
2. JS中整型數據不分Byte/ Short/ Int等,因此數組元素類型都為int (COM中的VT_I4,其中I表示整型、4表示4字節)
3. JS中的Array在COM中是一個實現了IDispatch的對象,可通過IDispatch接口api進行操作。關於IDispatch請看下一節介紹。
4. COM中C++定義
下面代碼中定義了兩個函數 GetArrayNumberOfIndex、GetArrayLength兩個函數,功能分別獲取數組長度和獲取指定序號元素
以下代碼含義,請參考下一節 “IDispatch接口介紹”
JS數組在COM中是一個IDispatch對象,獲取長度,實際是獲取其中名為“length”的屬性值。
而獲取最后一個數組,實際是獲取名為“4”的屬性值(假設5個元素)
STDMETHODIMP CJsAtl::GetLastElement(VARIANT vArray, LONG* retVal) { int len = 0; HRESULT hr = GetArrayLength(vArray.pdispVal, &len); if (FAILED(hr)) return hr; hr = GetArrayNumberOfIndex(vArray.pdispVal, len - 1, retVal); return S_OK; } // *** // 獲取Javascript數組長度 // Javascript中的數組length只計算列表中下標為數字的部分 // *** HRESULT GetArrayLength(IDispatch* pDisp, int* pLength) { BSTR varName = L"length"; VARIANT varValue; DISPPARAMS noArgs = {NULL, NULL, 0, 0}; DISPID dispId; HRESULT hr = 0; hr = pDisp->GetIDsOfNames(IID_NULL, &varName, 1, LOCALE_USER_DEFAULT, &dispId); if (FAILED(hr)) return hr; hr = pDisp->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &noArgs, &varValue, NULL, NULL); if (SUCCEEDED(hr)) { *pLength = varValue.intVal; return hr; } else { return hr; } } // *** // 獲取Javascript數組中指定位置的整數元素值 // *** HRESULT GetArrayNumberOfIndex(IDispatch* pDisp, int index, int * pValue) { CComVariant varName(index, VT_I4); // 數組下標 DISPPARAMS noArgs = {NULL, NULL, 0, 0}; DISPID dispId; VARIANT varValue; HRESULT hr = 0; varName.ChangeType(VT_BSTR); // 將數組下標轉為數字型,以進行GetIDsOfNames // // 獲取通過下標訪問數組的過程,將過程名保存在dispId中 // hr = pDisp->GetIDsOfNames(IID_NULL, &varName.bstrVal, 1, LOCALE_USER_DEFAULT, &dispId); if (FAILED(hr)) return hr; // // 調用COM過程,訪問指定下標數組元素,根據dispId 將元素值保存在varValue中 // hr = pDisp->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTY GET , &noArgs, &varValue, NULL, NULL); if (SUCCEEDED(hr)) { *pValue = varValue.intVal; // 將數組元素按int類型取出 return hr; } else { return hr; } }
5. JS中調用
function test_get_last() { var array = new Array(0, 1, 2, 3); // 定義數組 try { var obj = document.getElementByIdx_xx_x("obj"); var lastElement = obj.GetLastElement(array); // 獲取數組最后一個元素 alert("lastElement: " + lastElement); } catch (e) { alert("JS ERROR: " + e.message); } }
6. 測試執行 數組定義:{0,1,2,3} 最后元素:3
(五)IDispatch接口介紹
1. C程序調用時,調用者必須預先知道接口規范(如:參數類型、參數字節長度、參數順序等)。由於不同語言這些規范有所不同,COM未解決不同語言之間調用,提供了IDispatch接口。
2. IDispatch要求其實例必須自我描述,即拿到一個對象后,可從對象中直接獲取調用方式,而無須預先明確。
3. IDispatch中通過VT_TYPE來指定相關類型,如 VT_I4為4字節整型、VT_BSTR為unicode字符串,VT_DISPATCH表示是一個IDispatch對象
4. 給對象中每一屬性或函數(Method)分配一個整型Id和一個字符串name,調用者可以通過name字符串確定如何調用。如:若name為"length"的屬性,調用者就理解為長度。由於這里通常是根據name來理解相應屬性,因此name描述應足夠准確。如,以"length()"為名稱的函數實現整數相加功能就是不恰當的。
5. 使用IDispatch對象時,首相調用 IDispatch::GetIDsOfNames()將屬性、函數名稱作為參數,獲取對應的屬性、函數id。
6. 再調用IDispatch::Invoke() 將id作為參數,實際調用功能。
7. 若為獲取屬性值,則 Invoke()調用時,傳入 Dispatch_PropertyGet標志。
8. 若為設置屬性值,則Invoke()調用時,傳入 Dispatch_PropertyPut標志。並在 DispParams參數中指定修該屬性改為何值。DispParams結構說明見后。
9. 若為調用函數,則 Invoke()調用時,傳入 Dispatch_Method標志。若該Method需要參數,則通過IDispatch::Invoke()的DispParams參數指定。
10. DispParams結構使用舉例:
DISPPARAMS dispparams; dispparams.rgdispidNamedArgs = &dispidOfNamedArgs; dispparams.cArgs = 1; dispparams.cNamedArgs = 1; dispparams.rgvarg = new VARIANTARG[1]; dispparams.rgvarg[0].vt = VT_I4; dispparams.rgvarg[0].intVal = 123;
a. 上面代碼是一個用於給Method傳參的簡單例子,先創建一個DispParams對象
b. cArgs指定Method中的參數個數。
c. cNamedArgs指定Method中已經命名的參數個數。(命名參數是對應無名參數的概念。有些語言可定義不定參數,此時IDispatch的描述中不會給參數分配名稱,而是調用時以無名參數存在。如,JS中 Array對象的push()方法,可支持不定個數的參數)
d. rgvarg 為實際參數數組,每一元素表示一個參數,其中.vt表明此元素的數據類型,intVal項是一個C++聯合結構,如vt == VT_I4時,應以intVal = xxx方式賦值;若 vt == VT_BSTR,則應以 bstrVal = xxx方式賦值
11. 舉例:兩個參數,都是無名稱參數,第一個為整型,第二個為BSTR型
DISPPARAMS dispparams; dispparams.rgdispidNamedArgs = NULL; dispparams.cArgs = 2; dispparams.cNamedArgs = 0; dispparams.rgvarg = new VARIANTARG[2]; // 2個參數,分配2個空間 dispparams.rgvarg[0].vt = VT_I4; // 整型 dispparams.rgvarg[0].intVal = 123; dispparams.rgvarg[1].vt = VT_BSTR; // 字符串型 dispparams.rgvarg[1].bstrVal = L"abcd";
(六)修改輸入數組內容
1. 第五節介紹了如何從JS向COM傳遞數組參數,以及如何在COM中獲取參數。本節介紹如何在COM中修改JS傳入的數組。
2. 修改JS數組值時,首先通過GetIDsOfNames獲取指定序號元素的dispid;然后調用Invoke(),傳入Dispatch_PropertyPut標志表明寫操作,並在DispParams結構中指明此元素類型和元素值。
3. COM中C++定義
STDMETHODIMP CJsAtl::ArrayModiy(VARIANT vArray) { SetArrayNumberOfIndex(vArray.pdispVal, 0, 123); // 修改數組第[0]個元素,值為 return S_OK; } // *** // 設置Javascript數組中指定位置的整數元素值 // *** HRESULT SetArrayNumberOfIndex(IDispatch* pDisp, int index, int value) { CComVariant varName(index, VT_I4); DISPID dispId; CComVariant varValue; HRESULT hr = 0; varName.ChangeType(VT_BSTR); // 將數組下標轉為數字型,以進行GetIDsOfNames hr = pDisp->GetIDsOfNames (IID_NULL, &varName.bstrVal, 1, LOCALE_USER_DEFAULT, &dispId); if (FAILED(hr)) return hr; DISPID dispidPut = DISPID_PROPERTYPUT; // put操作 DISPPARAMS dispparams; dispparams.rgvarg = new VARIANTARG[1]; // 初始化rgvarg dispparams.rgvarg[0].vt = VT_I4; // 數據類型 dispparams.rgvarg[0].intVal = value; // 更新值 dispparams.cArgs = 1; // 參數數量 dispparams.cNamedArgs = 1; // 參數名稱 dispparams.rgdispidNamedArgs = &dispidPut; // 操作DispId,表明本參數適用於put操作 hr = pDisp->Invoke(dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTY PUT, &dispparams, NULL, NULL, NULL); return hr; }
4. JS調用
function test_set_first() { var array = new Array(0, 1, 2, 3); try { var obj = document.getElementByIdx_xx_x("obj"); obj.ArrayModiy(array); alert("first element: " + array[0]); } catch (e) { alert("JS ERROR: " + e.message); } }
5. 測試執行
原數組:{0, 1,2,3} 修改后:{123,1,2,3}
(七)增加數組內容
1.在COM中無法向JS中一樣,直接增加數組元素。只能使用屬性、方法的方式訪問數組對象,並以此產生增加數組元素的效果。
2.JS的Array中包含push( )、 pop( )兩個方法,用於在數組尾部增減元素。在COM中需要增減元素時,可通過IDispatch:: Invoke( )接口調用 "push"、"pop"方法來實現。 3.COM中C++定義
STDMETHODIMP CJsAtl::AddNewElement(VARIANT vArray) { AddArrayElement(vArray.pdispVal, 123); // 增加元素,值為 123 return S_OK; } // **************************************************** // 向js數組中增加元素 // **************************************************** HRESULT AddArrayElement(IDispatch* pDisp, int value) { HRESULT hr = 0; DISPID dispid[2] = {0}; CComBSTR funcName(L"push"); hr = pDisp->GetIDsOfNames(IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, dispid); if (FAILED(hr)) return hr; DISPID dispidNamed = DISPID_UNKNOWN; DISPPARAMS params; params.rgdispidNamedArgs = NULL; params.cArgs = 1; params.cNamedArgs = 0; params.rgvarg = new VARIANTARG[1]; params.rgvarg[0].vt = VT_I4; params.rgvarg[0].intVal = value; hr = pDisp->Invoke(dispid[0], IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); return hr; }
4.JS調用
function test_add_element() { var array = new Array(0, 1, 2, 3); try { var obj = document.getElementByIdx_x("obj"); obj.AddNewElement(array); alert("length: [" + array.length + "] " + array[array.length - 1]); } catch (e) { alert("JS ERROR: " + e.message); } }
5.測試執行 原數組:{0,1,2,3} 增加后:{0,1,2,3,123}
(八)以數組傳參方式,JS調用S4Execute( )
1.本例展示如何在JS中執行精銳4鎖內程序,且以數組方式處理參數。
2.本例在Execute傳參時,直接以整形數組表示字節數組,而不再需要Hex字符串形式,使得JS端接口更加直觀。
3.JS代碼
var obj = document.getElementByIdx_x("obj"); var deviceID = "123"; var userPin = "12345678"; var fileID = "0001"; var inBuff = new Array(1, 2, 3, 4); var outBuff = new Array(0, 0, 0, 0); var ret = 0; try { ret = obj.OpenLock(deviceID); ret = obj.ChangeDir("\"); ret = obj.VerifyPin(userPin); ret = obj.Execute(fileID, inBuff, outBuff); ret = obj.Close(); } catch (e) { alert("JS Exception: " + e.message); } // JS數組操作,打印結果 var str = ""; for (var i = 0; i < outBuff.length; i++) str += " " + outBuff[i]; alert(str);
4.ActiveX代碼
SENSE4_CONTEXT g_ctx = {0}; //全局變量保存當前打開的ctx // 打開設備,以設備ID作為篩選條件,若設備ID指定為空串,則打開第一把鎖 STDMETHODIMP CS4ActiveX::OpenLock(BSTR deviceID, LONG* retVal) { SENSE4_CONTEXT * pctx = NULL; unsigned long ret = 0; unsigned long size = 0; unsigned long devCount= 0; unsigned long i = 0; char bDeviceID[9] = {0}; char bUserPin[9] = {0}; S4Enum(NULL, &size); if (size == 0) { *retVal = S4_NO_LIST; goto cleanup; } pctx = (SENSE4_CONTEXT*) malloc(size); ret = S4Enum(pctx, &size); if (ret != S4_SUCCESS) { *retVal = ret; goto cleanup; } // 獲取ascii格式的設備ID WideCharToMultiByte(CP_ACP, 0, deviceID, SysStringLen(deviceID), bDeviceID, 9, NULL, NULL); // 遍歷,尋找deviceID為指定值的設備 devCount = size / sizeof(SENSE4_CONTEXT); for (i = 0; i < devCount; i++) { if (strlen(bDeviceID) == 0) // 未指定設備ID,返回第一把鎖 { break; } if (0 == memcmp(bDeviceID, pctx[i].bID, 8)) { break; } } // 沒有找到 if (i == devCount) { *retVal = S4_NO_LIST; goto cleanup; } memcpy(&g_ctx, &pctx[i], sizeof(SENSE4_CONTEXT)); ret = S4Open(&g_ctx); if (ret != S4_SUCCESS) { *retVal = ret; goto cleanup; } *retVal = S4_SUCCESS; cleanup: if (pctx) { free(pctx); pctx = NULL; } return S_OK; } STDMETHODIMP CS4ActiveX::ChangeDir(BSTR dir, LONG* retVal) { char bDir[20] = {0}; WideCharToMultiByte(CP_ACP, 0, dir, SysStringLen(dir), bDir, 20, NULL, NULL); *retVal = S4ChangeDir(&g_ctx, bDir); return S_OK; } STDMETHODIMP CS4ActiveX::Execute(BSTR fileID, VARIANT inBuff, VARIANT outBuf, LONG* retVal) { char bFileID[5] = {0}; BYTE * bInBuff = NULL; BYTE * bOutBuff = NULL; int inBuffSize = 0; int outBuffSize = 0; unsigned long size = 0; unsigned long ret = 0; int i = 0; int tmp = 0; GetArrayLength(inBuff.pdispVal, &inBuffSize); GetArrayLength(outBuf.pdispVal, &outBuffSize); if (inBuffSize > 0) bInBuff = (BYTE*) malloc(inBuffSize); if (outBuffSize > 0) bOutBuff = (BYTE*) malloc(outBuffSize); for (i = 0; i < inBuffSize; i++) { GetArrayNumberOfIndex(inBuff.pdispVal, i, &tmp); bInBuff[i] = (BYTE)tmp; } WideCharToMultiByte(CP_ACP, 0, fileID, SysStringLen(fileID), bFileID, 5, NULL, NULL); ret = S4Execute(&g_ctx, bFileID, bInBuff, inBuffSize, bOutBuff, outBuffSize, &size); if (ret != S4_SUCCESS) { *retVal = ret; return S_FALSE; } for (i = 0; i < size; i++) { SetArrayNumberOfIndex(outBuf.pdispVal, i, bOutBuff[i]); } return S_OK; } STDMETHODIMP CS4ActiveX::VerifyPin(BSTR userPin, LONG* retVal) { unsigned char bUserPin[9] = {0}; WideCharToMultiByte(CP_ACP, 0, userPin, SysStringLen(userPin), (char*)bUserPin, 9, NULL, NULL); *retVal = S4VerifyPin(&g_ctx, bUserPin, 8, S4_USER_PIN); return S_OK; } STDMETHODIMP CS4ActiveX::Close(LONG* retVal) { *retVal = S4Close(&g_ctx); return S_OK; }