Jump to content

Accessing AutoIt Variables


LarsJ
 Share

Recommended Posts

Link to comment
Share on other sites

Hi!

Very interesting article!:thumbsup:

Some time ago i noticed the (you mentioned it) time of the "conversion" of variables/strings during a DllCall of Assemblercode. The conversion of "big arrays" takes usually more time than the  execution of my ASM-code....so imho the transfer of variables/strings into a ASM-code with something other than a pointer to the data is useless. 

Spoiler
$string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
For $i = 1 To 20                                                                   ;build a 37MB string
    $string &= $string
Next
ConsoleWrite(@CRLF & "stringlen = " & StringLen($string) & @CRLF & @CRLF)


$binarycode = "0xC3"                                                               ;a simple RET
$tCodeBuffer = DllStructCreate("byte[" & StringLen($binarycode) / 2 - 1 & "]")     ;reserve memory for opcodes
DllStructSetData($tCodeBuffer, 1, $binarycode)                                     ;write asm-ccode into memory



;conversion by hand
$t = TimerInit()
$string_struct = DllStructCreate("char[" & StringLen($string) & "]")               ;reserve memory
DllStructSetData($string_struct, 1, $string)                                       ;copy string into memory
$string_ptr = DllStructGetPtr($string_struct)                                      ;get ptr

$dllcalltimer1 = TimerInit()                                                       ;time only DllCalladdress
$ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "ptr", $string_ptr) ;run asm-code
$dllcalltimer = TimerDiff($dllcalltimer1)

$string_after_processing = DllStructGetData($string_struct, 1)                     ;get string

$m = TimerDiff($t)
ConsoleWrite("time for stringcopy by hand and DllCallAddress [ms] = " & Int($m * 1000) / 1000 & @CRLF)
ConsoleWrite("...but...time for DllCall only [ms] = " & Int($dllcalltimer * 1000) / 1000 & @CRLF)
ConsoleWrite("stringlen = " & StringLen($string_after_processing) & @CRLF & @CRLF)



;stringvariable
$t = TimerInit()
$ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "str", $string) ;run asm-code
$m = TimerDiff($t)
ConsoleWrite("time for DllCallAddress with str [ms] = " & Int($m * 1000) / 1000 & @CRLF)


;pointer to the stringvariable
$t = TimerInit()
$ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "str*", $string) ;run asm-code
$m = TimerDiff($t)
ConsoleWrite("time for DllCallAddress with str* [ms] = " & Int($m * 1000) / 1000 & @CRLF)

 

This is the reason why I am not using "Arrays" in my scripts when it is necessary to process those data via ASM-code. 

 

After some profiling of the functions I have noticed that there could be a great performance boost to some of the _Array-functions, i.e. _ArraySort()

Lets see what we can do :drool:

Link to comment
Share on other sites

ptrex, You should do.


AndyG, These are some of the possibilities. Other obvious applications are such things as saving/reading a 2D-array to/from a file.

Edited by LarsJ
Link to comment
Share on other sites

This is output in SciTE console for the three demo examples on my PC.

1) Create and fill new array\Example1.au3:

Executing AutoIt code to fill array (~15 seconds) ...
Time for AutoIt code to fill array: 14581.8287544935

Executing FillArray to fill array ...
Time for FillArray to fill array (fasm code only): 28.8684658448691
Time for FillArray to fill array (inside FillArray): 29.0141864248259
Time for FillArray to fill array (outside FillArray): 2382.18795849346
Time for FillArray to fill array: 2382.18795849346

1) Create and fill new array\Example2.au3:

--- Inspect safearray ---
Number of dimensions          = 2
Features flags                = 0x00000880 ($FADF_VARIANT+$FADF_HAVEVARTYPE, array of variants)
Variant type                  = 0x000C     (VT_VARIANT, variant data type)
Size of array element (bytes) = 16         (size of the element structure)
Number of locks               = 0
Pointer to data               = 0x02DD0020 (pvData)
Dimension 1:
  Elements in dimension       = 1048576
  Lower bound of dimension    = 0
Dimension 2:
  Elements in dimension       = 16
  Lower bound of dimension    = 0

Applied time to create and fill array (UDF and fasm): 1217.83259752752

--- Inspect $aArray1 ---
Number of dimensions          = 2
Features flags                = 0x00000880 ($FADF_VARIANT+$FADF_HAVEVARTYPE, array of variants)
Variant type                  = 0x000C     (VT_VARIANT, variant data type)
Size of array element (bytes) = 16         (size of the variant structure)
Number of locks               = 0
Pointer to data               = 0x7FFF0020 (pvData)
Dimension 1:
  Elements in dimension       = 1048576
  Lower bound of dimension    = 0
Dimension 2:
  Elements in dimension       = 16
  Lower bound of dimension    = 0

2) Manipulate existing array\Example1.au3:

Executing AutoIt code to fill array (~10 seconds) ...
Time for AutoIt code to fill array: 10104.0087565325

Executing AutoIt code to test array (~7 seconds) ...
Time for AutoIt code to test array: 6166.67248440845
$iMin1, $iMax1 = 83, 2147483630

Executing TestArray to test array ...
Time for TestArray to test array (fasm code only): 19.23234620107
Time for TestArray to test array (inside TestArray): 19.3880400526589
Time for TestArray to test array (outside TestArray): 2143.26383071147
Time for TestArray to test array: 2143.26383071147
$iMin2, $iMax2 = 83, 2147483630

3) Concatenate 1D-arrays\Example1, 2 columns.au3:

Executing AutoIt code to fill array (~1 second) ...
Time for AutoIt code to fill array: 561.14945279983

Executing AutoIt code to concatenate arrays (~2 seconds) ...
Time for AutoIt code to concatenate arrays: 1748.80902514594

Executing ConcatArrays to concatenate arrays ...
Time for ConcatArrays to concatenate arrays (fasm code only): 4.81709019826864
Time for ConcatArrays to concatenate arrays (inside ConcatArrays): 4.99051431053665
Time for ConcatArrays to concatenate arrays (outside ConcatArrays): 797.439528729671
Time for ConcatArrays to concatenate arrays: 797.439528729671

3) Concatenate 1D-arrays\Example1, 8 columns.au3:

Executing AutoIt code to fill array (~1 second) ...
Time for AutoIt code to fill array: 610.600535010616

Executing AutoIt code to concatenate arrays (~7 seconds) ...
Time for AutoIt code to concatenate arrays: 7168.83114364614

Executing ConcatArrays to concatenate arrays ...
Time for ConcatArrays to concatenate arrays (fasm code only): 20.7541012309233
Time for ConcatArrays to concatenate arrays (inside ConcatArrays): 21.0427720376059
Time for ConcatArrays to concatenate arrays (outside ConcatArrays): 3115.63759125544
Time for ConcatArrays to concatenate arrays: 3115.63759125544

3) Concatenate 1D-arrays\Example1, 16 columns.au3:

Executing AutoIt code to fill array (~1 second) ...
Time for AutoIt code to fill array: 580.067917979814

Executing AutoIt code to concatenate arrays (~15 seconds) ...
Time for AutoIt code to concatenate arrays: 13388.9531610839

