rc.com

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 immer propput verwendet

  • propputref: es wird immer propputref verwendet

  • byOperand:

    • Ist der Operand ein COM-Objekt (rechte Seite der Zuweisung)  → es wird propputref verwendet

    • Ist 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 */