# COM Integration

(Redirected from COMPutProperty)

Requires Analytica Enterprise, Optimizer or ADE.

Analytica supports the Microsoft Component Object Model (COM) as a standard way to communicate with other Windows applications that support COM within the Windows operating system. These include all Microsoft Office applications, including Excel, and many other Windows applications. Analytica's support for COM lets your model call other applications that have a COM automation interface (known as the IDispatch interface). This means that your Analytica model can do just about anything with Microsoft Excel that you might want. You can also write your own COM server in C++, C#, VB, and a host of other languages to add new functions to Analytica.

This feature lets allow your Analytica model call other applications to provide or obtain information But it doesn't let other COM applications call Analytica. For that you must use ADE.

## COMCreateObject(name, server, flags, user, password)

The first step is to use function COMCreateObject(COMclass) to create a COM object as a new instance of an available COM class. If you want to access an Excel spreadsheet as a COM object, you can use SpreadsheetOpen to create an Excel::_Workbook COM object). Usually, you need provide only the first parameter, the name of the COM class. You rarely need to worry about the other parameters.

Parameters:

«name»
The name as a text value of the COM class from which you want to create an instance ("instantiate"). For example, "WScript.Shell" or "ADE4.CAEngine", or a clsid such as "clsid:72C24DD5-D70A-438B-8A42-98424B88AFB8".
«server»
(optional text) The name of the computer where the object will run. This lets you invoke (run) the COM object on a remote server, or it can be "localhost".
«flags»
(optional integer bitfield). Various esoteric flags. Some notable flags are:
0x1 = Allow in-process component (but be same bit-ness)
0x4 = Allow out-of-process component
0x40000 = Allow 32-bit component
0x80000 = Allow 64-bit component
0x100000000 = 2^32: Faster instantiation if component is guaranteed to instantiate quickly and reliably.

You can add flags together to combine them. For more flags, see Microsoft documentation on CLSCTX enumeration.

«user»
(optional text) the account name that the component will run under.
(optional text) the account password to obtain credentials for the «user» account.

Usually, the instantiated object runs on the same machine as the Analytica calling process, but you can use remote invocation, known as DCOM. This lets you set up parallel computations on multiple servers. If you don't specify the «server» parameter, it selects the target computer by the DCOMCNFG settings for that component. By running the DCOMCNFG program, or entering "DCOM Config" in the Microsoft Component Services, you can configure specific COM components to run on specific remote servers. The «server» parameter overrides that setting. The COM component will usually need to be installed on both machines.

The programs that implement COM objects can either run in-process or out-of-process and can be 32-bit or 64-bit programs. Some COM servers provide both in-process and out-of-process versions and some provide both 32-bit and 64-bit versions. If you don't specify any of the optional flags, it'll allow any possible combination that it finds to run (either 32- or 64-bit and in-process or out-of-process). You can use these flags to force a specific type to run. For example:

Normally, COMCreateObject allows Analytica to continue processing windows events -- for example, the user can press Ctrl+Break or "Stop", if the object takes too long to instantiate or hangs. But, this processing takes some computational overhead. You can switch off this overhead using the 2^32 bit «flags», but then Analytica will be unresponsive while creating the object and you won't be able to interrupt it. So, you should do this only if you need fast instantiation and are sure that your component instantiates quickly and reliably.

COMCreateObject("MyObject", flags:0x1 + 0x80000)

would force the creation of a MyObject in an out-of-process 64-bit server, whereas without the flags it might have created an in-process 32-bit instance. Some COM servers provide only one type. For example, ABODB classes can only be used in-process.

Not all combinations of flags are valid. Here are some basic constraints:

• A remote invocation (on a different machine) is automatically out-of-process.
• An in-process invocation must have the same bitness (32-bit or 64-bit) as the Analytica or ADE program you are using. (Out-of-process bitness can differ).
• You can only use 64-bit objects when you are running Windows 64-bit.
• If you specify neither «force64bit» nor «force32bit», it defaults to the same bitness as the calling process when both are available.

### Using the .NET libraries

