AX Miscellaneous

From Team Developer SqlWindows Wiki
Jump to: navigation, search

ActiveX/COM/Applets/JScript


Contents


Pointer2.png How to include an attachment with Outlook Automation Pointer.png

Including Attachments within Outlook eMail automation is not immediatley obvious .

Once you have created the Mail Item , you need to create an 'Attachment' object using 'PropGetAttachments' to actualy create it, before you 'Add' Attachments objects to the Mail Item.

!
!   Create Outlook object
    If NOT SalActiveXGetActiveObject( objOutlookApplication, 'Outlook.Application' )
	Set bOk =objOutlookApplication2.Create()
	If bOk
		Set bOk = objOutlookApplication2.PropGetApplication( objOutlookApplication )
	If bOk
		Set bOk = objOutlookApplication.ActiveExplorer( objOutlookExplorer )
	If bOk
		Set bOk = objOutlookApplication.GetNamespace( 'MAPI', objOutlookNameSpace )
	If bOk
		Set bOk = objOutlookNameSpace.GetDefaultFolder( Outlook_OlDefaultFolders_olFolderInbox, objOutlookMAPIFolder )
	If bOk
		Set bCreated = TRUE

!  Create Mail Item
        If bOk
	        Set bOk = objOutlookApplication.CreateItem( Outlook_OlItemType_olMailItem, objOutlookMailItem )

! Set Mail Item properties
        If bOk
	        Set bOk =  objOutlookMailItem.PropSetTo(sTo)
        If bOk
	        Set bOk =  objOutlookMailItem.PropSetSubject(sSubject)
        If bOk
	        Set bOk =  objOutlookMailItem.PropSetBody(sBody)

! 'Get' the Attachments object to create it !
        Call objOutlookMailItem.PropGetAttachments( Attachments )

!  Set the Varients so they can be used in the Attachments.Add
        Call AttachmentFile.SetString( 'c:\\AttachmentFile.docx' )
        Call AttachmentType.SetNumber( Outlook_OlAttachmentType_olByValue, VT_I1 )
        Call AttachmentPos.SetNumber( 1, VT_I1  )

!  Add the Attachment object created using PropGetAttachments() earlier, to the Mail Item
        Call Attachments.Add( AttachmentFile, AttachmentType, AttachmentPos, AttachmentFile , Attachment )


Sample can be downloaded here:
Down.png Including Attachments in Outlook Automation.zip


Pointer2.png How to disable internal TD error messages Pointer.png

You can disable the TD error messages when errors occur while calling ActiveX/COM functionality.

While having the internal messages off, you have to code custom error handling.

   ! Set internal error messages OFF
   Call SalActiveXAutoErrorMode( FALSE )
   ! Do ActiveX/COM functionality here...
   ...
   ! Set internal error messages ON
   Call SalActiveXAutoErrorMode( TRUE )


Pointer2.png Copy/Paste of ActiveX objects results in : ActiveX object creation failed Pointer.png

Sometimes, on specific ActiveX objects, it is impossible to copy/paste the object from one form to another.
The original object works, but a copy to a new form results in a "ActiveX object creation failed" error at runtime.

Another issue which could emerge is that in the IDE the events are not shown. Even copy/paste of implemented events
of the original ActiveX object to another one could lead to a compile error "Undefined Event Name".

This is due to a TD bug, caused by the fact that the pasted copy of the ActiveX object looses information which is needed
to correctly work in the IDE and at runtime.
But there is a workaround which could solve this annoying issue.


When a new ActiveX object is placed on a form, TD creates an invisible block of information within the object in the source.


Here an example. Below you see a form window on which an ActiveX object is placed (named axInternetExplorer)


ActiveXOnForm1.gif


When you open the source in notepad, and look for axInternetExplorer, you will find something like this:

.head 2 +  Contents
.head 3 +  ActiveX: axInternetExplorer
.data RESOURCE 5 0 1 4270659340
0000: 000A000009020000 0000000000000000 020000D0CF11E0A1 B11AE1F8000000FF
0020: 003E000300FEFF09 00F0060000000F01 000000F601000110 0000020000002601
0040: 00FEFFFFF8000000 F8FFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
0060: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
0080: FF40FDFFFFFFFEFF FF66FEFFFEFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
00A0: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
00C0: FFFFFFFFFFFFFFFF FFFFFFFF2052006F 006F740081004500 6E0074C072007900
00E0: 0000FFFFFFFFFFA0 16000500FFFF4F01 00000061F956880A 34D01100A96B00C0
0100: 4FD705A2F8000000 0FA068897600779B C90103000000F0C0 000000284A004545
0120: 5000085300724300 4F00C24F4C000000 FFFFFFFFFF821602 01FFFFFFFFFA0000
0140: FFFFFFFFF09C0000 00FFFFFFFFFFFFFF FFAFFFFFFFFA0000 FFFFFFFFFFFFFFFF
0160: FFFFFFFFFFAFFFFF FFFA0000FFFFFFFF FF460100020000F6 FEFFFFFFFFFFFFFF
0180: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
01A0: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 044C0000C51A0000
01C0: E06515000000FFFF 6F4C00FFFFFF0F08 0000006F4C00D001 1402000076C00046
01E0: F080000000FFFFFF FFF4010000FFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
0200: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFF
.enddata
.head 4 -  Class Child Ref Key: 0

Just after the line ".head 3 + ActiveX: axInternetExplorer" you see a data block starting from .data up to .enddata.
In between there are a bunch of hex codes. This block holds information needed for the IDE and at runtime to reference the ActiveX object correctly.
What those codes mean and how to get the info from it is unknown to me.


Now, what happens when you copy the ActiveX object from one form to another. Just have a look with notepad at the location where
the newly copied object is placed:

.head 2 +  Contents
.head 3 +  ActiveX: axInternetExplorer
.head 4 -  Class Child Ref Key: 0

As can be seen, the data block is not there, so it is lost in the copy/paste process.
This results in faulty IDE behaviour of the object (eg events are missing) or at runtime the object can not be created.


To solve this you need to manually copy/paste the missing data block from a working ActiveX object (same class) to the faulty object using notepad (or other text editor).
Copy the entire .data line up until .enddata line and place it right under the ActiveX:<<name>> line.
If done right, the newly copied ActiveX object will work like its original.


Pointer2.png How to generate a GUID Pointer.png

Creating a GUID programatically can be done in two ways.

First, you may use the CDK for this. See:

MediaWikiLink.png How to generate a GUID using CDK


Since TD is UNICODE-able, you may also call some of the OLE-APIs directly.

You have to import the following functions:


Library name: ole32.dll
  Function: CoCreateGuid
    Description: Creates a GUID, a unique 128-bit integer used for CLSIDs and interface identifiers.
                 *Returns: S_OK - The GUID was successfully created.
    Export Ordinal: 0
    Returns
      Number: DWORD
    Parameters
      structPointer
        Receive String: byte[16]
          ! Pointer to the requested GUID on return.
  Function: StringFromGUID2
    Description: Converts a globally unique identifier (GUID) into a string of printable characters.
                 *Returns: 0 (zero) - Array at lpsz is too small to contain a string representation of a GUID.
                  Non-zero value - The number of bytes (not characters) in the returned string, including the null terminator.
    Export Ordinal: 0
    Returns
      Number: INT
    Parameters
      structPointer
        String: byte[16]
          ! GUID to be converted.
        Receive String: LPWSTR
          ! Pointer to a caller-allocated string variable to contain the resulting string on return.
        Number: INT
          ! Number of characters available in the buffer indicated by lpsz.

After that, two simple calls give you a new GUID.

! Create a new GUID
Call SalSetBufferLength( createdGUID, 16 )	! a GUID has 16 bytes (= 128 bits)
Call CoCreateGuid( createdGUID )
!
! Transform to string
Call SalSetBufferLength( createdGUIDString, 100 )
Call StringFromGUID2( createdGUID, createdGUIDString, 50 )

Down.png CreateGUID_TD52.zip


Pointer2.png Use applets from Team Developer applications: Geogebra demo Pointer.png

The article describes how you can use (Java) applets which are running in a WebBrowser from Team Developer applications.
I needed to call the public methods of a 3rd party applet to be able to integrate it into the TD application.

As applets are running inside the browser, how do you actually use the methods of that API?

