rc.com
COM-Objekte können als Input-Objekte dem Skript übergeben werden. Ebenso können COM-Objekte im Skript erstellt werden. Dadurch ist es möglich auch aus serverseitigem Javascript-Code auf Funktionen zurückzugreifen, die per COM zur Verfügung gestellt werden.
Dies stellt eine OS-Seitige Erweiterung der Möglichkeiten in Javascript dar, die nur innerhalb des Serverscripting Verwendung finden kann.
Weitere Informationen zur Konvertierung spezieller COM-Typen zwischen COM und JS finden Sie hier: Behandlung spezieller COM-Typen. Als weitere Informationsquelle zu den Interna von COM und den Charakteristiken der einzelnen COM - Interfaces können Sie die entsprechende Fachliteratur oder die entsprechenden Dokumentationen von Microsoft heranziehen.
Erstellen
Ein COM-Objekt wird mit dem Konstruktor rc.com.ActiveXObject
erstellt:
Code
const FSO = new rc.com.ActiveXObject("Scripting.FileSystemObject");
FSO ist nun ein JS-Objekt mit den Methoden und Properties, das aus der TypeInfo von FileSystemObject entstanden ist. Als Konstruktorname wird Name der COM-Klasse verwendet, meistens ist es der Name des Interfaces.
Code
const FSO = new rc.com.ActiveXObject("Scripting.FileSystemObject");
rc.console.log(FSO); // => [object IFileSystem3]
$type
Jedes COM-Objekt hat die Property $type
. Als Wert wird COM-Interface Name zurück geliefert.
Diese Property kann mit oder ohne Attribute DontEnum
erstellt werden. Das wird in oxv8.cfg
geregelt:
TypeEnumerable = 1
Allerdings kann auch das Skript selbst dies steuern, indem es (vor dem ersten new ActiveXObject
für die COM-Klasse) die entsprechende Property mit rc.settings
setzt:
Code
rc.settings.TypeEnumerable = false;
const FSO = new rc.com.ActiveXObject("Scripting.FileSystemObject");
rc.console.log(JSON.stringify(FSO,null,3));
/*
{
"Drives": {
"_NewEnum": {},
"Count": 3
}
}
*/
versus
Code
rc.settings.TypeEnumerable = true;
const FSO = new rc.com.ActiveXObject("Scripting.FileSystemObject");
rc.console.log(JSON.stringify(FSO,null,3));
/*
{
"$type": "IFileSystem3",
"Drives": {
"$type": "IDriveCollection",
"_NewEnum": {
"$type": "IUnknown"
},
"Count": 3
}
}
*/
rc.com.release(o)
Mit diesem Aufruf kann man explizit den COM-Inhalt des Objektes freigeben, ohne zu warten bis das durch GC passiert:
IDispath, IUnknown, IEnumVariant → Release()
Variant → VariantClear()
SafeArray → SafeArrayDestroy()
Meistens ist es nicht notwendig, allerdings ist es in seltenen Fällen die einzige Möglichkeit, die Objekte explizit freizugeben. Nach diesem Aufruf stehen die Objekte zur weiteren Verwendung im Skript nicht mehr zur Verfügung.
Code
rc.settings.TypeEnumerable = false;
const FSO = new rc.com.ActiveXObject("Scripting.FileSystemObject");
rc.console.log(FSO);
rc.console.log(rc.com.native.__ptr(FSO));
rc.console.log(JSON.stringify(FSO,null,3));
rc.com.release(FSO);
rc.console.log(FSO);
rc.console.log(rc.com.native.__ptr(FSO));
rc.console.log(JSON.stringify(FSO,null,3));
/*
[object IFileSystem3]
0x0000000004AA8F90
{
"Drives": {
"_NewEnum": {},
"Count": 3
}
}
[object IFileSystem3]
0x0000000000000000
Uncaught Error: pIDispatch == nullptr. Already released?. File: apps-script. Line # 14:
rc.console.log(JSON.stringify(FSO,null,3));
--------------------^
at apps-script : 9 : 21
*/
TypeInfo
Mit dem Aufruf rc.com.typeInfo(x)
kann man die TypeInfo für COM-Objekt ermitteln:
Code
const FSO = new rc.com.ActiveXObject("Scripting.FileSystemObject");
const typeInfo = rc.com.typeInfo(FSO);
rc.console.log(JSON.stringify(typeInfo,null,3));
typeInfo
ist ein Array. Als erstes Element kommt die Typinformation des Objektes. Weitere Elemente beinhalten Typinformationen von weiteren Typen, die dabei vorkommen (Typen von Methoden Parameter etc.)
Output
[
{
"name": "IFileSystem3",
"guid": "{2A0B9D10-4B87-11D3-A97A-00104B365C9F}",
"tkind": "DISPATCH",
"flags": "DUAL NONEXTENSIBLE DISPATCHABLE",
"impl_types": "IDispatch",
"funcs": [
{
"memid": 10010,
"invkind": "propertyget",
"name": "Drives",
"parameters": [],
"return_type": {
"vt": "VT_PTR",
"ptr_type": {
"vt": "VT_USERDEFINED",
"ud": {
"kind": "DISPATCH",
"name": "IDriveCollection",
"guid": "{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}"
}
}
}
},
...
]
},
...
]
Damit kann man bei fehlender Doku der COM-Klasse analysieren, welche Properties das COM-Objekt besitzt, welche Parameter haben die Methoden usw.
Diese Information kann als Datei gespeichert werden. Dafür gibt es den Schalter AutoPrintTypeInfo = 1
in oxv8.cfg
. Ist dieser Schalter gesetzt, werden alle TypeInfos im Unterverzeichnis comtypes ausgegeben.
COM-Properties
COM-Properties werden als Eigenschaften von JS-Objekten abgebildet. Die TypeInfo legt fest, ob man diese Properties lesen (propget
) und ändern (propput
bzw. propputref
) kann.
Technisch sind COM-Properties normalerweise als die Methoden get_X
(hat keinen Eingabeparameter, hat einen Rückgabewert) und put(ref)_X
(hat einen Eingabeparameter, hat keinen Rückgabewert) implementiert.
Ob diese Methoden im Skript bei JS-Objekten zur Verfügung stehen, wird in oxv8.cfg
geregelt:
BindRawGet = 0/1
BindRawPut = 0/1
Das kann der Skript auch selbst regeln, indem am Anfang die Einstellungen in rc.settings
gesetzt werden:
Code
rc.settings.BindRawGet = true;
rc.settings.BindRawPut = true;
In diesem Fall ist es möglich, die get_X
/ put(ref)_X
Methoden im Skript direkt aufzurufen (put(ref)_X
bedeutet "put_X
bzw. putref_X
"):
Code
const a = o.get_X();
o.put_X(1);
Diese Aufrufe sind im Normalfall nicht notwendig, allerdings kann ein COM-Objekt sogenannte parametrisierte Properties haben. In diesem Fall hat get_X
mindestens einen Eingabeparameter und put(ref)_X
mehr als einen Eingabeparameter. Solche COM-Properties können mit JS-Eigenschaften nicht abgebildet werden, und sie können nur direkt per get_X
/ put(ref)_X
angesprochen werden. Bei parametrisierten Properties sind die get_X
/ put(ref)_X
Methoden immer vorhanden, d. h. unabhängig von den konfigurierten BindRawGet
/ BindRawPut
Werten.
Code
const a = o.get_X('param');
o.put_X('param', 1);
Falls entweder nur propput
oder propputref
vorhanden ist, wird die vorhandene Methode bei der Zuweisung der COM-Property verwendet. Falls beide vorhanden sind, dann wird das Verhalten in der Datei oxv8.cfg
durch den Schalter PropputrefUsage
geregelt (das kann auch im Skript am Anfang geregelt werden):
Code
enum class EPropputrefUsage
{
propput = 0,
propputref = 1,
byOperand = 2
};
Dies bedeutet:
propput
: es wird immerpropput
verwendetpropputref
: es wird immerpropputref
verwendetbyOperand
:Ist der Operand ein COM-Objekt (rechte Seite der Zuweisung) → es wird
propputref
verwendetIst der Operand kein COM-Objekt (rechte Seite der Zuweisung) → es wird
propput
verwendet
IDispatch::Invoke
Die COM-Methoden werden per IDispatch::Invoke
aufgerufen. Die Parameter werden als VARIANT
übertragen.
Die COM-Methode gibt vor (durch TypeInfo) welchen VT_xx
Typ dieses VARIANT
haben muss. Deswegen müssen JS-Variablen nach VARIANT
konvertiert werden, und der Rückgabeparater von VARIANT
nach JS-Variable. Die Regeln dabei s. im Kapitel conversion.
Rückgabewert hat immer Flags out,retval
und wird automatisch behandelt.
Ansonsten hat ein Parameter Flags - in
oder out
oder in,out
. Falls Flag out
vorhanden ist, muss dieser Parameter besonders behandelt werden. Js hat keine native out
Parameter, deswegen erwartet oxv8 bei out
Parameter ein JS-Objekt mit Eigenschaft value
. Daraus wird der in
Wert für in,out
Parameter genommen, und darein wird der out
Wert geschrieben.
Angenommen misc
ist ein Objekt mit Methode F_Swap
. Diese Methode hat zwei Parameter, beide sind als in,out
deklariert. Die Methode liest beide Werte (in
), tauscht sie um und schreibt zurück (out
)
Code
const p1 = { value: 1 };
const p2 = { value: 2 };
misc.F_Swap(p1,p2);
assert(p1.value == 2);
assert(p2.value == 1);
IEnumVARIANT
Manche COM-Klassen implementieren COM-Collections. In diesen Fällen ist die Property _NewEnum
vorhanden. Sie stellt jedoch kein IDispatch Interface sondern ein IEnumVARIANT Interface dar. Daraus kann nun ein JS-Objekt erstellt werden (durch Verwendung des new-Konstruktors rc.com.EnumVARIANT
), das folgende Methoden besitzt:
Next()
liefert ein JS-Objekt und verschiebt (falls nicht EOF) den Iterator um eine Position. Das JS-Objekt beinhaltet die Eigenschaft fetched
(true
/false
), und (falls nicht EOF) die Eigenschaft item
, die den Wert enthält.
Skip(integer)
verschiebt den Iterator
Reset()
setzt den Iterator auf den Anfang zurück
Code
function iterate() {
while (true) {
const next = enumerator.Next();
if (next.fetched === false) {
break;
}
print("next",JSON.stringify(next));
}
print("finally",JSON.stringify(enumerator.Next()));
}
const object = new rc.com.ActiveXObject("oxv8.Test_Enum");
// oxv8.Test_Enum ist eine COM-Collection mit 6 Elementen: 1,2,3,a,b,c
const enumVARIANT = object._NewEnum;
rc.console.log("object._NewEnum ==> " + enumVARIANT);
const enumerator = new rc.com.EnumVARIANT(enumVARIANT);
rc.console.log("new rc.com.EnumVARIANT(enumVARIANT) ==> " + enumerator);
iterate();
/*
object._NewEnum ==> [object IUnknown]
new rc.com.EnumVARIANT(enumVARIANT) ==> [object IEnumVARIANT]
next ==> {"item":1,"fetched":true}
next ==> {"item":2,"fetched":true}
next ==> {"item":3,"fetched":true}
next ==> {"item":"a","fetched":true}
next ==> {"item":"b","fetched":true}
next ==> {"item":"c","fetched":true}
finally ==> {"fetched":false}
*/
Erstellte COM-Objekte, die die Property _NewEnum
besitzen, werden dabei automatisch als JS iterable erstellt. Das bedeutet, sie besitzen die Methode [Symbol.iterator]
und können sowohl in for-of
Schleife als auch mit dem spread
-Operator verwendet werden:
Code
const object = new rc.com.ActiveXObject("oxv8.Test_Enum");
// oxv8.Test_Enum ist eine COM-Collection mit 6 Elementen: 1,2,3,a,b,c
rc.console.log("iterable ==> for-of");
for (const e of object) {
rc.console.log(e);
}
rc.console.log("iterable ==> skip 3 elements, spread");
const iterator = object[Symbol.iterator]();
iterator.next();
iterator.next();
iterator.next();
rc.console.log([...iterator]);
/*
iterable ==> for-of
1
2
3
a
b
c
iterable ==> skip 3 elements, spread
a,b,c
*/