Executing ConcatArrays to concatenate arrays ...
Time for ConcatArrays to concatenate arrays (fasm code only): 48.5318790087011
Time for ConcatArrays to concatenate arrays (inside ConcatArrays): 48.9812303027885
Time for ConcatArrays to concatenate arrays (outside ConcatArrays): 6235.3390136656
Time for ConcatArrays to concatenate arrays: 6235.3390136656

In these examples, fasm code including conversions is 2-10 times faster than AutoIt code. fasm code alone is several hundred times faster than AutoIt code but the conversions takes quite some time.

In order to minimize the time spent on conversions you can reduce the number of rows in the arrays by a factor of 4-8. Then the conversions will take less than a second. But then the AutoIt code will also be 4-8 times faster.

If the code in the array loops is more complicated than in the demo examples, it will be less beneficial for the AutoIt code and more beneficial for the fasm code and the conversions.

The optimum is to avoid conversions completely so that it's only execution time for the fasm code that counts.

Link to comment
Share on other sites

The idea for this project originated from the example of virtual listviews. If eg. a virtual listview is loaded with data from a CSV-file (which seems to be a very obvious data source and therefore I cannot understand why I have forgotten such an example, but I'll add one), data will appear in the listview almost instantly. Loading a CSV-file into a 1D-array with FileReadToArray is very fast. And displaying array data in a virtual listview is very fast too.

However, to manipulate the array (add, delete or modify rows) or sort the array through indexes will be slow operations if there are many rows.

It must be possible to carry out these operations in a faster way. One day I was playing with RectToVariant method of the UIAutomation object an idea occurred to use COM conversions to access native AutoIt arrays through safearrays. And then came this project.

In a virtual listview rows are displayed through $LVN_GETDISPINFOW notifications. Whether the data source is a native AutoIt array or a safearray doesn't matter for the $LVN_GETDISPINFOW notifications.

Then it's possible to completely replace native AutoIt arrays with safearrays. Without any native AutoIt arrays you don't need any conversions. And in safearrays you can easily get a pointer to the data area to be able to manipulate data through fast functions of compiled code.

Because there is a complete set of API functions to support operations on safearrays and variants this should not be too hard.

Up to a million elements, you can use native AutoIt arrays without conversions take too long. If you have more than a million elements, you can replace native AutoIt arrays with safearrays and avoid any conversions at all.


Thank you very much for the feedback. Always nice to have some feedback.

Edited by LarsJ
Link to comment
Share on other sites

Nice

Are you really using AutoIT with millions of rows? When is this udf giving me a real performance benefit?

Maybe integrate some examples with jsc.exe, csc.exe, vbc.exe of .NET installed on almost all windows machines for compiling (C:\Windows\Microsoft.NET\Framework\v4.0.30319 certainly contains this but as far as i know its default since .NET 2.0)

Ecmascript 6/7 javascript is powerfull with arrays
https://github.com/Microsoft/ChakraCore/wiki/JsCreateExternalArrayBuffer

And as we have from microsoft default jscript9.dll or chakra.dll for a javascript language would be nice to see above integrating. 

Speed diff between chakra javascript and compiled language is maybe smaller then expect .

https://www.barbarianmeetscoding.com/blog/2016/03/25/javascript-arrays-the-all-in-one-data-structure/

https://github.com/Microsoft/ChakraCore/wiki/Embedding-ChakraCore

to much to read ;-)

Link to comment
Share on other sites

I usually give up using arrays around 100,000 elements eg. 10,000 rows and 10 columns. If complex calculations at an even smaller number.

This UDF is giving you pointers to one or more arrays (more precisely pointers to safearray copies of the native AutoIt arrays). Then it's up to you to create some fast functions to handle the arrays (safearrays).

If these functions are implemented in real compiled code or assembler code they'll be several hundred times faster than AutoIt code.

If you want to be able to access the arrays as native AutoIt arrays you cannot avoid the conversions. And the conversions takes time. Especially for a large number of elements (more than a million).

If you are manipulating the arrays (safearrays) through functions of compiled code anyway, and you have a large number of elements you could consider to give up using native AutoIt arrays at all. Then you don't need the conversions and the array manipulations will be several hundred times faster than AutoIt code.

I do not see these scripting languages as an alternative to real compiled code. I would like to see an example. But I'll not make one myself.

Link to comment
Share on other sites

Simple variables

Numeric variables
The most interesting aspect of AccessingVariables UDF is the possibility to get a pointer to a native AutoIt array, and thereby being able to manipulate the elements through fast functions of compiled code.

But you can also get pointers to simple AutoIt variables (not arrays) eg. numeric and string variables, and then you can access these variables through functions coded in a another language or functions coded in AutoIt.

The pointers you get are pointers to variants. The functions whether coded in AutoIt or another language must be able to handle variants.

Here are some (AutoIt) examples related to simple numeric variables. You find the examples in zip (updated) in bottom of first post. The examples are located in Examples\Demo examples\5) Simple numeric variables\.

Example1.au3
In AutoIt there are three internal numeric data types:

  • Int32 (VT_I4, 4 bytes signed integer) covering the range -2,147,483,648 - 2,147,483,647
  • Int64 (VT_I8, 8 bytes signed integer), -9,223,372,036,854,775,808 - 9,223,372,036,854,775,807
  • Double (VT_R8, 8 bytes double), -1.79769313486232E+308 - 1.79769313486232E+308

Use this notation for the largest negative Int64 integer:

$iInt = "-9223372036854775808"

Int32 and Int64 are exact integer data types covering limited ranges. Double is a fast compact but not accurate data type covering a large range. VarGetType returns the internal representation of a variable.

Note that unsigned 64 bit integers in the range 9,223,372,036,854,775,808 - 18,446,744,073,709,551,615 are not represented by internal integer types. These integers may be stored in a "uint64" structure (DllStructCreate), or you can code a function where you interpret Int64 signed integers as unsigned 64 bit integers.

Double is the only native data type that directly supports these large integers. Use this notation:

$fInt1 =  9223372036854775808.0
$fInt2 = 18446744073709551615.0

This is demonstrated in Example1.au3. Last in the example the inaccuracies that arises when the two integers above are stored as Doubles are calculated in two different ways: With variant calculations through the UDF, and with string calculations without using the UDF.

Code to calculate Double inaccuracies with variants:

Func DoubleErrorVariants( $pfInt, $psInt )
  Local $tVar = DllStructCreate( $tagVARIANT ), $pVar = DllStructGetPtr( $tVar )
  Local $tRes = DllStructCreate( $tagVARIANT ), $pRes = DllStructGetPtr( $tRes )

  ; Change float to decimal
  VariantChangeType( $pVar, $pfInt, 0, $VT_DECIMAL ) ; Change type to $VT_DECIMAL

  ; Print decimal value
  VariantChangeType( $pRes, $pVar, 0, $VT_BSTR )
  Local $pBStr = DllStructGetData( $tRes, "data" )
  ConsoleWrite( "$fInt = " & SysReadString( $pBStr ) & @CRLF )

  ; Calculate inaccuracy
  VarSub( $pVar, $psInt, $pRes ) ; $pRes = $pVar - $psInt = $pfInt - $psInt

  ; Print inaccuracy
  VariantChangeType( $pRes, $pRes, 0, $VT_BSTR )
  $pBStr = DllStructGetData( $tRes, "data" )
  ConsoleWrite( "$iErr = " & SysReadString( $pBStr ) & " (variants)" & @CRLF )
