i have an ActiveX Object (Master) and would like to invoke functions dynamically on it. To do this i use the apply() Function. But sadly the InternetExplorer tells me something along the lines of: "This Object doesn't support this Method". Can someone give me a hint what i could do?
(To test this you also could use a small flash object as Master and call "doSomething" instead of my specific "Initialize".)
function invoke(object, fnName, args)
{
return object[fnName].apply(object, args);
}
function test_it()
{
try{
Master = window.document["Master"];
}
catch(e){alert(e);}
var param = [1,"VC2"];
var ret = invoke(Master, "Initialize", param);
alert("got: "+ret);
}
For parsion, this is the apply() Function in action:
function Obj()
{
this.msg = function(a, b, c)
{
alert("msg: \n a: "+a+"\n b: "+b+"\n c: "+c);
return "hi";
}
return this;
}
function invoke(object, fnName, args)
{
return object[fnName].apply(object, args);
}
function test_it()
{
var obj = new Obj();
var ret = invoke(obj, "msg", [1, 2, 3]);
alert("got: "+ret);
}
i have an ActiveX Object (Master) and would like to invoke functions dynamically on it. To do this i use the apply() Function. But sadly the InternetExplorer tells me something along the lines of: "This Object doesn't support this Method". Can someone give me a hint what i could do?
(To test this you also could use a small flash object as Master and call "doSomething" instead of my specific "Initialize".)
function invoke(object, fnName, args)
{
return object[fnName].apply(object, args);
}
function test_it()
{
try{
Master = window.document["Master"];
}
catch(e){alert(e);}
var param = [1,"VC2"];
var ret = invoke(Master, "Initialize", param);
alert("got: "+ret);
}
For parsion, this is the apply() Function in action:
function Obj()
{
this.msg = function(a, b, c)
{
alert("msg: \n a: "+a+"\n b: "+b+"\n c: "+c);
return "hi";
}
return this;
}
function invoke(object, fnName, args)
{
return object[fnName].apply(object, args);
}
function test_it()
{
var obj = new Obj();
var ret = invoke(obj, "msg", [1, 2, 3]);
alert("got: "+ret);
}
Share
Improve this question
edited Sep 16, 2009 at 15:37
hobotron
asked Sep 16, 2009 at 15:25
hobotronhobotron
4721 gold badge4 silver badges16 bronze badges
5 Answers
Reset to default 3The problem with some of the host objects (i.e. any non-native objects) in IE (and not only IE) is that they don't inherit from Function.prototype
(and often neither from top level Object.prototype
). Some host objects that might look like functions actually have nothing to do with functions except that they can be called. The fact that these objects don't inherit from Function.prototype
means that they fail to be identified as functions with instanceof
operator; that their constructor is not referencing Function
; and that they lack all of the Function.prototype.*
methods, such as call
or apply
. Even their internal [[Class]] property might not be that of "Function", as it is with any native object (note that [[Class]] can be inferred from the result of Object.prototype.toString
value).
This is actually expected, since host objects are not required to implement many things that native objects do (as per ECMA-262, 3rd ed.). It is perfectly allowed for a host object to, say, throw error on method invocation (e.g. hostObject.hostMethod()
); or when passing it as an operand to standard operators like delete
(e.g. delete hostObject.hostMethod
). As you can see, it is also OK for callable host objects to NOT inherit from native Function.prototype
.
Such unpredictable (yet perfectly pliant) behavior is actually one of the main reasons why host objects augmentation is remended against.
But back to your call
problem : )
The thing about these "tricky" IE host objects is that they often implement internal [[Call]] method, and it is possible to invoke call
and apply
on them, although not directly.
Here's a pattern to emulate apply
invocation on an object that doesn't have it:
function f(){ return arguments };
Function.prototype.apply.call(f, null, [1,2,3]); // [1,2,3]
null
can be replaced with whatever context object should be called in, of course.
And an example of apply
invocation on host object that has no call
:
// should work in IE6, even though `alert` has no `call` there
Function.prototype.call.call(alert, window, 'test');
Applying it to your code
// Calls Master.initialize borrowing from Function.prototype
Function.prototype.apply.call(Master.initialize, Master, [1,"VC2"]);
I had this same problem and I solved it by piling a thunk function at run-time to unroll the right number of arguments (similar to the previous solution, but without the restriction that the ActiveX object handle has to be in a global variable).
varArgsThunkFunctionsCache = [];
function getVarArgsThunkFunction(arrayLength) {
var fn = varArgsThunkFunctionsCache[arrayLength];
if (!fn) {
var functionCode = 'return o[m](';
for (var i = 0; i < arrayLength; ++i) {
if (i != 0) {
functionCode += ','
}
functionCode += 'a[' + i + ']';
}
functionCode += ')';
fn = new Function('o', 'm', 'a', functionCode);
varArgsThunkFunctionsCache[arrayLength] = fn;
}
return fn;
};
function invoke(object, methodName, args) {
var fn = getVarArgsThunkFunction(args.length);
return fn(object, methodName, args);
};
Apparently IE's JS engine doesn't see ActiveX functions as JavaScript Function objects on which you can call apply()
. How about just doing an eval()
-- although ugly, it seems to be your only option.
function invoke(objectName, fnName, args) {
return eval(objectName + "." + fnName + "(" + args + ")");
}
Thanks kangax for your time and your extensive explanation! Sadly i could not get it working this way(it works for the alertbox though) But it led me to the idea to use a proxy-class. It's not the most elegant way because i have to provide every function from my object i want to use but it works AND it doesn't involve eval()!
function proxy(obj)
{
this.obj = obj;
this.Initialize = function(a, b)
{
return obj.Initialize(a, b);
}
}
function test_it()
{
var myMaster = new proxy(window.document["Master"]);
var ret = myMaster["Initialize"].apply(myMaster, [1, "VC2"]);
alert(ret);
}
Again, thanks for your time!
Just thought I'd mention that if you use the eval
method as Ates Goral said you need to be careful of string arguments in your array as they will be considered variable names, eg
function invoke(objectName, fnName, args) {
return eval(objectName + "." + fnName + "(" + args + ")");
}
invoke("Master", "Initialize", [1, "VC1"]);
the eval
will be passed the line
Master.Initialize(1,VC1)
which will throw an error if VC1 is not a defined variable. It might be best to "unroll" the array name instead of passing literals:
function UnrollArray(arrayname, length) {
var s = "";
for(var i = 0; i < length; i++) {
s += arrayname + "[" + i + "],";
}
return s.substring(0, s.length - 1); //remove the trailing ma
}
so invoke bees
function invoke(objectName, fnName, args) {
var unrolledarray = UnrollArray("args", args.length);
return eval(objectName + "." + fnName + "(" + unrolledarray + ");");
}
invoke("Master", "Initialize", [1, "VC1"]);
the eval
will then be passed
Master.Initialize(args[0],args[1]);