You can use these COM integration features to instantiate and use .NET objects, including certain .NET libraries (subject to security considerations).

### Security

TBD. The user should be given control over whether COM objects are allowed to run, so that if you receive a model from someone else, it can't inflict arbitrary damage. Also, there should be restrictions on the ACP server as to what objects are allowed to run, and who can configure this.

### Run as impersonation

It is possible to run out-of-process COM objects under a different identity. This can be configured in DCOMCNFG, or you can specify the «user» and «password» parameters.

### Error handling

When you try to instantiate a COM object, it may fail with an error that terminates computation. There are situations where you don't want to terminate, but you might instead fall back on some non-COM-based algorithm instead. To do this, you can catch the error using the Try function:

Try(
COMCreateObject("MySearchComponent"),
Catch:
Null
)


### Ctrl+Break

While Analytica is instantiating the object, you can cancel the operation by pressing Ctrl+break, unless you specified the fast instantiation bit (2^32) in the «flags» parameter to COMCreateObject. When you set that bit, it avoids the overhead for setting of a thread to handle windows events, but Analytica will appear to hang while the instantiation is occurring and cannot be revived until the instantiation completes or times out.

## COMCallMethod(object, methodName, param1, param2, param3,...,resultIndex: I, J,..., concurrent)

This calls a method of a COM object that was instantiated using COMCreateObject (or SpreadsheetOpen). The return value is the value returned by the method.

You can can only be invoked for «object»s that implement the IDispatch interface.

Example:

Var Ade := COMCreateObject("ADE4.CAEngine");
COMCallMethod(Ade, "SendCommand","news");
COMCallMethod(Ade, "OutputBuffer")

In the above example, the SendCommand method is invoked with the parameter "news". Its return value is ignored. Then the OutputBuffer property is accessed, and its value is returned.

#### Invoke Method operator ->

For syntactic convenience, you can also call a COM method using this operator: obj -> method(param1, param2,..)

For example, this does the same as the previous example:

ADE -> SendCommand("News")

ADE -> OutputBuffer

### Array-valued results

If a method returns an array (known as a SafeArray in COM lingo), it converts it to an Analytica array. By default it uses local indexes named .dim1, .dim2, etc. Alternatively, you can specify existing global indexes to use with the «resultIndex» parameter. If a result index is shorter than the corresponding array dimension, it ignores the extra elements (slices) of the result. If it is too long, it pads the extra cells (slices) with Null. If you specify too many indexes (e.g., you provide 3 indexes for a 2-D result), it ignores the extra indexes. If you don't specify enough indexes, it creates local indexes for the missing ones.

Example:

COMCallMethod(reuters, "GetPriceHistory", "AAPL", 1-Jan-2012, Today(), resultIndex: Time, DateOpenCloseHighLow)

This hypothetical call returns the stock price history for a given stock from a start date to an end-date, where the result is a 2-D array. The first dimension of the result has one row for each date, and the columns are ['Date', 'Open', 'High', 'Low', 'Close']. The «resultIndex» parameter maps these to existing Analytica indexes (named Time and DateOpenCloseHighLow).

### Array parameters and COMArray

Function COMArray(x, I..., datatype})

If one or more parameters in a call to a COM method is an array, Analytica does its usual array abstraction -- that is, it repeats the call for each atomic combination of the parameters over their index(es). It assembles the results from these calls back into an Analytica array with the same index(es) as the array-valued parameters. This works even if the «object» parameter is an array of objects, calling the method for each object.

When a method expects an array parameter, you must package it using the COMArray function. This function specifies which index(es) to use in the array value to pass to the COM method, and in what sequence if the array has more than one dimension. Analytica will do its usual array abstraction over any index(es) not included mentioned in the COMArray call -- i.e. it will repeat the method call over each of the unmentioned indexes, and reassemble the results into an array using these indexes, plus any resultIndex if each call to the method returns an array.