EndFunc

Output in SciTE console from the last part of the example:

$sInt = 9223372036854775808
Hex   = 0x8000000000000000 (unsigned)
$fInt = 9.22337203685478e+018
$fInt = 9223372036854780000
$iErr = 4192 (variants)
$fInt = 9223372036854780000
$iErr = 4192 (strings)
Type  = Double
ptr  = 0x00DC8A60 ($pVariant)
vt   = 0x0005     (VT_R8, 8 bytes double)
data = 9.22337203685478e+018

$sInt = 18446744073709551615
Hex   = 0xFFFFFFFFFFFFFFFF (unsigned)
$fInt = 1.84467440737096e+019
$fInt = 18446744073709600000
$iErr = 48385 (variants)
$fInt = 18446744073709600000
$iErr = 48385 (strings)
Type  = Double
ptr  = 0x00DC8A18 ($pVariant)
vt   = 0x0005     (VT_R8, 8 bytes double)
data = 1.84467440737096e+019

Example2.au3
It's not too hard to code a function where integers stored as Int64 values are interpreted as unsigned 64 bit integers in the range 0 - 18,446,744,073,709,551,615. But in the range 9,223,372,036,854,775,808 - 18,446,744,073,709,551,615 it's not possible to enter these figures as literal decimal integers. Not even if they are stored as strings.

Now when we can handle variants it's possible to do something about it. Example2.au3 shows how to enter unsigned 64 bit integers as literal decimal strings. It's very simple. You need one code line.

Example3.au3
In Example2.au3 unsigned 64 bit integers can be entered as literal decimal strings and stored as Int64 variables. The variables are printed in SciTE console as hexadecimal numbers.

Example3.au3 shows how to print the variables as literal decimal integers. Output in SciTE console:

$su8 = 18446744073709551615
$su8 = 18,446,744,073,709,551,615

$su8 =                          0
$su8 =                        255
$su8 =                     65,535
$su8 =                 16,777,215
$su8 =              4,294,967,295
$su8 =          1,099,511,627,775
$su8 =        281,474,976,710,655
$su8 =     72,057,594,037,927,935
$su8 = 18,446,744,073,709,551,615

Example3.au3 (2016-12-30)
The day before yesterday I added an incorrect version of Example3.au3 to the zip at bottom of first post. The correct version is added to the zip below.

Numeric variants
There are eight numeric variant types:

  • VT_UI1, byte, 1 byte, 0 to 255
  • VT_I2, short, 2 bytes, -32,768 to 32,767
  • VT_I4, integer, 4 bytes, -2,147,483,648 to 2,147,483,647
  • VT_I8, longlong, 8 bytes, -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
  • VT_R4, single, 4 bytes, -3.402823E+38 to 3.402823E+38
  • VT_R8, double, 8 bytes, -1.79769313486232E+308 to 1.79769313486232E+308
  • VT_CY, currency, 8 bytes, -922,337,203,685,477.5808 to 922,337,203,685,477.5807
  • VT_DECIMAL, decimal, 16 bytes, -/+79,228,162,514,264,337,593,543,950,335 (0 decimal places), -/+7.9228162514264337593543950335 (28 decimal places)

Single and double are not exact types. All the others are exact types.

The fact that these types are considered numerical means that they can be used in calculations with the Variant Arithmetic Functions.

In particular decimal type is interesting. It uses 16 bytes of storage. If you are running 32 bit it uses the entire variant structure. The data range is -79,228,162,514,264,337,593,543,950,335 to 79,228,162,514,264,337,593,543,950,335 with zero decimal places and -7.9228162514264337593543950335 to 7.9228162514264337593543950335 with 28 decimal places.

It's defined in this way (DECIMAL structure):

Global Const $tagDEC = "word wReserved;byte scale;byte sign;uint Hi32;uint Lo32;uint Mid32"

Example4.au3
The only way to specify an exact decimal value in AutoIt is to enter it as a literal decimal string. Example4.au3 shows how decimal types are stored in variant and decimal structures. This is done with _WinAPI_DisplayStruct. You must change the digit grouping symbol (1000 separator) and decimal symbol to your local symbols in top of script.

Example5.au3
Example5.au3 shows how to add numeric variants. Again you must change the digit grouping symbol (1000 separator) and decimal symbol to your local symbols in top of script. The other Variant Arithmetic Functions can be implemented more or less in the same way.

UDFs
Based on the variant decimal type it should be possible to create a couple of interesting UDFs: A UDF to handle 12 bytes integers. And a UDF to perform exact calculations on very large decimal numbers with up to 28 decimals.

With regard to the latter probably two variants are needed to handle each decimal number. A variant for the integer part and a variant for the decimal part.

I'll not make any UDFs on these topics myself, but if anyone is interested you can use the ideas.

Note that since all five examples in this post is about simple variables (not arrays), AccessingVariables UDF is not strictly necessary. You can implement all the examples using only DllStruct-functions where you manipulate variant- and BSTR-structures directly.

Zip
All examples are located in Examples\Demo examples\5) Simple numeric variables\ in zip at bottom of first post.

Edited by LarsJ
Examples copied to zip in first post
Link to comment
Share on other sites

Internal conversions

Limitations
This code is based on a copy of Example5.au3 in "Examples\Demo examples\5) Simple numeric variables\". See post 11.

;#AutoIt3Wrapper_UseX64=y

#include "..\..\..\Includes\InspectVariable.au3"

Opt( "MustDeclareVars", 1 )

Example6()

Func Example6()
  ; Change to your local digit grouping and decimal symbols     ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  Local $sLocalGroupingSymbol = ".", $sLocalDecimalSymbol = "," ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

  Local $sDec1 = "3.1415926535897932384626433832", $sDec ; pi with 28 decimals
  $sDec1 = StringReplace( $sDec1, ".", $sLocalDecimalSymbol, -1 )
  AccessVariables03( DecimalsAdd, $sDec1, $sDec1, $sDec )
  ConsoleWrite( "DecimalsAdd:" & @CRLF )
  ConsoleWrite( "  " & $sDec1 & @CRLF )
  ConsoleWrite( "+ " & $sDec1 & @CRLF )
  ConsoleWrite( "--------------------------------" & @CRLF )
  ConsoleWrite( "= " & $sDec & @CRLF & @CRLF )

  $sDec1 = "3.1415926535897932384626433832" ; pi with 28 decimals
  $sDec1 = StringReplace( $sDec1, ".", $sLocalDecimalSymbol, -1 )
  AccessVariables03( DecimalsAddError, $sDec1, $sDec1, $sDec )
  ConsoleWrite( "DecimalsAddError:" & @CRLF )
  ConsoleWrite( "  " & $sDec1 & @CRLF )
  ConsoleWrite( "+ " & $sDec1 & @CRLF )
  ConsoleWrite( "--------------------------------" & @CRLF )
  ConsoleWrite( "= " & $sDec & @CRLF & @CRLF )
