Passing array of bytes from ActiveX to javascript and vice versa
I need to pass data (byte array, i.e char*) from ActiveX object (using Visual C++ with ATL) to my javascript code (and vice versa). I've digged the Web for such problem and tried lots of solutions but have not succeeded. I've tried the followings:
- Converting char* to BSTR and pass it to javascript (JS), but my result in JS is "", due to the nature of my data is not string.
//in C++:
STDMETHODIMP CActiveXObj::f(BSTR* msg) // msg is the return value in ATL automation function
{
char *buffer; // byte data is stored in buffer
*msg = SysAllocStringByteLen((LPCSTR)buffer, bufferLen+1);
}
//////////////////////////////////////////////////////////////////////////
//in JavaScript:
var myobj= new ActiveXObject("IGCE.ActiveXObj");
var result = myobj.f(); // result = ""
- Pass safe array of byte data from C++
Could anyone 开发者_高级运维please give me the working code in its simplest form?
Thank you very much!
Kristin
// In *.idl file
[propget, id(0)] HRESULT ArrayProperty([out, retval] SAFEARRAY(VARIANT) *pArray);
[propput, id(0)] HRESULT ArrayProperty([in] SAFEARRAY(VARIANT) Array);
// Somewhere in javascript
function ax2js(axArray) {
return new VBArray(array).toArray();
}
function js2ax(jsArray) {
var dict = new ActiveXObject("Scripting.Dictionary");
for (var i = 0; i < jsArray.length; i++) {
dict.add(i, jsArray[i]);
}
return dict.Items();
}
function fooHandler() {
var ax = new ActiveXObject("My.My");
var ar = ax2js(ax.ArrayProperty);
ax.ArrayProperty = js2ax(ar);
}
You probably need to use a SAFEARRAY to pass the data. There is an ATL wrapper for this called CComSafeArray. Hopefully that will be enough for you to get started, if not then I'll dig out some code.
To the best of my knowledge (and in my experience), you can only use the following basic datatypes when talking to javascript:
- String
- Int
- Double
- Bool
- IDispatch*
Anything else doesn't seem to work. I've never tried using a SAFEARRAY, but I can suggest a possible alternative.
If you get a reference to the DOM window (I won't cover that here; if you don't know how, search and/or submit a new question and I can answer it there), you can use IDispatch to Invoke the method "Array" on the window, and it will return an IDispatch* for an empty javascript array. You can then Invoke "push" on the Array IDispatch* for each byte that you want to send to javascript as an int, and then return the IDispatch* of the Array as a return value from the method or property in question. You'll get the data in javascript as an array of integers, but each element is a byte and you can use it that way.
If you can suggest another way to use binary data in javascript (forget the activex control for a moment), I might be able to tell you how to return the data from the control that way.
This is essentially the method that FireBreath (open source plugin framework for IE and Firefox; http://www.firebreath.org) uses when you return a vector from a JSAPI (javascript scripted object) method to return the data to javascript as an array. You can use a similar (almost identical) method on NPAPI compatible browsers.
I understand this is a very old post, but I myself stumbled upon the same issue of passing binary data from ActiveX to Javascript and decided to present a solution based on taxilian's proposal.
Before that, I'd like to point out that it's also possible to build SAFEARRAY of binary data and send this object back to JS. The only problem is that VBScript has to be used to unpack this object, convert it to data type recognised only by JScript (Microsoft's Javascript dialect) that can than be used to build traditional JS array.
Without going into reason behind this solution (for that check taxilian's answer), here is a method that will build Javascript array within ActiveX control and return this array to JS.
/** NOTE: you have to include MsHTML.h header in order to access IServiceProvider,
IHTMLWindow2 and related constants. **/
IDispatch* CActiveX_TutorialCtrl::GetJSArrayObject(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
LPOLECLIENTSITE site = this->GetClientSite();
IServiceProvider* serviceProvider = nullptr;
site->QueryInterface(IID_IServiceProvider, reinterpret_cast<void**>(&serviceProvider));
IHTMLWindow2* window_obj = nullptr;
serviceProvider->QueryService(SID_SHTMLWindow, IID_IHTMLWindow2, reinterpret_cast<void**>(&window_obj));
DISPPARAMS disparam = { nullptr, nullptr, 0, 0 };
VARIANT ret_val;
DISPID dispid;
LPOLESTR method_name = L"Array";
HRESULT hr = window_obj->GetIDsOfNames(IID_NULL, &method_name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
hr = window_obj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &disparam, &ret_val, nullptr, nullptr);
if (ret_val.vt != VT_DISPATCH)
return nullptr;
VARIANTARG push_arg;
method_name = L"push";
hr = ret_val.pdispVal->GetIDsOfNames(IID_NULL, &method_name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (hr != S_OK)
return nullptr;
::VariantInit(&push_arg);
::VariantChangeType(&push_arg, &push_arg, 0, VT_I4);
for (int i = -10; i <= 10; ++i)
{
push_arg.intVal = i;
disparam.rgvarg = &push_arg;
disparam.rgdispidNamedArgs = nullptr;
disparam.cArgs = 1;
disparam.cNamedArgs = 0;
hr = ret_val.pdispVal->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &disparam, nullptr, nullptr, nullptr);
if (hr != S_OK)
return nullptr;
}
::VariantClear(&push_arg);
serviceProvider->Release();
window_obj->Release();
serviceProvider = nullptr;
window_obj = nullptr;
return ret_val.pdispVal;
}
Most of the code you see here is typical COM programming. Firstly, we retrieve a pointer to the client site where our control is hosted. Then, we QI (query interface) for the IServiceProvider which is an IE interface that implements many supported services. One of them is IHTMLWindow2 which is the type of window object in Javascript. Now that we have a pointer to our window object we can create an Array object. Array is just a method of IHTMLWindow2 object and in order to create a new array we have to invoke this function.
In order to invoke a method on COM object (and IHTMLWindow2 is just an interface implemented by some COM object), this object has to implement IDispatch interface that allows the user to call this object's method by using Invoke method. GetIDsOfNames method is used to retrieve a DISPID (dispatch id) of the Array method and then we finally create a new array by invoking Array method on our window_obj object. In the ret_val parameter (of type VARIANT), we will get a IDispatch* pointer representing our JS array.
It is obvious what to do next: use this pointer to get the DISPID of the push method and then fill the array by "Invoking" this method over and over. The sample function also shows how to build the DISPPARAMS and VARIANTARG object needed for the IDispatch::Invoke method.
Finally, we return the IDispatch* pointer from the method. JS will recognise this object as a native JS array because this is in fact its internal implementation.
精彩评论