In COM jargon, COMArray(x, I, J, K) creates a SAFEARRAY(VT_VARIANT), so if you are also writing the code on the server side, this is what your function should be accepting. In this example, three indexes were listed, so the safe array is 3-dimensional. Each dimension starts at 1 (Analytica's convention).

Examples

COMCallMethod(mathObj, "PrincipleEigenVector", COMArray(A, I, J), resultIndex: I)

This hypothetical method accepts a 2-D matrix A indexed by I and J. It returns a 1-D vector indexed by I.

COMCallMethod(reuters, "OpenPriceOnGivenDates", Stock, COMArray(Time, Time), resultIndex: Time)

This hypothetical method accepts a stock symbol and Time, which is a 1-D array of dates. Time is an Analytica Index, so COMArray needs to specify that is indexed by Time. It returns a vector containing the stock price on each date in Time. The model may send this an array of stock symbols. It calls the method once for each stock, and collected the results as an array with the dimensions of Stock.

On the component server side, if you are writing your COM component, your method needs to have a SAFEARRAY(VT_VARIANT) parameter type. In many languages that have COM support, this is straightforward. Not so in the C# language, which certainly took us a very long time to figure out. In the C# language, the parameter needs to have a long annotated type as follows:

public double ExampleMethod([In, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_VARIANT)] Array x);

(New to Analytica 5.3) You can use the «datatype» parameter of COMArray to change the cell data type (or what COM refers to as the VARTYPE) to a different variant type. For example, to pass as array of 64-bit (i.e., 8-byte) floating point numbers (doubles) use:

COMArray(x, I,J, datatype:"VT_R8")

«datatype» can be any of the following:

• Floating point types: "VT_R4", "VT_R8", "VT_CY", "VT_DATE"
• Integer types, "VT_I1", "VT_I2", "VT_I4", "VT_I8", "VT_UI1", "VT_UI2", "VT_UI4", "VT_UI8", "VT_BOOL"
• String type: "VT_BSTR"
• General: "VT_VARIANT"
• Implemented by COMArray( ), but often unsupported in server-side languages including IDL: "VT_INT" (use "VT_I4" instead), "VT_UINT" (use "VT_UI4" instead) and "VT_DECIMAL". You'll probably want to avoid these.

If you are implementing a method in your component that accepts an array of boolean, be aware that the IDL declaration must use SAFEARRAY(VARIANT_BOOL) and not SAFEARRAY(BOOL). This is a quirk of IDL -- the array passed really does contain BOOLs and not variants of BOOL.

### Pass-by value

All parameters are passed-by-value. The option to pass-by-reference is not available. This limits the methods that can be called to those without pass-by-reference parameters (a.k.a., "out" parameters).

### Data types

Parameters are passed as COM VARIANTs. COM Objects themselves can be passed as parameters and result values can return COM objects. Hence, it is possible to have methods that return a new COM object, and then that object can be used as the first parameter in a call to COMCallMethod, COMGetProperty, obj->property, etc.

### Names parameters

The COM Automation specification has a concept of named parameters. This is not supported by COMCallMethod. Parameters are passed by position only.

### Error handling

Errors in the COM layer cause an error message to appear and evaluation to terminate.

COM objects may themselves implement error handling in various ways, such as by including an ErrorCode property that the caller can check after a method call, etc. These are just return values and are not interpreted as errors by Analytica.

### Concurrent calls

When you specify the optional «concurrent» as true, COMCallMethod returns right away, it returns a special COM object that encapsulates the state of the computation. Your model can then work on something else, or launch other concurrent calls. Your model will need to eventually ask the object for the result, or it can query the object to find out it the call has finished yet.

The special object returned supports these properties and methods:

• (property, read-only) IsDone
• (property, read-only) HasError
• (method) Result() { Blocks until done, issues an error if there was an error }

This example creates three parallel computations, one for each molecule. Two of the molecule folds are computed on this computer, and one is computed on a remote computer named Computer2. A lengthy CalculateFold method is called, and the three computations run in parallel. The final line waits for them all to complete and merges the results into a single array. The local variable, h, holds the special COM object that tracks the state of the concurrent computations.