To show how to do this I have chosen an applet API which is widely used and is free: Geogebra !
It is software to visualize mathematics and physics and can be used for demonstration or educational purposes.

GeogebraDemo.png


Below, the link will give you the full description in a separate document. Read it for more details on applet integration in TD:
Down.png UseAppletsFromTD.doc


You can also watch a video of the Geogebra demo (running in TD 6.1):
Video.png TD_GeogebraApplet_Demo.wmv


And the demo application can be found here: (TD 2.1 and above)
Down.png TD_GeogebraApplet_Demo.zip


Pointer2.png Use JScript from Team Developer applications: Google Maps demo Pointer.png

JavaScript/JScript (JS) is an interpreted computer programming language.
It was originally implemented as part of web browsers so that client-side scripts could interact with the user, control the browser, communicate asynchronously, and alter the document content that was displayed.
More recently, however, it has become common in both game development and the creation of desktop applications.


So, as this is used for client-based scripting in web-pages, is it possible to execute JScript functionality from TD?

Have a look at Google Maps as example. Using a web-browser, you can show and manipulate geographic maps.


Web.png Google Maps


Using JScript on a custom web page, Google Maps can be called to display specific information, calculate routes etc etc.


What this article will explain how you can integrate the Google Maps GUI with a TD Form Window and manipulate it using JScript, called from TD code.


First a screenshot of a TD integration example:


GoogleMapsSample.png


The GUI consists of two parts, the Google Maps 'map' showing the visuals.
Below some fields and buttons to manipulate the map.
Basically, the feature is that using TD code we call the Google Maps API using JScript.


What we need first is a HTML page which defines a Google Map.
This HTML page is loaded in the WebBrowser control which we place on a TD FormWindow.
An then we access the Google Maps API, using the scripting features the WebBrowser control offers.


Lets have a look on the HTML page:

<html>
 <head>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
  <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
  <script type="text/javascript">

   var map
   var myOptions
   var marker
   var infowindow

   function initialize()
   {
     var latlng = new google.maps.LatLng(52.032654, 4.707180);
     myOptions = {
       zoom: 13,
       center: latlng,
       mapTypeId: google.maps.MapTypeId.ROADMAP,
       navigationControl: false,
       mapTypeControl: false,
       scaleControl: false
     };
     map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
     infowindow = new google.maps.InfoWindow();
   }

  </script>
 </head>

 <body onload="initialize()">
  <style type="text/css">
   html,body {
   height:100%;
   width:100%;
   overflow:auto;
   }
  </style>
   <div id="map_canvas" style="float:left;width:100%; height:100%"></div>
   <div id="directionsPanel" style="float:right;visibility:hidden;width:50%;height:100%"></div>
   <input id="sVar1" type="hidden" value="">
   <input id="sVar2" type="hidden" value="">
   <input id="sVar3" type="hidden" value="">
 </body>
</html>

As can be seen, the Google Maps API is initiated using:

map = new google.maps.Map( ... )


The object (variable) "map" is now initialized to the Google Map.
Then using JScript, we can set the zoom level of the map for instance:


   map.setZoom( "8" );


The Map will zoom to level 8.
This is done on the webpage itself, but how to set the zoom level from TD?


We need to place a MS WebBrowser control on a TD FormWindow first.
You have to use the ActiveX Explorer and generate the classes for SHDocVw_WebBrowser and MSHTML_IHTMLDocument.


Then drop the SHDocVw_WebBrowser on a FormWindow.


When the application starts, we need to load the specific local HTML file, which initializes the Google Maps API.
When loaded, the document object and the ParentWindow object needs to be fetched.


! Open the HTML file in the webbrowser
Call uVar.MakeOptional(  )
If axWebbrowser.Navigate( "file:///" || wsRuntimeDir || psFile, uVar, uVar, uVar, uVar )
	! Get the HTML contents as document object
	!
	If axWebbrowser.PropGetDocument( wuDocument )
		! Get the parent of the document (there the Exec script method is present)
		!
		If wuDocument.PropGetparentWindow( wuParentWindow )
			Set bOk = TRUE
		Else
			Call wuDocument.Detach(  )


The WebBrowser control is able to execute JScript using the method execScript.


   execScript(code, language)