EndFunc

Func DecimalsAdd( $psDec1, $psDec2, $psDec )
  Local $tVar1 = DllStructCreate( $tagVARIANT ), $pVar1 = DllStructGetPtr( $tVar1 )
  Local $tVar2 = DllStructCreate( $tagVARIANT ), $pVar2 = DllStructGetPtr( $tVar2 )
  VariantChangeType( $pVar1, $psDec1, 0, $VT_DECIMAL )
  VariantChangeType( $pVar2, $psDec2, 0, $VT_DECIMAL )
  VarAdd( $pVar1, $pVar2, $psDec )
  VariantChangeType( $psDec, $psDec, 0, $VT_BSTR )
EndFunc

Func DecimalsAddError( $psDec1, $psDec2, $psDec )
  VariantChangeType( $psDec1, $psDec1, 0, $VT_DECIMAL )
  VariantChangeType( $psDec2, $psDec2, 0, $VT_DECIMAL )
  VarAdd( $psDec1, $psDec2, $psDec )
  VariantChangeType( $psDec, $psDec, 0, $VT_BSTR )
EndFunc

You can find the example as Example6.au3 in Tests\Examples\3) Internal conversions\ in zip in first post.

Output in SciTE console:

DecimalsAdd:
  3,1415926535897932384626433832
+ 3,1415926535897932384626433832
--------------------------------
= 6,2831853071795864769252867664

DecimalsAddError:
  3.14159265358979
+ 3.14159265358979
--------------------------------
= 6,2831853071795864769252867664

Internal conversions for DecimalsAdd:

Conversions for $sDec1 and $sDec2
Function entry: AutoIt string -> Pointer to variant string
Function exit:  Pointer to variant string -> AutoIt string

Internal conversions for DecimalsAddError:

Conversions for $sDec1 and $sDec2
Function entry: AutoIt string -> Pointer to variant string
Function exit:  Pointer to variant decimal -> AutoIt double

In DecimalsAddError the strings with 28 decimals on function entry are converted to doubles with 14 decimals on function exit. We certainly don't want this. We'll lose a great deal of the accuracy of the string variables.

In AutoIt there is no internal decimal type. The decimal variant is converted to the internal type that fits best: Double.

In this example it's not a problem. We can just use the code in DecimalsAdd. However, it can certainly be a problem for methods of real COM objects.

COM methods
When I was creating this example I was playing with $oUIAutomation.RectToVariant method (Tests\Examples\0) Accessing variables\Example1.au3). It takes a rectangle structure as input and returns a 1D array with four elements (left, top, width, height). This works fine.

But $oUIAutomation.VariantToRect which takes a 1D array with four elements as input and returns a rectangle structure does not work. And there is nothing to do about it.

On method entry the AutoIt array is converted to a safearray of variants. But VariantToRect does not take a safearray of variants as input parameter. It takes a safearray of doubles as input parameter. The consequence is that the method is not working. See Tests\Examples\0) Accessing variables\Example2.au3.

Example2.au3 returns the error code $iErr = -2147024809 = 0x80070057 = E_INVALIDARG (One or more arguments are invalid).

See, however, a workaround using Com subclassing in post 16 below.

Zip
Zip at bottom of first post is updated.

Edited by LarsJ
Link to comment
Share on other sites

Link to comment
Share on other sites

In the UIAutomation object it's possible to replace a method eg. RectToVariant with your own method eg. RectToVariantEx and still be able to execute the original RectToVariant method.

The original RectToVariant method can be executed with DllCallAddress, where the address is the original RectToVariant function pointer in the VTable.

The interesting part of the code looks like this:

Example1()

Func Example1()
  ; Create UIAutomation object
  Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $stag_IUIAutomation )

  ; Replace RectToVariant with RectToVariantEx
  ; And get original RectToVariant function pointer
  Local $pRectToVariantEx = DllCallbackGetPtr( DllCallbackRegister( "RectToVariantEx", "long", "ptr;ptr;ptr*" ) )
  $pRectToVariant = ReplaceVTableFuncPtr( Ptr( $oUIAutomation() ), ( 3 + 42 - 1 ) * ( @AutoItX64 ? 8 : 4 ), $pRectToVariantEx )

  ; Create rectangle structure
  Local $tRect = DllStructCreate( $tagRECT )
  DllStructSetData( $tRect, "Left",    100 )
  DllStructSetData( $tRect, "Top",     200 )
  DllStructSetData( $tRect, "Right",  3000 )
  DllStructSetData( $tRect, "Bottom", 4000 )

  ; Output array
  Local $aArray

  ; Execute RectToVariantEx method
  $oUIAutomation.RectToVariantEx( $tRect, $aArray )

  ; Display array
  _ArrayDisplay( $aArray )
EndFunc

Func RectToVariantEx( $pSelf, $pRect, $pVariant )
  ; Here it is possible to debug and modify parameters after conver-
  ; sions on function entry but before the original method is executed.

  ; Execute original RectToVariant method
  Local $aRet = DllCallAddress( "long", $pRectToVariant, "ptr", $pSelf, @AutoItX64 ? "struct*" : "struct", DllStructCreate( $tagRECT, $pRect ), "ptr", $pVariant )
  ; Note that no COM conversions are applied neither to DllCall nor DllCallAddress functions

  ; Here it is possible to debug and modify parameters after the origi-
  ; nal method is executed but before conversions on function exit.

  Return $aRet[0]
EndFunc

Example1.au3 in the zip below.

As described at the end of the code, this makes it possible to debug and modify parameters immediately before and after the original method is executed.

In Example2.au3 debug code is added after the original method is executed:

Func RectToVariantEx( $pSelf, $pRect, $pArray )
  ; Here it's possible to debug and modify parameters after conver-
  ; sions on function entry but before the original method is executed.

  ; Execute original RectToVariant method
  Local $aRet = DllCallAddress( "long", $pRectToVariant, "ptr", $pSelf, @AutoItX64 ? "struct*" : "struct", DllStructCreate( $tagRECT, $pRect ), "ptr", $pArray )
  ; Note that no COM conversions are applied neither to DllCall nor DllCallAddress functions

  ; Here it's possible to debug and modify parameters after the origi-
  ; nal method is executed but before conversions on function exit.

  ; $pArray info
  ConsoleWrite( "Variant $pArray:" & @CRLF )
  ConsoleWrite( "----------------" & @CRLF )
  Local $tVariant = DllStructCreate( $tagVARIANT, $pArray )
  Local $vt = DllStructGetData( $tVariant, "vt" )
  ConsoleWrite( "vt   = 0x" & Hex( $vt, 4 ) & " = $VT_ARRAY + $VT_R8" & @CRLF )
  Local $data = DllStructGetData( $tVariant, "data" )
  ConsoleWrite( "data = " & $data & " (Pointer to safearray)" & @CRLF )
  ConsoleWrite( @CRLF )

  ; $pSafeArray info
  Local $pSafeArray = $data
  ConsoleWrite( "Safearray $pSafeArray:" & @CRLF )
  ConsoleWrite( "----------------------" & @CRLF )
  InspectSafeArray( $pSafeArray )
  ConsoleWrite( @CRLF )

  ; Safearray data
  Local $iUBound, $pSafeArrayData
  SafeArrayGetUBound( $pSafeArray, 1, $iUBound )
  SafeArrayAccessData( $pSafeArray, $pSafeArrayData )
  Local $tDouble = DllStructCreate( "double" )
  Local $iDoubleSize = DllStructGetSize( $tDouble )
  ConsoleWrite( "Safearray data:" & @CRLF )
  ConsoleWrite( "---------------" & @CRLF )
  For $i = 0 To $iUBound
    $tDouble = DllStructCreate( "double", $pSafeArrayData )
    ConsoleWrite( DllStructGetData( $tDouble, 1 ) & @CRLF )
    $pSafeArrayData += $iDoubleSize
  Next
  SafeArrayUnaccessData( $pSafeArray )

  Return $aRet[0]