Index molecule := ["JunK", "JanA", "TcpK"];
Var computer := Array(molecule, ["LocalHost", "LocalHost", "Computer2"]);
Var obj := COMCreateObject("ProteinFolder", server: computer);
Var h := COMCallMethod(obj, "CalculateFold", molecule, concurrent: true);
COMCallMethod(h, "Result")

Once the concurrent COMCallMethod evaluates, thus starting the concurrent computations, the computations continue in the client components even if you abort the computation in Analytica (via Ctrl+break or because of an error). If the concurrency object returned from COMCallMethod is cached, you can evaluate the Result() method later and get the result.

.NET libraries let you overload methods -- that is, a method may have several different versions with different numbers or types of parameter. For example, in the System.Random class, there are three overloads for the method Next:

int Next();
int Next(int);
int Next(int, int);

Even though COM doesn't support method overloading, COMCallMethod will find the matching .NET overloads and call it. When .NET wraps an class in a COM wrapper, it adds overloaded variants of methods by appending an underscore and number, starting with 2, to create new names for the overloaded variants. It the example of Next, it exposes three methods

int Next();
int Next_2(int, int)
int Next_3(int)

This example uses the .NET class System.Random to generate an integer between 200 and 300:

Var sysRand := COMCreateObject("System.Random") Do sysRand- > Next_2(200, 300)

However, you don't need to figure out the overloaded name. If you simply use

Var sysRand := COMCreateObject("System.Random") Do sysRand -> Next(200, 300)

COMCallMethod first tries to call Next(), and when that fails (with an invalid number of parameters error), it looks for a Next_2 variant, then a Next_3 variant, and so on, until it eventually finds the appropriate variant. However, overloading resolution does not occur when the «concurrent» flag is true.

## COMPutProperty(object, propertyName, value, params: param1, param2, param3,...)

Sets a property of a COM object to «value».

Most properties are simple properties, so that COMPutProperty is akin to an assignment. In these cases, the «params» parameters are not used. In less common cases, a COM object may have an indexed property that accepts parameters, similar to array coordinates for the property (although they need not be array coordinates), and these indexed parameters are specified in «params».

COMPutProperty calls IDispatch::Invoke with the DISPATCH_PROPERTYPUT flag. No method for calling with DISPATCH_PROPERTYPUTREF will be provided.

No return value. In other respects, same as COMCallMethod.

Syntactic shortcuts:
obj -> propertyName := value
obj -> propertyName(param1, param2) := value

## COMGetProperty(obj, propertyName, param1, param2, param3,..., resultIndex: I, J,...)

Returns the value of a property. This is the same as COMCallMethod, except that it uses the DISPATCH_PROPERTYGET flag. The following syntactic shortcut can be used for an non-indexed, scalar-valued property:

obj -> propertyName

If parameters need to be passed, then you must call the function explicitly.

### TypeOf

You can test whether a scalar value holds a COM object using TypeOf in TypeOf(X, true) = "COM Object".

New to Analytica 5.4

Enumerates the items of «obj», where «obj» is an enumerable collection. This only works for objects that implement the IEnumVARIANT interface, or which have a property named "_NewEnum" that returns a IEnumVARIANT object. When «resultIndex» is omitted, it enumerates the entire collection and returns a list. When «resultIndex» is specified, it enumerates IndexLength(resultIndex) items, and pads with Null if there are fewer items than that.

This example obtains a listing of file names in the current data folder:  

 Local folder := COMCreateObject("Scripting.FileSystem")->GetFolder( CurrentDataFolder() ); COMEnumerate( folder )->Name 

## Limitations

The Analytica-COM integration facility comes with several notable limitations.

• Objects must support the IDispatch interface (also known as ActiveX automation). All calls and property accesses are automation calls. It doesn't support native COM calls.
• It doesn't support method calls with reference parameters. The only result from calling a method is the single value returned (which could be an array).
• Expression Assist doesn't "known" the names of the properties and methods supported by an object, so it can't assist you with possible method-name completions while typing. It detects non-existent methods (e.g. mistyped names) only when the expression gets evaluated, when it returns a missing name error, not when the definition is parsed (after typing it in).

## History

Introduced in Analytica 4.6.