(As of InternetExplorer 11, the execScript method is replaced by the method eval( code ) )


This method is part of the class MSHTML_IHTMLWindow, which is fetched in the TD code above (as variable wuParentWindow)
So, having this we can execute a piece of JScript code within the WebBrowser document.
For instance, if we want to set the zoom level, we can do this in TD:


   Call wuParentWindow.execScript( 'map.setZoom( "8" );', 'JavaScript', uReturnVar )


It is even possible to specify complete blocks of JScript code which is then executed, like:


Call wuParentWindow.execScript( "
		var myLatLng = new google.maps.LatLng(0, -180);
		var myOptions = 
		     {
		        zoom: 2,
		        center: myLatLng,
		        mapTypeId: google.maps.MapTypeId.TERRAIN
		      };
		
		map = new google.maps.Map(document.getElementById(\"map_canvas\"), myOptions);
		
		var flightPlanCoordinates = [
		        new google.maps.LatLng(37.772323, -122.214897),
		        new google.maps.LatLng(21.291982, -157.821856),
		        new google.maps.LatLng(-18.142599, 178.431),
		        new google.maps.LatLng(-27.46758, 153.027892)
		    ];
		var flightPath = new google.maps.Polyline({
		      path: flightPlanCoordinates,
		      strokeColor: \"#FF0000\",
		      strokeOpacity: 1.0,
		      strokeWeight: 2
		    });
		
		   flightPath.setMap(map);
		
		", "JavaScript", uRetVar )

The code above will draw a red polygon line from the USA to Australia in the map.
So, very advanced stuff can be done by coding the correct JScript sourcecode and pass it to the execScript method.


By concatenating values from TD code (from datafields, comboboxes, variables etc) into the JScript code we can call JScript using dynamic data.


But what about getting values back to TD? We can use a trick for that. When we define fields in the HTML code, we can assign values to them.
For instance, we can call this piece of JScript code which gets the latitude of a specific location and stores the value in the field "sVar1":


   Call wuParentWindow.execScript( "document.getElementById('sVar1').value = results[0].geometry.location.lat();" .... )


The field can be queried by looking for the HTML document element by its name and then fetching the value from it, like this:


If wuDocument.getElementById( "sVar1", uElement )
	Call uElement.getAttribute( "value", 2, uVariant )
	Call uVariant.GetString( sValue )

If you are using Internet Explorer in a version greater than 7.0 and do not wish to use compatibility mode, you can fetch the value of an element by accessing its innerText property instead:

If wuDocument.getElementById( "sVar1", uElement )
	Call uElement.PropGetinnerText( sValue  )


How about a signal from JScript so that TD can react on it? For instance, when a method is called which takes time to complete or you want to react on a specific event, like clicking on the map.


Well, also a trick. We use the messages from the WebBrowser control to signal back to TD.
When we change the title of the document, TD gets the event SHDocVw_TitleChange:


   ActiveX: axWebbrowser
  	Message Actions
		On SHDocVw_TitleChange
			Parameters
				String: Text
			Actions
				Call OnTitleChange( Text )


So, when we change the title from JScript, and specify a predefined title, we can react on this in TD.
In JScript:


document.title = \"GEOCODE1_FINISHED\";


In TD:


If Title = "GEOCODE1_FINISHED"
   ...
   ! Get values from the document we are interested in
   ...


I can imagine this info will raise questions. So the best way is to inspect a working sample and see how it all fits together.


You can download the Google Maps demo TD sources which works on ANSI and UNICODE TD versions here:
Down.png GoogleMaps JScript Sample.zip


Pointer2.png GetObject in Team Developer: VBScript workaround Pointer.png

You might have encountered the use of GetObject in other programming languages like VBA or VBScript.
The function returns a reference to a COM object provided by an ActiveX component.


It is widely used in scripting for several diverse usages.
Have a look at this VBScript as example:


Option Explicit
Dim objWMIService, objComputer, colComputer, strComputer 

Set objWMIService = GetObject( "winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2" ) 
Set colComputer = objWMIService.ExecQuery( "Select * from Win32_ComputerSystem" ) 

For Each objComputer in colComputer
               Wscript.Echo objComputer.UserName & " is logged on"
Next


The sample gets the username of the logged on user on the current system.


Another example, now getting the groups of the user from LDAP (Active Directory).


Option Explicit
Dim objUser ,colGroups, objGroup 

Set objUser = GetObject("LDAP://CN=Dave Rabelink,OU=Finance,DC=fabrikam,DC=com")
Set colGroups = objUser.Groups
For Each objGroup in colGroups
    Wscript.Echo objGroup.CN
Next


There are a lot of samples on the internet to get information from the system like this.
And many more usages which require GetObject.


Unfortunately, Team Developer does not have a similar GetObject functionality.
So trying to port samples like this to native TD will be difficult.


But a trick makes it possible to have GetObject working from TD which opens a world of possibilities within native TD.


With the help of Microsoft Script Control we are able to create a wrapper to get the COM objects which are returned by GetObject into TD.


The MS Script control is a COM interface to be able to run scripts, like VBScript.
From any environment supporting COM/ActiveX you are able to start script functions, TD included.
A nice feature of this script control is that you can pass objects to the script, but for our trick, the script can also pass back objects to the calling application.


Objects are in all cases COM objects. The data communication is done using dynamic COM instances.


So what this means is that we can execute a script function which returns a COM object. The returned object can then be used in TD.


The global overview what we need here:


  • Add a VbScript function to the MS Script Control from TD which accepts the needed parameters for GetObject.
  • The VBScript function performs the GetObject call. The resulting object is then returned.
  • TD receives the COM object returned by the VBScript function and is mapped to the needed COM interface class
  • Use the COM object within TD by calling the available methods.


Ok, let’s start with the Microsoft Script Control:


Microsoft Script Control


First we need to generate the ActiveX library (apl) using the ActiveX Explorer within the TD IDE


AXExplorer MSScript.png


When the library is generated, the class we need is


COM Proxy Class: MSScriptControl_ScriptControl


Using this you are able to run a script, for example VBScript or JavaScript by adding the script code to the control and execute it.
The script code is just a string which can be hardcoded in your application or the script is loaded from a file and then passed as string to the control.


MSScript EvalMethod.png


This is how to use the MS script control to execute a VBScript function which as an example will display a messagebox:


! First create the control
If uMSScriptControl.Create(  )
   ! Now set the script language to use
   Call uMSScriptControl.PropSetLanguage( 'VBScript' )
   
   ! Create a string with valid VBscript code, as example a function to display a messagebox
   Set sScript = '
Function ShowMyMessagebox()
  MsgBox "Please Click OK"
End Function'

   ! Add the VBScript code to the control
   Call uMSScriptControl.AddCode( sScript )

   ! And finally, execute the defined function of the just added script
   Call uMSScriptControl.Eval( "ShowMyMessagebox", uVarReturn )


When you execute this, the messagebox will be shown which is created within the VBScript function.


The nice thing about the Eval method of the script control is that the result of the script you call will be passed back.
The second parameter of the Eval function is a Variant object which can contain any datatype.
As an example, here a VBScript function which returns a string.
(only the script and Eval part is displayed below)


  
   ! Create a string with valid VBscript code, as example a function which returns a string
   Set sScript = '
Function MyStringFunction()
  MyStringFunction = "This is a text returned by the function"
End Function'

   ! And finally, execute the defined function of the just added script
   Call uMSScriptControl.Eval( "MyStringFunction", uVarReturn )


(The return value of a VBScript function is to set a value to the name of the function)


When you execute Eval, the second parameter, uVarReturn, contains the returned string.
Because the return object is a Variant, you need to extract the string using this:


  
   ! And finally, execute the defined function of the just added script
   Call uMSScriptControl.Eval( "MyStringFunction", uVarReturn )
   Call uVarReturn.GetString( sReturn )


The sReturn variable, defined as String, will contain the returned string of the VBScript function.


Any datatype can be passed back. Obviously the "normal" datatypes as strings and numbers, but also complex objects.


Have a look at this VBScript code. It creates a custom object (from a custom class) and returns the object:


Class Dog
   Public Name
   Public Color
   Public Breed
End Class

Function MyFirstDog( )

	Set myDog = New Dog

	myDog.Name = "Rover"
	myDog.Color = "Brown"
	myDog.Breed = "Beagle"	

	Set MyFirstDog = myDog
End Function


Ok, looking the code above we see that there is a custom class Dog having some attributes.
The function creates a new object of this class and sets the attributes.
Then it returns the object (by setting the function name to the object).


When we use the script above and execute the function


  
   ! And finally, execute the defined function of the just added script
   Call uMSScriptControl.Eval( "MyFirstDog", uVarReturn )
   Call uVarReturn.GetObject( uMyDog )


The uVarReturn, which is a Variant, holds the object which was created by the VBScript function.
So it has an object of type Dog and has all the attributes which were set by the script.


But the object is a custom one. The class was defined within the script.
How do we access the attributes when we have the object in uVarReturn?


Well, you have to know that objects are passed between the script and the caller (in our case the TD application) as a dynamic COM object.
TD is able to use COM objects as we know.


Normally, we generate all needed COM classes from a component using the ActiveX Explorer.
TD will inspect the interface of the component and generate the TD code to use the component.
The result is an ActiveX library having all methods and classes needed.


But here, we do not have a component. It is a dynamic object which is created within the VBScript.
So there is no way to use the ActiveX Explorer to generate the classes and methods for us.
We have to create it manually.


To do this you need to know how TD creates an ActiveX library. Just by inspecting examples of generated libraries, we can see what TD needs to use the classes and methods.


This article will not explain in detail how TD creates the ActiveX classes and methods.
For our example here, how to use the Dog object in TD, have a look at following TD code.
It shows how to access one of the attributes of the Dog object, the attribute Dog.Name string.


Functional Class: cfcDog
   Derived From
      Class: Object
   Class Variables
   Instance Variables
   Functions
      Function: PropGetName
         Description:
         Returns
            Boolean:
         Parameters
            Receive String: returnValue
         Local variables
            Boolean: tmpret
         Actions
            Set tmpret = __ObjectInvoke("Name", INVOKE_GET)
            Call __ObjectPopString(-1, returnValue)
            Call __ObjectFlushArgs()
            Return tmpret


First we have to create a functional class for our Dog, here named as cfcDog.
The class needs to be derived from Object, which is a base COM class defined in Automation.apl.


We know that the Dog class has an attribute Name which is a string.
Because it is an attribute (a variable), we need to instruct TD to get the value of the attribute.
This is done using the TD internal function __ObjectInvoke.
The first parameter is the name of the attribute, which is "Name".
The second parameter tells TD what "Name" actually is. Is it a method or an attribute?
Well, it is an attribute, so we need to pass INVOKE_GET constant telling TD to access the "Name" as attribute value.


The __ObjectInvoke function actually calls the object trying to fetch the "Name" attribute from the object.
When the object has a "Name" attribute, the return value of __ObjectInvoke will be TRUE.
When the object does not have the attribute, it returns FALSE.
(tmpret local variable)


In the case the attribute was found, the actual value of the attribute is put on an internal stack.
To get the value from the stack, we use __ObjectPopString(-1, returnValue).
Now, returnValue will contain the value, in this case the string.


So if the object contains Name = "Hello", after calling __ObjectInvoke and then calling __ObjectPopString, will give you "Hello" which is placed in the String variable returnValue.
Looking at more examples from ActiveX libraries which are generated using the ActiveX explorer you can find out how to process other datatypes and how to
use object methods input and output parameters.


You can manually create all kinds of classes, method and attribute access. Just see how TD generates similar objects and recreate it manually.


So, having created this functional class cfcDog, here is how to use it in our VBScript example.
The VBScript returns a Dog object. Next, how to implement in TD to get the Name attribute from the returned Dog object:


Local variables:
   cfcDog: uMyDog
   Variant: uVarReturn

   Call uMSScriptControl.Eval( "MyFirstDog", uVarReturn )
   Call uVarReturn.GetObject( uMyDog )
   Call uMyDog.PropGetName( sName )
   !
   ! sName = "Rover"


It is very straightforward. Difficulty is here to manually create the Dog class in TD and use the valid functions to access the object.


Now we know that returned objects are just COM objects and that we can use the object in TD as COM object, we can use this to implement a workaround for the initial issue.


How to use GetObject functionality in TD


Let’s revisit this VBScript:


Option Explicit
Dim objWMIService, objComputer, colComputer, strComputer 

Set objWMIService = GetObject( "winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2" ) 
Set colComputer = objWMIService.ExecQuery( "Select * from Win32_ComputerSystem" ) 

For Each objComputer in colComputer
               Wscript.Echo objComputer.UserName & " is logged on"
Next


Using the knowledge of the first part of this article, how do we get the object back in TD which is returned by GetObject in the VbScript?


Well, this should be something like this:


sScript = '
Function TDGetObject( )
  Dim objWMIService

  Set objWMIService = GetObject( "winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2" ) 

  Set TDGetObject = objWMIService
End Function
'


Using the Microsoft Script Control Eval function, calling


   Call uMSScriptControl.AddCode( sScript )
   Call uMSScriptControl.Eval( "TDGetObject", uVarReturn )
   Call uVarReturn.GetObject( uWMIService )


The uWMIService object in TD is actually the object from the VBScript function.
It is a fully working COM object, having all attributes and methods.


As explained earlier, the difficulty is now to create a functional class for uWMIService.
And it really depends on what type of class uWMIService object is.
Depending on the VBScript, the GetObject function can return any class. In the example, the class returned is a WMIService class.


We have now two options to create our class at TD side.
We can try to find if the class can be generated using ActiveX explorer, only when the class is present in a component which can be generated from.
Or, implement the needed methods manually.


As for WMIService, we are lucky. The class is present when we generate Microsoft WMI Scripting V1.2 Library.
It can be found in the ActiveX Explorer list of registered COM components.
Generating the library will give us a fully working class description.


Using the generated class we can do this:


   Call uMSScriptControl.AddCode( sScript )
   Call uMSScriptControl.Eval( "TDGetObject", uVarReturn )
   Call uVarReturn.GetObject( uWMIService )
   Call uWMIService.ExecQuery( "Select * from Win32_ComputerSystem", uWin32Computers )


The ExecQuery method is the COM method of the uWMIService class which returns us a list of Win32_Computer objects.
How to proceed is really dependent of the objects features. In the example above, we need to generate or manually create the uWin32Computers class.
And if that class needs other objects we need to generate or create them also.


Could be a huge task to get everything in place, but mostly you are only interested in just some of the methods or attributes.
It is not needed to create all methods, just do the ones you really need.


GetObject wrapper


Based on this info, we are able to use GetObject within TD. In fact all features which are implemented in VBScript can be ported to TD.
The only thing you really need is the GetObject workaround and the rest is a question of implementing all needed COM classes. Whether generated from ActiveX Explorer or doing it manually.


For your convenience, a working Internal TD Function is created which does a GetObject with just one call.


Here it is:


Boolean PALActiveXGetObject( psPathname, psClass, rpuObject, rpuErrorInfo )
   TD wrapper to VBScript GetObject functionality.
   This function calls GetObject within VBScript and gives back the COM object to TD

   See more info on MSDN VBScript GetObject:
   https://msdn.microsoft.com/en-us/library/kdccchxa(VS.85).aspx

   Parameter	Type		Omschrijving
   psPathname	String		[Optional] Path + filename
   psClass	String		[Optional] Classname
   rpuObject	Object		OUTPUT: The returned COM object given by GetObject
   rpuErrorInfo	OleErrorInfo	OUTPUT: COM error info if failed

Return waarde :
Boolean = TRUE when COM object was given back correctly. FALSE on any error


An example how to use this function. It displays the logged on user(s) of the current system. All executed in TD code.
The only non-TD part is the VBScript GetObject which is wrapped within the single PAL function.


If PALActiveXGetObject( '', "winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2", uService, uError )
   ! Get the collection
   If uService.ExecQuery1( "Select * from Win32_ComputerSystem", uWbemObjectSet )
      Call uWbemObjectSet.PropGetCount( nTotal )
      !
      While uWbemObjectSet.Next( uComputer )
         Call uComputer.PropGetUserName( sName )
         Call SalMessageBox( sName || " is logged on", "Msg", MB_Ok )


The PALActiveXGetObject function can be found in the sample which can be downloaded here:
Down.png WIKI_ActiveX_GetObject.zip


The PAL function and sample can be used starting from TD 2.1 and up.