EndFunc

This is exactly the same debugging as I did with C++ code in first post when I was playing with RectToVariant method.

I believe it's possible to get VariantToRect to work in this way. I've done a few tests that have failed. I've probably not been sufficiently careful.

Examples\Subclassing\1) RectToVariant\

Edited by LarsJ
Examples included in zip in bottom of first post.
Link to comment
Share on other sites

VariantToRect is working with this code:

Example3()

Func Example3()
  ; Create UIAutomation object
  Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $stag_IUIAutomation )

  ; Replace VariantToRect with VariantToRectEx
  ; And get original VariantToRect function pointer
  Local $pVariantToRectEx = DllCallbackGetPtr( DllCallbackRegister( "VariantToRectEx", "long", "ptr;ptr;ptr" ) )
  $pVariantToRect = ReplaceVTableFuncPtr( Ptr( $oUIAutomation() ), ( 3 + 43 - 1 ) * ( @AutoItX64 ? 8 : 4 ), $pVariantToRectEx )

  ; Input array
  Local $aArray = [ 100, 200, 2900, 3800 ]

  ; Output rectangle structure
  Local $tRect = DllStructCreate( $tagRECT )
  Local $pRect = DllStructGetPtr( $tRect )

  ; Execute VariantToRectEx method
  $oUIAutomation.VariantToRectEx( $aArray, $pRect )

  ; Print structure data
  ConsoleWrite( "Rect struct:" & @CRLF )
  ConsoleWrite( "------------" & @CRLF )
  ConsoleWrite( "Left =   " & DllStructGetData( $tRect, "Left"   ) & @CRLF )
  ConsoleWrite( "Top  =   " & DllStructGetData( $tRect, "Top"    ) & @CRLF )
  ConsoleWrite( "Right =  " & DllStructGetData( $tRect, "Right"  ) & @CRLF )
  ConsoleWrite( "Bottom = " & DllStructGetData( $tRect, "Bottom" ) & @CRLF )
EndFunc

Func VariantToRectEx( $pSelf, $pArrayEx, $pRect )
  ; Here it's possible to debug and modify parameters after conver-
  ; sions on function entry but before the original method is executed.

  ; $pArrayEx info
  ConsoleWrite( "Variant $pArrayEx:" & @CRLF )
  ConsoleWrite( "------------------" & @CRLF )
  Local $tVariant = DllStructCreate( $tagVARIANT, $pArrayEx )
  Local $vt = DllStructGetData( $tVariant, "vt" )
  ConsoleWrite( "vt   = 0x" & Hex( $vt, 4 ) & " = $VT_ARRAY + $VT_VARIANT" & @CRLF )
  Local $data = DllStructGetData( $tVariant, "data" )
  ConsoleWrite( "data = " & $data & " (Pointer to safearray)" & @CRLF )
  ConsoleWrite( @CRLF )

  ; $pSafeArrayEx info
  Local $pSafeArrayEx = $data
  ConsoleWrite( "Safearray $pSafeArrayEx:" & @CRLF )
  ConsoleWrite( "------------------------" & @CRLF )
  InspectSafeArray( $pSafeArrayEx )
  ConsoleWrite( @CRLF )

  ; Upper bound
  Local $iUBound
  SafeArrayGetUBound( $pSafeArrayEx, 1, $iUBound )

  ; Pointer to data
  Local $pSafeArrayExData
  SafeArrayAccessData( $pSafeArrayEx, $pSafeArrayExData )

  ; $pArrayEx variant structure size
  Local $tArrayEx = DllStructCreate( $tagVARIANT )
  Local $iVariantSize = DllStructGetSize( $tArrayEx )

  ; Create safearray of type $VT_R8 (double)
  ; The input safearray type expected by VariantToRect
  Local $tSafeArrayBound, $pSafeArray, $pSafeArrayData
  $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND )        ; Bound structure
  DllStructSetData( $tSafeArrayBound, "cElements", $iUBound + 1 ) ; Number of elements
  $pSafeArray = SafeArrayCreate( $VT_R8, 1, $tSafeArrayBound )    ; Type $VT_R8 (double)
  SafeArrayAccessData( $pSafeArray, $pSafeArrayData )             ; Pointer to data

  ; Double structure size
  Local $tDouble = DllStructCreate( "double" )
  Local $iDoubleSize = DllStructGetSize( $tDouble )

  ; Copy data from $pSafeArrayEx to $pSafeArray
  ; Copy data from array of variants to array of doubles
  For $i = 0 To $iUBound
    $data = DllStructGetData( DllStructCreate( "int", $pSafeArrayExData + 8 ), 1 )
    DllStructSetData( DllStructCreate( "double", $pSafeArrayData ), 1, $data )
    $pSafeArrayExData += $iVariantSize
    $pSafeArrayData += $iDoubleSize
  Next

  SafeArrayUnaccessData( $pSafeArrayEx )
  SafeArrayUnaccessData( $pSafeArray )

  ; The safearray must be stored in a variant:

  ; Create $tArray variant structure
  Local $tArray = DllStructCreate( $tagVARIANT )

  ; Set vt element to $VT_ARRAY + $VT_R8 (array of doubles)
  ; The input variant type expected by VariantToRect
  DllStructSetData( $tArray, "vt", $VT_ARRAY + $VT_R8 )

  ; Set data element to pointer to safearray
  DllStructSetData( $tArray, "data", $pSafeArray )

  ; The same debug information as printed in Example2.au3:

  ; $tArray info
  ConsoleWrite( "Variant $tArray:" & @CRLF )
  ConsoleWrite( "----------------" & @CRLF )
  $vt = DllStructGetData( $tArray, "vt" )
  ConsoleWrite( "vt   = 0x" & Hex( $vt, 4 ) & " = $VT_ARRAY + $VT_R8" & @CRLF )
  $data = DllStructGetData( $tArray, "data" )
  ConsoleWrite( "data = " & $data & " (Pointer to safearray)" & @CRLF )
  ConsoleWrite( @CRLF )

  ; $pSafeArray info
  ConsoleWrite( "Safearray $pSafeArray:" & @CRLF )
  ConsoleWrite( "----------------------" & @CRLF )
  InspectSafeArray( $pSafeArray )
  ConsoleWrite( @CRLF )

  ; Safearray data
  SafeArrayGetUBound( $pSafeArray, 1, $iUBound )
  SafeArrayAccessData( $pSafeArray, $pSafeArrayData )
  ConsoleWrite( "Safearray data:" & @CRLF )
  ConsoleWrite( "---------------" & @CRLF )
  For $i = 0 To $iUBound
    $tDouble = DllStructCreate( "double", $pSafeArrayData )
    ConsoleWrite( DllStructGetData( $tDouble, 1 ) & @CRLF )
    $pSafeArrayData += $iDoubleSize
  Next
  SafeArrayUnaccessData( $pSafeArray )
  ConsoleWrite( @CRLF )

  ; Execute original VariantToRect method
  Local $aRet = DllCallAddress( "long", $pVariantToRect, "ptr", $pSelf, @AutoItX64 ? "struct*" : "struct", $tArray, "struct*", $pRect )
  ; Note that no COM conversions are applied neither to DllCall nor DllCallAddress functions

  ; Here it's possible to debug and modify parameters after the origi-
  ; nal method is executed but before conversions on function exit.

  Return $aRet[0]
EndFunc

Examples\Subclassing\2) VariantToRect\

Edited by LarsJ
Zip deleted. Included in zip in bottom of first post.
Link to comment
Share on other sites

To get VariantToRect to work, UIAutomation.au3 UDF should be coded in this way:

#include-once

#include "Variant.au3"
#include "SafeArray.au3"
#include "Utilities.au3"

Global Const $sCLSID_CUIAutomation = "{FF48DBA4-60EF-4201-AA87-54103EEF594E}"

Global Const $sIID_IUIAutomation = "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}"
Global Const $stag_IUIAutomation = _
  "CompareElements hresult(ptr;ptr;long*);" & _
  "CompareRuntimeIds hresult(ptr;ptr;long*);" & _
  "GetRootElement hresult(ptr*);" & _
  "ElementFromHandle hresult(hwnd;ptr*);" & _
  "ElementFromPoint hresult(struct;ptr*);" & _
  "GetFocusedElement hresult(ptr*);" & _
  "GetRootElementBuildCache hresult(ptr;ptr*);" & _
  "ElementFromHandleBuildCache hresult(hwnd;ptr;ptr*);" & _
  "ElementFromPointBuildCache hresult(struct;ptr;ptr*);" & _
  "GetFocusedElementBuildCache hresult(ptr;ptr*);" & _
  "CreateTreeWalker hresult(ptr;ptr*);" & _
  "ControlViewWalker hresult(ptr*);" & _
  "ContentViewWalker hresult(ptr*);" & _
  "RawViewWalker hresult(ptr*);" & _
  "RawViewCondition hresult(ptr*);" & _
  "ControlViewCondition hresult(ptr*);" & _
  "ContentViewCondition hresult(ptr*);" & _
  "CreateCacheRequest hresult(ptr*);" & _
  "CreateTrueCondition hresult(ptr*);" & _
  "CreateFalseCondition hresult(ptr*);" & _
  "CreatePropertyCondition hresult(int;variant;ptr*);" & _
  "CreatePropertyConditionEx hresult(int;variant;long;ptr*);" & _
  "CreateAndCondition hresult(ptr;ptr;ptr*);" & _
  "CreateAndConditionFromArray hresult(ptr;ptr*);" & _
  "CreateAndConditionFromNativeArray hresult(ptr;int;ptr*);" & _
  "CreateOrCondition hresult(ptr;ptr;ptr*);" & _
  "CreateOrConditionFromArray hresult(ptr;ptr*);" & _
  "CreateOrConditionFromNativeArray hresult(ptr;int;ptr*);" & _
  "CreateNotCondition hresult(ptr;ptr*);" & _
  "AddAutomationEventHandler hresult(int;ptr;long;ptr;ptr);" & _
  "RemoveAutomationEventHandler hresult(int;ptr;ptr);" & _
  "AddPropertyChangedEventHandlerNativeArray hresult(ptr;long;ptr;ptr;struct*;int);" & _
  "AddPropertyChangedEventHandler hresult(ptr;long;ptr;ptr;ptr);" & _
  "RemovePropertyChangedEventHandler hresult(ptr;ptr);" & _
  "AddStructureChangedEventHandler hresult(ptr;long;ptr;ptr);" & _
  "RemoveStructureChangedEventHandler hresult(ptr;ptr);" & _
  "AddFocusChangedEventHandler hresult(ptr;ptr);" & _
  "RemoveFocusChangedEventHandler hresult(ptr);" & _
  "RemoveAllEventHandlers hresult();" & _
  "IntNativeArrayToSafeArray hresult(int;int;ptr*);" & _
  "IntSafeArrayToNativeArray hresult(ptr;int*;int*);" & _
  "RectToVariant hresult(" & ( @AutoItX64 ? "struct*;" : "struct;" ) & "variant*);" & _
  "VariantToRect hresult(variant*;struct*);" & _
  "SafeArrayToRectNativeArray hresult(ptr;ptr*;int*);" & _
  "CreateProxyFactoryEntry hresult(ptr;ptr*);" & _
  "ProxyFactoryMapping hresult(ptr*);" & _
  "GetPropertyProgrammaticName hresult(int;bstr*);" & _
  "GetPatternProgrammaticName hresult(int;bstr*);" & _
  "PollForPotentialSupportedPatterns hresult(ptr;ptr*;ptr*);" & _
  "PollForPotentialSupportedProperties hresult(ptr;ptr*;ptr*);" & _
  "CheckNotSupported hresult(variant;long*);" & _
  "ReservedNotSupportedValue hresult(ptr*);" & _
  "ReservedMixedAttributeValue hresult(ptr*);" & _
  "ElementFromIAccessible hresult(idispatch;int;ptr*);" & _
  "ElementFromIAccessibleBuildCache hresult(iaccessible;int;ptr;ptr*);"

VariantToRectInit()

Func VariantToRectInit()
  Static $pVariantToRectOrg = 0
  If $pVariantToRectOrg Then Return
  ; VariantToRectInit should be called only once

  ; Create UIAutomation object
  Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $stag_IUIAutomation )

  ; Replace original VariantToRect with new method
  ; And get original VariantToRect function pointer
  Local $pVariantToRectNew = DllCallbackGetPtr( DllCallbackRegister( "VariantToRect", "long", "ptr;ptr;ptr" ) )
  $pVariantToRectOrg = ReplaceVTableFuncPtr( Ptr( $oUIAutomation() ), ( 3 + 43 - 1 ) * ( @AutoItX64 ? 8 : 4 ), $pVariantToRectNew )

  ; Initialize VariantToRect by passing $pVariantToRectOrg to the function
  VariantToRect( 0, $pVariantToRectOrg, 0 )
EndFunc

Func VariantToRect( $pSelf, $pArrayVar, $pRect )
  Static $pVariantToRectOrg = 0
  If $pSelf = 0 Then
    $pVariantToRectOrg = $pArrayVar
    Return
  EndIf

  ; $pArrayVar
  Local $tVariant = DllStructCreate( $tagVARIANT, $pArrayVar )
  Local $vt = DllStructGetData( $tVariant, "vt" ), $data
  If $vt <> $VT_ARRAY + $VT_VARIANT Then Return SetError(1,0,1)

  ; $pSafeArrayVar
  Local $pSafeArrayVar = DllStructGetData( $tVariant, "data" )

  ; Dimensions
  Local $iDims = SafeArrayGetDim( $pSafeArrayVar )
  If $iDims <> 1 Then Return SetError(2,0,1)

  ; Upper bound
  Local $iUBound
  SafeArrayGetUBound( $pSafeArrayVar, 1, $iUBound )

  ; Pointer to data
  Local $pSafeArrayVarData
  SafeArrayAccessData( $pSafeArrayVar, $pSafeArrayVarData )

  ; Variant structure size
  Local $iVariantSize = DllStructGetSize( DllStructCreate( $tagVARIANT ) )

  ; Create safearray of type $VT_R8 (double)
  ; The input safearray type expected by VariantToRect
  Local $tSafeArrayBound, $pSafeArrayDbl, $pSafeArrayDblData
  $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND )        ; Bound structure
  DllStructSetData( $tSafeArrayBound, "cElements", $iUBound + 1 ) ; Number of elements
  $pSafeArrayDbl = SafeArrayCreate( $VT_R8, 1, $tSafeArrayBound ) ; Type $VT_R8 (double)
  SafeArrayAccessData( $pSafeArrayDbl, $pSafeArrayDblData )       ; Pointer to data

  ; Double structure size
  Local $iDoubleSize = DllStructGetSize( DllStructCreate( "double" ) )

  ; Copy data from $pSafeArrayVar to $pSafeArrayDbl
  ; Copy data from array of variants to array of doubles
  For $i = 0 To $iUBound
    $vt = DllStructGetData( DllStructCreate( "word", $pSafeArrayVarData ), 1 )
    Switch $vt
      Case $VT_I4 ; 4 bytes signed integer
        $data = DllStructGetData( DllStructCreate( "int", $pSafeArrayVarData + 8 ), 1 )
      Case $VT_R8 ; 8 bytes double
        $data = DllStructGetData( DllStructCreate( "double", $pSafeArrayVarData + 8 ), 1 )
      Case $VT_BSTR ; Basic string
        $data = Number( SysReadString( DllStructGetData( DllStructCreate( "ptr", $pSafeArrayVarData + 8 ), 1 ) ) )
      Case Else
        $data = 0
    EndSwitch
    DllStructSetData( DllStructCreate( "double", $pSafeArrayDblData ), 1, $data )
    $pSafeArrayVarData += $iVariantSize
    $pSafeArrayDblData += $iDoubleSize
  Next

  SafeArrayUnaccessData( $pSafeArrayVar )
  SafeArrayUnaccessData( $pSafeArrayDbl )

  ; The safearray must be stored in a variant:

  ; Create $tArrayDbl variant structure
  Local $tArrayDbl = DllStructCreate( $tagVARIANT )

  ; Set vt element to $VT_ARRAY + $VT_R8 (array of doubles)
  ; The input variant type expected by VariantToRect
  DllStructSetData( $tArrayDbl, "vt", $VT_ARRAY + $VT_R8 )

  ; Set data element to pointer to safearray
  DllStructSetData( $tArrayDbl, "data", $pSafeArrayDbl )

  ; Execute original VariantToRect method
  Return DllCallAddress( "long", $pVariantToRectOrg, "ptr", $pSelf, @AutoItX64 ? "struct*" : "struct", $tArrayDbl, "struct*", $pRect )[0]
EndFunc

VariantToRectInit is called automatically when the UDF is loaded. VariantToRectInit replaces VariantToRect with a new method and returns the original function pointer. The original function pointer is passed to the new method. Additional globals are not needed.

Some examples:

;#AutoIt3Wrapper_UseX64=y

#include <StructureConstants.au3>
#include "UIAutomation.au3"

Opt( "MustDeclareVars", 1 )

Example1()

Func Example1()
  ; Create UIAutomation object
  Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $stag_IUIAutomation )

  ; Variables
  Local $aArray, $tRect, $pRect


  ; Example 1 (integers)
  ; --------------------

  ; Input array
  Dim $aArray = [ 100, 200, 2900, 3800 ]

  ; Output rectangle structure
  $tRect = DllStructCreate( $tagRECT )
  $pRect = DllStructGetPtr( $tRect )

  ; Execute VariantToRect method
  $oUIAutomation.VariantToRect( $aArray, $pRect )

  ; Print structure data
  ConsoleWrite( "Example 1" & @CRLF )
  ConsoleWrite( "---------" & @CRLF )
  ConsoleWrite( "Left =   " & DllStructGetData( $tRect, "Left"   ) & @CRLF )
  ConsoleWrite( "Top  =   " & DllStructGetData( $tRect, "Top"    ) & @CRLF )
  ConsoleWrite( "Right =  " & DllStructGetData( $tRect, "Right"  ) & @CRLF )
  ConsoleWrite( "Bottom = " & DllStructGetData( $tRect, "Bottom" ) & @CRLF )
  ConsoleWrite( @CRLF )


  ; Example 2 (doubles)
  ; -------------------

  ; Input array
  Dim $aArray = [ 500.0, 600.0, 6500.0, 7400.0 ]

  ; Output rectangle structure
  $tRect = DllStructCreate( $tagRECT )
  $pRect = DllStructGetPtr( $tRect )

  ; Execute VariantToRect method
  $oUIAutomation.VariantToRect( $aArray, $pRect )

  ; Print structure data
  ConsoleWrite( "Example 2" & @CRLF )
  ConsoleWrite( "---------" & @CRLF )
  ConsoleWrite( "Left =   " & DllStructGetData( $tRect, "Left"   ) & @CRLF )
  ConsoleWrite( "Top  =   " & DllStructGetData( $tRect, "Top"    ) & @CRLF )
  ConsoleWrite( "Right =  " & DllStructGetData( $tRect, "Right"  ) & @CRLF )
  ConsoleWrite( "Bottom = " & DllStructGetData( $tRect, "Bottom" ) & @CRLF )
  ConsoleWrite( @CRLF )


  ; Example 3 (strings)
  ; -------------------

  ; Input array
  Dim $aArray = [ "100.0", "200.0", "2900.0", "3800.0" ]

  ; Output rectangle structure
  $tRect = DllStructCreate( $tagRECT )
  $pRect = DllStructGetPtr( $tRect )

  ; Execute VariantToRect method
  $oUIAutomation.VariantToRect( $aArray, $pRect )

  ; Print structure data
  ConsoleWrite( "Example 3" & @CRLF )
  ConsoleWrite( "---------" & @CRLF )
  ConsoleWrite( "Left =   " & DllStructGetData( $tRect, "Left"   ) & @CRLF )
  ConsoleWrite( "Top  =   " & DllStructGetData( $tRect, "Top"    ) & @CRLF )
  ConsoleWrite( "Right =  " & DllStructGetData( $tRect, "Right"  ) & @CRLF )
  ConsoleWrite( "Bottom = " & DllStructGetData( $tRect, "Bottom" ) & @CRLF )
  ConsoleWrite( @CRLF )


  ; Example 4 (mix of types)
  ; ------------------------

  ; Input array
  Dim $aArray = [ 500, 600.0, "6500", "7400.0" ]

  ; Output rectangle structure
  $tRect = DllStructCreate( $tagRECT )
  $pRect = DllStructGetPtr( $tRect )

  ; Execute VariantToRect method
  $oUIAutomation.VariantToRect( $aArray, $pRect )

  ; Print structure data
  ConsoleWrite( "Example 4" & @CRLF )
  ConsoleWrite( "---------" & @CRLF )
  ConsoleWrite( "Left =   " & DllStructGetData( $tRect, "Left"   ) & @CRLF )
  ConsoleWrite( "Top  =   " & DllStructGetData( $tRect, "Top"    ) & @CRLF )
  ConsoleWrite( "Right =  " & DllStructGetData( $tRect, "Right"  ) & @CRLF )
  ConsoleWrite( "Bottom = " & DllStructGetData( $tRect, "Bottom" ) & @CRLF )
  ConsoleWrite( @CRLF )
EndFunc

UIAutomation.7z

Edited by LarsJ
Link to comment
Share on other sites

I think it's a problem only when a method takes an array as an input parameter. And that's probably not many methods. Perhaps only VariantToRect.

Link to comment
Share on other sites

In posts 15 - 17 it's demonstrated how it's possible to replace RectToVariant and VariantToRect methods of the UIAutomation object with our own methods. The purpose is to be able to access the variables inside the method functions. Particularly native AutoIt arrays can be accessed as safearrays. The original methods are executed inside our own methods.

The UIAutomation object is created with ObjCreateInterface. This technique can also be applied to objects created with ObjCreate eg. the Dictionary object. Here it's demonstrated with the Items method which returns an array.

ItemsInit replaces the original Items method with our own method:

Func ItemsInit()
  Static $pItemsOrg = 0
  If $pItemsOrg Then Return
  ; ItemsInit should be called only once

  _WinAPI_CoInitialize( $COINIT_APARTMENTTHREADED )
  ; Check Dictionary object in Registry for $COINIT_APARTMENTTHREADED

  Local $tIDispatch = _WinAPI_GUIDFromString( "{00020400-0000-0000-C000-000000000046}" )
  Local $sCLSID = _WinAPI_CLSIDFromProgID( "Scripting.Dictionary" )
  Local $tCLSID = _WinAPI_GUIDFromString( $sCLSID )
  Local $oDict

  ; Create Dictionary object
  CoCreateInstance( $tCLSID, 0, 1, $tIDispatch, $oDict ) ; 1 = $CLSCTX_INPROC_SERVER

  ; Replace original Items with new method
  ; And get original Items function pointer
  Local $pItemsNew = DllCallbackGetPtr( DllCallbackRegister( "Items", "long", "ptr" ) )
  $pItemsOrg = ReplaceVTableFuncPtr( Ptr( $oDict ), 13 * ( @AutoItX64 ? 8 : 4 ), $pItemsNew )
  ; 13 is the zero based number of Items method in IDictionary VTable
  ; This includes the methods of IUnknown (3) and IDispatch (4)

  ; Initialize Items by passing $pItemsOrg to the function
  Items( $pItemsOrg )

  _WinAPI_CoUninitialize()
EndFunc

To get a pointer to the object it's necessary to create the object with CoCreateInstance function. The pointer is needed to replace the original Items method in the VTable with our own method.

When ItemsInit is executed we can run the example:

Global $aItems

Example1()

Func Example1()
  ; Create Dictionary object
  Local $oDict = ObjCreate( "Scripting.Dictionary" )
  $oDict.Add( "Key1", "Item1" )
  $oDict.Add( "Key2", "Item2" )
  $oDict.Add( "Key3", "Item3" )
  $oDict.Items() ; Pass $aItems as global
  _ArrayDisplay( $aItems )
EndFunc

In this implementation a global array is needed to pass the array returned by Items method back to the function.

Our own Items method is coded in this way:

Func Items( $pSelf )
  Static $pItemsOrg = 0
  If $pItemsOrg = 0 Then
    $pItemsOrg = $pSelf
    Return
  EndIf

  ; Create variant to store array
  Local $tArray = DllStructCreate( $tagVARIANT )
  Local $pArray = DllStructGetPtr( $tArray )

  ; Execute original Items method
  DllCallAddress( "long", $pItemsOrg, "ptr", $pSelf, "ptr", $pArray )

  ; Inspect $pArray as variable
  ConsoleWrite( "Inspect variable $pArray" & @CRLF )
  ConsoleWrite( "------------------------" & @CRLF )
  InspectVariableMtd( $pArray )
  ConsoleWrite( @CRLF )

  ; Inspect $pArray as array
  ConsoleWrite( "Inspect array $pArray" & @CRLF )
  ConsoleWrite( "---------------------" & @CRLF )
  InspectArrayMtd( $pArray )
  ConsoleWrite( @CRLF )

  ; Print elements in $pArray
  ConsoleWrite( "Elements in $pArray" & @CRLF )
  ConsoleWrite( "-------------------" & @CRLF )
  Local $pSafeArray = DllStructGetData( DllStructCreate( "ptr", $pArray + 8 ), 1 )
  PrintSafeArray1D( $pSafeArray )
  ConsoleWrite( @CRLF )

  ; Store safearray in $aItems (global)
  AccVars_SafeArrayToArray( $pSafeArray, $aItems )
EndFunc

Output in SciTE console:

Inspect variable $pArray
------------------------
ptr  = 0x02CF12F8 ($pVariant)
vt   = 0x200C     (VT_ARRAY+VT_VARIANT, array of variants, safearray)
data = 0x02AC9C88 (pointer to safearray)

Inspect array $pArray
---------------------
Number of dimensions          = 1
Features flags                = 0x00000880 ($FADF_VARIANT+$FADF_HAVEVARTYPE, array of variants)
Variant type                  = 0x000C     (VT_VARIANT, variant data type)
Size of array element (bytes) = 16         (size of the variant structure)
Number of locks               = 0
Pointer to data               = 0x02AB8E68 (pvData)
Dimension 1:
  Elements in dimension       = 3
  Lower bound of dimension    = 0

Elements in $pArray
-------------------
Item1
Item2
Item3

To get an example like this to work it's essential that internal AutoIt code related to COM objects is working properly. This is not the case for AutoIt 3.3.14. The example is not working in AutoIt 3.3.14. You need AutoIt 3.3.10 or 3.3.12.

Examples\Subclassing\3) Dictionary object\

Edited by LarsJ
Zip deleted. Included in zip in bottom of first post.
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...