Popular Post LarsJ Posted December 3, 2016 Popular Post Share Posted December 3, 2016 (edited) Is it possible to pass a native AutoIt array as a parameter to a function coded in assembler, C, C++, C# or FreeBasic? And how is this possible? That's what this example is about. If possible, it may increase the performance of array manipulations significantly through fast functions of compiled code. The very, very short answers to the two questions above are: Yes. And through COM objects. Here is a small example that shows what it's all about. You can find the example in zip file in bottom of post (goto top of second post and scroll up a little bit). expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "Includes\AccVarsUtilities.au3" #include "Includes\InspectVariable.au3" #include "Includes\ArrayDisplayEx.au3" #include "Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example() Func Example() ; === UDF and flat assembler (fasm) code === Local $hTimer1 = TimerInit() ; --- Create and fill safearray --- ; Create safearray Local $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND ) DllStructSetData( $tSafeArrayBound, "cElements", 2^24 ) ; Number of elements in array DllStructSetData( $tSafeArrayBound, "lLbound", 0 ) ; Lower bound of array index Local $pSafeArray = SafeArrayCreate( $VT_I4, 1, $tSafeArrayBound ) ConsoleWrite( @CRLF & "--- Inspect safearray ---" & @CRLF ) InspectSafeArray( $pSafeArray ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; AutoIt code ;For $i = 0 To 2^24 - 1 ; DllStructSetData( $tSafeArrayData, 1, $i, $i + 1 ) ;Next ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; Get fasm code Local $sFasmCode = @AutoItX64 ? "0xB80000000089024883C204FFC0E2F6C3" _ ; Example-x64.asm : "0x5589E58B4D088B550CB800000000890283C20440E2F85DC20800" ; Example-x86.asm Local $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Return ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code to fill safearray DllCallAddress( "int", $pFasmCode, "int", 2^24, "ptr", $pSafeArrayData ) SafeArrayUnaccessData( $pSafeArray ) ; --- Convert safearray to native AutoIt array --- Local $aArray1 AccVars_SafeArrayToArray( $pSafeArray, $aArray1 ) ; <<<< The UDF >>>> ConsoleWrite( @CRLF & "Applied time to create and fill array (UDF and fasm): " & TimerDiff( $hTimer1 ) & @CRLF ) ; ------------------------------------------------ _ArrayDisplayEx( $aArray1, Default, "", 0x0010, "", "", "", 75 ) ConsoleWrite( @CRLF & "--- Inspect $aArray1 ---" & @CRLF ) InspectArray( $aArray1 ) $aArray1 = 0 ; === Pure AutoIt code === ConsoleWrite( @CRLF & "Executing pure AutoIt code to create and fill array (~10 seconds) ..." & @CRLF ) Local $hTimer2 = TimerInit() Local $aArray2[2^24] For $i = 0 To 2^24 - 1 $aArray2[$i] = $i Next ConsoleWrite( @CRLF & "Applied time to create and fill array (pure AutoIt): " & TimerDiff( $hTimer2 ) & @CRLF ) _ArrayDisplayEx( $aArray2, Default, "", 0x0010, "", "", "", 75 ) ConsoleWrite( @CRLF & "--- Inspect $aArray2 ---" & @CRLF ) InspectArray( $aArray2 ) $aArray2 = 0 EndFunc Line 53 is the crucial line of code: AccVars_SafeArrayToArray( $pSafeArray, $aArray1 ) ; <<<< The UDF >>>> Output in SciTE console should look like this: expandcollapse popup--- Inspect safearray --- Number of dimensions = 1 Features flags = 0x00000080 ($FADF_HAVEVARTYPE, array of variant type) Variant type = 0x0003 (VT_I4, 4 bytes signed integer) Size of array element (bytes) = 4 (size of the element structure) Number of locks = 0 Pointer to data = 0x02B80020 (pvData) Dimension 1: Elements in dimension = 16777216 Lower bound of dimension = 0 Applied time to create and fill array (UDF and fasm): 797.855906880413 --- Inspect $aArray1 --- 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 = 0x7FFF0020 (pvData) Dimension 1: Elements in dimension = 16777216 Lower bound of dimension = 0 Executing pure AutoIt code to create and fill array (~10 seconds) ... Applied time to create and fill array (pure AutoIt): 8670.46279987079 --- Inspect $aArray2 --- 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 = 0x7FFF0020 (pvData) Dimension 1: Elements in dimension = 16777216 Lower bound of dimension = 0 The UDF and fasm code is about 10 times faster than the pure AutoIt code. The code to populate the array is very simple. That's why the AutoIt code is doing relatively well compared to the UDF and fasm code. The technique implemented here can also be used to pass simple AutoIt variables to a function coded in a another language. This makes it possible to test on simple variables, rather than more complex arrays. And simple variables are needed in the final UDF. Post 7 contains a brief description of how the idea for this project arose. You'll find the following sections below: Accessing variables COM objects - COM objects can handle AutoIt arrays. Can this be exploited? AccessingVariablesTest.au3 - Test with UIAutomation::RectToVariant (Examples - Tests\Examples\0) Accessing variables\) Simple variables Variants - Introduction Basic strings - Introduction Examples - Tests\Examples\1) Simple variables\ Numeric variables (post 11) Numeric variants (post 11) Array variables Safearrays - Introduction Examples - Tests\Examples\2) Array variables\ Assembler code SafeArrayDisplay.au3 (post 24) Safearrays of integers (post 24) Internal conversions Exploiting conversions Avoiding conversions (Examples - Tests\Examples\3) Internal conversions\) Limitations (post 13) Final UDF - AccessingVariables.au3 AccessVariablesXY - 30 functions Restrictions - No literal params, no nested funcs Utility funcs - AccVarsUtilities.au3 InspectVariable - InspectVariable.au3 Using the UDF - Examples\Demo examples\4) Other demo examples\6) Using the UDF\ Subclassing (post 21) - Examples\Subclassing\ Examples Demo examples - Examples\Demo examples\ Assembler code - Tools to create fasm code not included Other demo examples - Examples\Demo examples\4) Other demo examples\ Real examples - Examples\Real examples\ sqlite3_get_table (post 25) What's next (post 26) Zip file For tests and for implementing the final UDF I've copied code written by monoceres, I've copied code from AutoItObject.au3 by the AutoItObject-Team: monoceres, trancexx, Kip, ProgAndy, and from CUIAutomation2.au3 by junkew. Lots of credit to these guys. Accessing variables AutoIt is a BASIC-like language. In BASIC-like languages simple variables are internally stored as variants, and arrays are internally stored as safearrays contained in variants. Assuming that an AutoIt variable is internally stored as a variant, is it possible to get a pointer to this variant? Assuming that an AutoIt array is internally stored as a safearray contained in a variant, then it should be possible to get a pointer to the safearray through the pointer to the variant. Why is a pointer to a safearray interesting? Because such a pointer can be passed as a parameter to a function coded in assembler, C, C++, C# or FreeBasic. We can thus access an AutoIt array directly from a function coded in another language, without the need to convert the array to a structure (DllStructCreate) or similar. In this way it's possible to code very fast array manipulation functions in a real compiled language. The crucial step is to get a pointer to the variant that contains the variable or array. COM objects If you have been using COM objects like the Dictionary ("Scripting.Dictionary") object, you know that this object can return AutoIt arrays in this way: $aKeys = $oDict.Keys() $aItems = $oDict.Items() In an example in "Obj/COM Reference" chapter in AutoIt Help file you can find this code line: $oExcel.activesheet.range("A1:O16").value = $aArray ; Fill cells with example numbers The Excel object seems to know how to handle a native AutoIt array. For objects created with ObjCreateInterface it's also easy to find examples where these objects understands how to handle native AutoIt arrays. An example is RectToVariant method of the UIAutomation object. This method converts a rectangle structure to an array: ; Create UIAutomation object Local $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $stag_IUIAutomation ) If Not IsObj( $oUIAutomation ) Then Return ConsoleWrite( "$oUIAutomation ERR" & @CRLF ) ConsoleWrite( "$oUIAutomation OK" & @CRLF ) ; Create rectangle structure Local $tRect = DllStructCreate( $tagRECT ) DllStructSetData( $tRect, "Left", 100 ) DllStructSetData( $tRect, "Top", 200 ) DllStructSetData( $tRect, "Right", 3000 ) DllStructSetData( $tRect, "Bottom", 4000 ) Local $aArray $oUIAutomation.RectToVariant( $tRect, $aArray ) ; Variant array: VT_ARRAY + VT_R8 If Not IsArray( $aArray ) Then Return ConsoleWrite( "$aArray ERR" & @CRLF ) ConsoleWrite( "$aArray OK" & @CRLF ) _ArrayDisplay( $aArray ) You can find the example (Tests\Examples\0) Accessing variables\Example1.au3) in the zip below. AccessingVariablesTest.au3 I've been playing with RectToVariant method of the UIAutomation object. And I have studied how the output array ($aArray in the code above) looks like in C++ (see also last code box in post 15). The description for RectToVariant in $stag_IUIAutomation looks like this: "RectToVariant hresult(" & ( @AutoItX64 ? "struct*;" : "struct;" ) & "variant*);" Note the last parameter "variant*". What's that? That's a pointer to a variant. Exactly what we need. You can get a pointer to the variant that contains $aArray in the example above in this way: Replace the RectToVariant method with your own function. Inside the function the pointer to the variant that contains $aArray is simply the last parameter. It's necessary to replace RectToVariant with our own function to be able to add code inside the function. And it's very important that the parameter type in the function (or method) description string is "variant*". Exactly this parameter type ensures that the parameter coming from the AutoIt code is converted to a pointer to a variant. Inside the RectToVariant method or our own function the last parameter is not a native AutoIt array. It's a pointer to a variant. This conversion between different data types is performed by internal AutoIt code. And the conversion is only performed in relation to objects. There is no such conversion in relation to eg. the DllCall function. That's why we have to deal with objects. The technique of replacing an object method with our own function has been seen many times before. Eg. in this old example by monoceres. Now when we have ObjCreateInterface it's much easier. You don't need much code: expandcollapse popup#include-once #include "..\Includes\Variant.au3" #include "..\Includes\SafeArray.au3" #include "..\Includes\Utilities.au3" Global $hAccessVariableFunction Func AccessVariable( $hAccessVariableFunc, ByRef $vVariable ) Static $oAccessVariable = AccessVariableInit() ; Init $oAccessVariable (only once) $hAccessVariableFunction = $hAccessVariableFunc ; Code to execute in VariableToVariant $oAccessVariable.VariableToVariant( $vVariable ) ; Execute VariableToVariant method EndFunc Func AccessVariableInit() ; Three locals copied from "IUIAutomation MS framework automate chrome, FF, IE, ...." by junkew ; https://www.autoitscript.com/forum/index.php?showtopic=153520 Local $sCLSID_CUIAutomation = "{FF48DBA4-60EF-4201-AA87-54103EEF594E}" Local $sIID_IUIAutomation = "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}" Local $stag_IUIAutomation = _ "f01 hresult();f02 hresult();f03 hresult();f04 hresult();f05 hresult();f06 hresult();f07 hresult();" & _ "f08 hresult();f09 hresult();f10 hresult();f11 hresult();f12 hresult();f13 hresult();f14 hresult();" & _ "f15 hresult();f16 hresult();f17 hresult();f18 hresult();f19 hresult();f20 hresult();f21 hresult();" & _ "f22 hresult();f23 hresult();f24 hresult();f25 hresult();f26 hresult();f27 hresult();f28 hresult();" & _ "f29 hresult();f30 hresult();f31 hresult();f32 hresult();f33 hresult();f34 hresult();f35 hresult();" & _ "f36 hresult();f37 hresult();f38 hresult();f39 hresult();f40 hresult();f41 hresult();" & _ "VariableToVariant hresult(variant*);" & _ ; "RectToVariant hresult(" & ( @AutoItX64 ? "struct*;" : "struct;" ) & "variant*);" "f43 hresult();f44 hresult();f45 hresult();f46 hresult();f47 hresult();f48 hresult();f49 hresult();" & _ "f50 hresult();f51 hresult();f52 hresult();f53 hresult();f54 hresult();f55 hresult();" ; Create AccessVariable object (Automation object) Local $oAccessVariable = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $stag_IUIAutomation ) If Not IsObj( $oAccessVariable ) Then Return SetError(1,0,1) ; Replace RectToVariant method with VariableToVariant method Local $pVariableToVariant = DllCallbackGetPtr( DllCallbackRegister( "VariableToVariant", "long", "ptr;ptr*" ) ) ReplaceVTableFuncPtr( Ptr( $oAccessVariable() ), ( 3 + 42 - 1 ) * ( @AutoItX64 ? 8 : 4 ), $pVariableToVariant ) Return $oAccessVariable EndFunc Func VariableToVariant( $pSelf, $pVariant ) $hAccessVariableFunction( $pVariant ) Return 0 ; $S_OK (COM constant) #forceref $pSelf EndFunc The code is saved in Tests\AccessingVariablesTest.au3 in the zip. To use the UDF you call AccessVariable function in the top. Note the ByRef keyword in the function. The ByRef keyword is very important. The function will not work without this keyword. Note also that the pointer to the variant in VariableToVariant function in bottom of the UDF is created by some internal AutoIt conversion code. And it's a local function parameter. The pointer is only valid within VariableToVariant and in the function you specify as a parameter when you call AccessVariable. As soon as VariableToVariant returns, the pointer is invalid. Let's try some small examples with simple variables and array variables. Simple variables Before we go to the examples let's take a quick look at variants and basic strings (BSTRs). Variants and basic strings are needed in our assembler, C, C++, C# or FreeBasic functions. Variants A variant is defined by this structure: Global Const $tagVARIANT = "word vt;word r1;word r2;word r3;ptr data; ptr" Only vt and data elements are used. The structure takes up 16/24 bytes when you're running 32/64 bit. Space for the data element at the end represents 2 pointers. This is 8/16 bytes when you're running 32/64 bit. If $pVariant is a pointer to a variant you can get vt and data elements in this way: Local $vt = DllStructGetData( DllStructCreate( "word", $pVariant ), 1 ) Local $data = DllStructGetData( DllStructCreate( "<vt>", $pVariant + 8 ), 1 ) The four word (word = 2 bytes) elements before the data element takes up 8 bytes. Common values of vt in AutoIt are: $VT_I4 = 3 ; Integer, "<vt>" = "int" $VT_R8 = 5 ; Double, "<vt>" = "double" $VT_BSTR = 8 ; Basic string, "<vt>" = "ptr" $VT_UI4 = 19 ; Pointer running 32 bit, "<vt>" = "ptr" $VT_UI8 = 21 ; Pointer running 64 bit, "<vt>" = "ptr" If $pVariant is a pointer to a variant which contains an array you'll always get this value for vt: $VT_ARRAY + $VT_VARIANT = 0x200C ; Pointer, "<vt>" = "ptr" A native AutoIt array is stored as a safearray ($VT_ARRAY = 0x2000) contained in a variant ($VT_VARIANT = 0x000C). The pointer to the safearray is stored in the data element of the variant. Variant constants and functions are defined in Includes\Variant.au3. Most of the code is shamelessly copied from AutoItObject.au3 by the AutoItObject-Team: monoceres, trancexx, Kip, ProgAndy. Basic strings Internally AutoIt strings are stored as basic strings or BSTRs. The pointer to the BSTR is stored in a variant. A BSTR is defined by this structure: Global Const $tagBSTR = & _ "dword Length;" & _ ; Length in bytes (2 * $iLen), does not include the Terminator "wchar String[" & $iLen & "];" & _ ; $iLen is wchars, $pBSTR = DllStructGetPtr( $tBSTR, "String" ) "word Terminator;" ; Two null characters Use this code to get the pointer to the BSTR (the pointer which is stored in a variant): Local $pBSTR = DllStructGetPtr( $tBSTR, "String" ) Note that the BSTR pointer is the start of the "String" element and not the start of the structure. Normally you do not handle BSTRs directly through this structure. You use the BSTR functions in Variant.au3. Also copied from AutoItObject.au3. You can find information about variant and BSTR conversion and manipulation functions here. Examples The examples are stored in "Tests\Examples\1) Simple variables\". There are three small examples. These examples are just AutoIt code. This is a part of Example2.au3: Local $sStr = "AutoIt" ConsoleWrite( "$sStr = " & $sStr & @CRLF ) AccessVariable( InspectVariableMtd, $sStr ) ; InspectVariableMtd is coded in Includes\InspectVariable.au3 AccessVariable( SetString, $sStr ) ; Shows how to use the AccessVariable function ConsoleWrite( "$sStr = " & $sStr & @CRLF ) Func SetString( $pVariant ) Local $pData = $pVariant + 8 ; See InspectVariable.au3 Local $tData = DllStructCreate( "ptr", $pData ) Local $pBStr = DllStructGetData( $tData, 1 ) SysFreeString( $pBStr ) ; See Variant.au3 $pBStr = SysAllocString( "Hello world" ) DllStructSetData( $tData, 1, $pBStr ) EndFunc The output i SciTE console should look like this: $sStr = AutoIt ptr = 0x006F9630 ($pVariant) vt = 0x0008 (VT_BSTR, basic string) data = AutoIt $sStr = Hello world Example3.au3 is similar to the example for VarGetType in the Help file. It prints the variant vt-values for the corresponding AutoIt data types. Array variables Before we go to the examples let's take a quick look at safearrays. Safearrays A safearray is defined by these structures: Global Const $tagSAFEARRAYBOUND = _ "ulong cElements;" & _ ; The number of elements in the dimension. "long lLbound;" ; The lower bound of the dimension. Global Const $tagSAFEARRAY = _ "ushort cDims;" & _ ; The number of dimensions. "ushort fFeatures;" & _ ; Flags, see below. "ulong cbElements;" & _ ; The size of an array element. "ulong cLocks;" & _ ; The number of times the array has been locked without a corresponding unlock. "ptr pvData;" & _ ; The data. $tagSAFEARRAYBOUND ; One $tagSAFEARRAYBOUND for each dimension. ; Examples ; 1D, 2D and 3D safearrays: Local $tagSAFEARRAY1D = $tagSAFEARRAY Local $tagSAFEARRAY2D = $tagSAFEARRAY & $tagSAFEARRAYBOUND Local $tagSAFEARRAY3D = $tagSAFEARRAY & $tagSAFEARRAYBOUND & $tagSAFEARRAYBOUND In AutoIt an array is stored as a safearray contained in a variant. The safearray is always an array of variants. That the safearray is contained in a variant means that the pointer to the safearray is stored in the data element of a variant. That the safearray is an array of variants means that the pvData element of the safearray points to a memory area which contains a continuous row of variant structures. If we're running 32 bit a variant takes up 16 bytes. For a 1D-array with three elements: Local $aArray[3] = [ 1, 2, 3 ] The pvData element of the safearray points to a memory area which takes up 48 bytes and consists of three variant structures. Normally you do not handle safearrays directly through these structures. You use the safearray functions in Includes\SafeArray.au3. Copied from AutoItObject.au3. You can find information about safearray conversion and manipulation functions here. Examples The examples are stored in "Tests\Examples\2) Array variables\". The examples are still just AutoIt code. Example1.au3 prints information for 1D-arrays of integers, floats and strings. Information for the integer array should look like this: $aInts = [ 0, 1, 2 ] --- InspectVariable $aInts --- ptr = 0x00990378 ($pVariant) vt = 0x200C (VT_ARRAY+VT_VARIANT, array of variants, safearray) data = 0x009981E8 (pointer to safearray) --- InspectVariable $aInts[0] --- ptr = 0x0098FEC8 ($pVariant) vt = 0x0003 (VT_I4, 4 bytes signed integer) data = 0 --- InspectArray $aInts --- 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 = 0x00998710 (pvData) Dimension 1: Elements in dimension = 3 Lower bound of dimension = 0 Example2.au3 prints information for 2D-arrays of integers, floats and strings, and prints the contents of the arrays through the data area of the safearray. pvData element of $tagSAFEARRAY structure is a pointer to the data area. Example3.au3 fills an existing array, $aArray[50000], with integers. Example4.au3 creates and fills an array with 50000 integers and assigns it to an uninitialized variable: $aArray (empty string). Assembler codeExample5.au3 is the first small example with fasm code (fasm = flat assembler, more info about fasm in one of the next sections). A 1D-array with 2^24 (16,777,216) elements is filled with integers from zero to 2^24 - 1. The array is first filled through AutoIt code. Then AccessVariable is used to fill the corresponding safearray through fasm code. There are two versions of the fasm code: A 32 bit version and a 64 bit version. AutoIt code (Example5.au3): expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\AccessingVariablesTest.au3" #include "..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example5() Func Example5() ConsoleWrite( "Executing AutoIt code to fill array (~10 seconds) ..." & @CRLF ) Local $aArray1[2^24] ; 2^24 = 16,777,216, maximum number of elements for an array Local $hTimer1 = TimerInit() For $i = 0 To 2^24 - 1 $aArray1[$i] = $i Next ConsoleWrite( "Time for AutoIt code to fill array: " & TimerDiff( $hTimer1 ) & @CRLF & @CRLF ) _ArrayDisplayEx( $aArray1 ) $aArray1 = 0 ConsoleWrite( "Executing FillArray to fill array ..." & @CRLF ) Local $aArray2[2^24] ; 2^24 = 16,777,216, maximum number of elements for an array Local $hTimer4 = TimerInit() AccessVariable( FillArray, $aArray2 ) Local $fTime4 = TimerDiff( $hTimer4 ) ConsoleWrite( "Time for FillArray to fill array (outside FillArray): " & $fTime4 & @CRLF ) ConsoleWrite( "Time for FillArray to fill array: " & $fTime4 & @CRLF ) _ArrayDisplayEx( $aArray2 ) $aArray2 = 0 EndFunc Func FillArray( $pVariant ) Local $hTimer3 = TimerInit() ; Pointer to safearray Local $pData = $pVariant + 8 Local $tData = DllStructCreate( "ptr", $pData ) Local $pSafeArray = DllStructGetData( $tData, 1 ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; Get fasm code Static $sFasmCode = @AutoItX64 ? "0xB80000000066C70203008942084883C218FFC0E2F0C3" _ ; Example5-x64.asm : "0x5589E58B4D088B550CB80000000066C702030089420883C21040E2F25DC20800" ; Example5-x86.asm Static $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Exit ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code Local $hTimer2 = TimerInit() DllCallAddress( "int", $pFasmCode, "int", 2^24, "ptr", $pSafeArrayData ) ConsoleWrite( "Time for FillArray to fill array (fasm code only): " & TimerDiff( $hTimer2 ) & @CRLF ) SafeArrayUnaccessData( $pSafeArray ) ConsoleWrite( "Time for FillArray to fill array (inside FillArray): " & TimerDiff( $hTimer3 ) & @CRLF ) EndFunc Note that the fasm code is executed inside FillArray function. And FillArray function is executed inside the $oAccessVariable.VariableToVariant method (in AccessingVariablesTest.au3). The pointer to the safearray data ($pSafeArrayData) is valid only inside this method. $pSafeArrayData is a pointer that points to the data area that contains the data of $aArray2. $pSafeArrayData however does not point directly to the same data as contained in $aArray2. Internal AutoIt conversions on function entry copies data from $aArray2 to the data area. Internal AutoIt conversions on function exit copies data from the data area to $aArray2. See next section. 32 bit fasm code (Example5-x86.asm): ; flat assembler code ; Translate AutoIt code to fasm code: ; For $i = 0 To 2^24 - 1 ; $aArray[$i] = $i ; Next ; Parameters: ; [ebp + 08] : iRows ; First parameter ; [ebp + 12] : pSafeArrayData ; Second parameter ; Init directive use32 ; 32 bit code ; Entry code push ebp ; Store base pointer on stack mov ebp, esp ; Use stack pointer as base pointer ; Function code mov ecx, [ebp + 08] ; ecx corresponds to 2^24 in "For $i = 0 To 2^24 - 1" mov edx, [ebp + 12] ; edx is pointer in safearray data area mov eax, 0 ; eax = 0, eax corresponds to $i iLoop: mov [edx], word 3 ; Set vt element in variant to 3 (VT_I4, integer) mov [edx + 08], eax ; Set data element in variant to eax ($i) add edx, 16 ; Add size of variant structure to edx inc eax ; eax += 1, corresponds to $i += 1 loop iLoop ; ecx -= 1, jump to iLoop if not zero ; Exit code pop ebp ; Restore base pointer from stack ret 08 ; Return and cleanup stack 64 bit fasm code (Example5-x64.asm): ; flat assembler code ; Translate AutoIt code to fasm code: ; For $i = 0 To 2^24 - 1 ; $aArray[$i] = $i ; Next ; Parameters: ; rcx : iRows ; First parameter, ecx corresponds to 2^24 in "For $i = 0 To 2^24 - 1" ; rdx : pSafeArrayData ; Second parameter, rdx is pointer in safearray data area ; Init directive use64 ; 64 bit code ; Function code mov eax, 0 ; eax = 0, eax corresponds to $i iLoop: mov [rdx], word 3 ; Set vt element in variant to 3 (VT_I4, integer) mov [rdx + 08], eax ; Set data element in variant to eax ($i) add rdx, 24 ; Add size of variant structure to rdx inc eax ; eax += 1, corresponds to $i += 1 loop iLoop ; ecx -= 1, jump to iLoop if not zero ; Exit code ret ; Return Output in SciTE console: Executing AutoIt code to fill array (~10 seconds) ... Time for AutoIt code to fill array: 9033.87383563625 Executing FillArray to fill array ... Time for FillArray to fill array (fasm code only): 28.5154490311906 Time for FillArray to fill array (inside FillArray): 28.6592369763861 Time for FillArray to fill array (outside FillArray): 2388.37594979003 Time for FillArray to fill array: 2388.37594979003 Why is there such a big difference in the time it takes to execute FillArray when the time is measured inside and outside the function? Internal conversions In "Obj/COM Reference" chapter, "COM Events" section and "Limitations on COM Events in AutoIt" subsection in AutoIt Help file you can find the following sentence: "... AutoIt uses its own variable scheme, which is not compatible to COM variables. This means that all values from Objects need to be converted into AutoIt variables ...". In the AutoIt website you can find small bits of information like this one. These internal conversions takes place between native AutoIt data types and COM data types, when AutoIt variables are passed to object methods as function parameters, and when COM variables are returned to AutoIt. There are two sets of conversions. Conversions on function entry (object method entry), and conversions on function exit. The previous section ended with this question: Why is there such a big difference in the time it takes to execute FillArray when the time is measured inside and outside the function (FillArray is executed inside the object method)? The large time difference is caused by the conversions. The conversions are performed by internal AutoIt code and Windows API functions. Both consists of compiled C++ code. Judging from the time the conversions takes (about 2.5 seconds on my PC), they seem to perform a complete (by value) copy of the entire array. The array with 16,777,216 integers. Even for compiled C++ code it takes time to copy such a large array. Example1.au3 ("Tests\Examples\3) Internal conversions\") shows how long time the conversions takes: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\AccessingVariablesTest.au3" #include "..\..\..\Includes\ArrayDisplayEx.au3" Opt( "MustDeclareVars", 1 ) Example1() Func Example1() ConsoleWrite( "Filling array of 16,777,216 integers (~10 seconds) ..." & @CRLF ) Local $aArray1[2^24] ; 2^24 = 16,777,216, maximum number of elements for an array For $i = 0 To 2^24 - 1 $aArray1[$i] = 1234 Next Local $hTimer = TimerInit() AccessVariable( ConversionTime, $aArray1 ) ConsoleWrite( "Time for conversion code to execute: " & TimerDiff( $hTimer ) & @CRLF & @CRLF ) _ArrayDisplayEx( $aArray1 ) $aArray1 = 0 ConsoleWrite( "Filling array of 16,777,216 doubles (~10 seconds) ..." & @CRLF ) Local $aArray2[2^24] ; 2^24 = 16,777,216, maximum number of elements for an array For $i = 0 To 2^24 - 1 $aArray2[$i] = 1234.5678 Next $hTimer = TimerInit() AccessVariable( ConversionTime, $aArray2 ) ConsoleWrite( "Time for conversion code to execute: " & TimerDiff( $hTimer ) & @CRLF & @CRLF ) _ArrayDisplayEx( $aArray2 ) $aArray2 = 0 ConsoleWrite( "Filling array of 1,048,576 100-chars strings (~5 seconds) ..." & @CRLF ) Local $aArray3[2^20] ; 2^20 = 1,048,576 For $i = 0 To 2^20 - 1 $aArray3[$i] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ; 100 chars Next $hTimer = TimerInit() AccessVariable( ConversionTime, $aArray3 ) ConsoleWrite( "Time for conversion code to execute: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray3 ) $aArray3 = 0 EndFunc ; Because this is an empty function the time measured above ; is the total time for all conversions of one array parameter. Func ConversionTime( $pVariant ) EndFunc Output in SciTE console: Filling array of 16,777,216 integers (~10 seconds) ... Time for conversion code to execute: 4303.24786930952 Filling array of 16,777,216 doubles (~10 seconds) ... Time for conversion code to execute: 4341.52716013949 Filling array of 1,048,576 100-chars strings (~5 seconds) ... Time for conversion code to execute: 2416.14925416137 Because 16,777,216 elements is the maximum number of elements in an array, the conversions will never take longer than 4.5 seconds on my PC for an array of integers or doubles. The time is about the same for integers and doubles because they are both stored as variants. A variant takes up 16 bytes when you're running 32 bit whether the variant contains a 4 bytes integer or an 8 bytes double. Why did the conversions in the previous section only take 2.5 seconds on my PC? Only about half as much time. I can only guess that this must be caused by the fact that this was an uninitialized array. And for an uninitialized array the conversions on function entry seems to be much faster. Conversions of an array of strings takes longer time. The variants which contains pointers to the strings has to be copied. And the strings (BSTRs) themselves has to be copied. That's the reason why the number of strings is limited to about 1,000,000. Still a decent number of strings. Although the assembler, C, C++, C# or FreeBasic code is lightning fast, the conversions especially for very large arrays means that the total execution time for the entire function (FillArray in the example in the previous section) will be increased with a few seconds. On the other hand we need the conversions. We don't want the array to be returned as a safearray contained in a variant. We want the array to be returned as a native AutoIt array. Exploiting conversions As I wrote in first section I've been playing with RectToVariant method of the UIAutomation object. In Remarks section in the link you can read this sentence: "The returned VARIANT has a data type of VT_ARRAY | VT_R8." (= VT_ARRAY + VT_R8). But this does not match the internal implementation of an AutoIt array which is VT_ARRAY + VT_VARIANT. Because RectToVariant returns a perfect AutoIt array this must mean that the conversion code also inspects the variant array type and converts it to a VT_ARRAY + VT_VARIANT type if necessary. I've tested that variants of types VT_ARRAY+VT_I4 (integers), VT_ARRAY+VT_R8 (doubles) and VT_ARRAY+VT_BSTR (strings) are properly converted to variants of type VT_ARRAY+VT_VARIANT. Rememeber that a variant of type VT_ARRAY+VT_I4 is a variant which contains a safearray (VT_ARRAY), where the pvData element of the safearray structure points to a memory area which contains a continuous row of integers (VT_I4). What is a continuous row of integers? Well, in AutoIt you create a continuous row of integers with DllStructCreate like this: Local $tIntegers = DllStructCreate( "int[50000]" ) This is a continuous row of 50000 integers. This means that if you are manipulating an array of integers in your assembler, C, C++, C# or FreeBasic code, you don't have to mess around with variant structures containing integers. You can simply use an array of integers. When you've finished the array manipulations you can store the integers as a fairly simple VT_ARRAY+VT_I4 variant (safearray of integers). And then you can leave it to the conversion code on function exit to convert the variant to a VT_ARRAY+VT_VARIANT variant (safearray of variants) which is understandable by AutoIt. And in fact, all these safearray data types are correctly converted to safearrays of variants: $VT_I2, $VT_I4, $VT_R4, $VT_R8, $VT_BSTR, $VT_BOOL, $VT_UI4, $VT_UI8 Example2a/b/c.au3, Example3a/b/c.au3 and Example4a/b/c.au3 demonstrates this technique in three slightly different ways for integers, doubles and strings. These techniques are needed in the assembler, C, C++, C# or FreeBasic code. Note that the examples are based on the final UDF and not the test UDF. See next section. This is Example2a.au3: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\Includes\AccessingVariables.au3" ; <<<< Final UDF (not test UDF) >>>> #include "..\..\..\Includes\InspectVariable.au3" #include <Array.au3> Opt( "MustDeclareVars", 1 ) Example2() Func Example2() Local $aArray ; Empty string ConsoleWrite( "--- InspectVariable ---" & @CRLF ) InspectVariable( $aArray ) ; $aArray is an empty string AccessVariables01( CreateArray, $aArray ) ConsoleWrite( "--- InspectVariable ---" & @CRLF ) InspectVariable( $aArray ) ; $aArray is an array ConsoleWrite( "--- InspectArray ---" & @CRLF ) InspectArray( $aArray ) _ArrayDisplay( $aArray ) EndFunc Func CreateArray( $pVariant ) ; --- Create and fill structure of integers --- ; Create structure Local $tIntegers = DllStructCreate( "int[50000]" ) Local $pIntegers = DllStructGetPtr( $tIntegers ) ; Fill structure ; Array manipulation For $i = 0 To 50000 - 1 DllStructSetData( $tIntegers, 1, $i, $i + 1 ) Next ; --- Create and fill safearray --- ; Create safearray Local $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND ) DllStructSetData( $tSafeArrayBound, "cElements", 50000 ) ; Number of elements in array DllStructSetData( $tSafeArrayBound, "lLbound", 0 ) ; Lower bound of array index Local $pSafeArray = SafeArrayCreate( $VT_I4, 1, $tSafeArrayBound ) ; <<<< Not a proper AutoIt safearray >>>> ; This is a safearray of integers and not variants as a usual AutoIt array ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; Create structure from safearray data area Local $tSafeArrayBytes = DllStructCreate( "byte[200000]", $pSafeArrayData ) ; Fill safearray data area with data from $tIntegers DllStructSetData( $tSafeArrayBytes, 1, DllStructGetData( DllStructCreate( "byte[200000]", $pIntegers ), 1 ) ) ; This technique only works with byte's, char's and wchar's SafeArrayUnaccessData( $pSafeArray ) ; --- Set variant to match an array of integers --- ; Set vt element to $VT_ARRAY + $VT_I4 Local $tvt = DllStructCreate( "word", $pVariant ) DllStructSetData( $tvt, 1, $VT_ARRAY + $VT_I4 ) ; <<<< Not a proper AutoIt array >>>> ; This is an array of integers and not variants as a usual AutoIt array ; Set data element to safearray pointer Local $pData = $pVariant + 8 Local $tData = DllStructCreate( "ptr", $pData ) DllStructSetData( $tData, 1, $pSafeArray ) ; <<<< On function exit the safearray contained in a variant is converted to a native AutoIt array >>>> EndFunc This is output in SciTE console. The array is displayed with _ArrayDisplay. --- InspectVariable --- ptr = 0x00D0E698 ($pVariant) vt = 0x0008 (VT_BSTR, basic string) data = --- InspectVariable --- ptr = 0x02B78028 ($pVariant) vt = 0x200C (VT_ARRAY+VT_VARIANT, array of variants, safearray) data = 0x00CAB170 (pointer to safearray) --- InspectArray --- 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 = 0x02EF0020 (pvData) Dimension 1: Elements in dimension = 50000 Lower bound of dimension = 0 Avoiding conversions For large arrays conversions may take quite some time. The conversions cannot be avoided, but in some situations they can be limited. Eg. a large array with 10 columns to sort by four columns (like the details view in Windows Explorer can be sorted by name, date, type and size). In this situation four indexes can be used to implement the sorting. And because it's a large array the indexes should be created with compiled code. Instead of converting the large array four times (once for each index), it would be much better to get a pointer to the safearray, and then reuse this pointer for each index. It'll only require one conversion of the large array to get a pointer to the safearray. For this purpose two functions in Includes\AccVarsUtilities.au3 can be used: AccVars_ArrayToSafeArray which creates a pointer to a safearray from a native AutoIt array, and AccVars_SafeArrayToArray which creates a native AutoIt array from a pointer to a safearray. More about these functions i a later section. Example5.au3 shows how long time it takes for the two functions to create a safearray and an array: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\Includes\AccessingVariables.au3" ; <<<< Final UDF (not test UDF) >>>> #include "..\..\..\Includes\AccVarsUtilities.au3" #include "..\..\..\Includes\ArrayDisplayEx.au3" Opt( "MustDeclareVars", 1 ) Example5() Func Example5() ; --- Array of integers --- ConsoleWrite( "Filling array of 16,777,216 integers (~10 seconds) ..." & @CRLF ) Local $aArray1[2^24] ; 2^24 = 16,777,216, maximum number of elements for an array For $i = 0 To 2^24 - 1 $aArray1[$i] = 1234 Next Local $pSafeArray1 Local $hTimer = TimerInit() AccVars_ArrayToSafeArray( $aArray1, $pSafeArray1 ) ConsoleWrite( "Time for AccVars_ArrayToSafeArray to execute: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray1 ) $aArray1 = 0 Local $aArray2 $hTimer = TimerInit() AccVars_SafeArrayToArray( $pSafeArray1, $aArray2 ) ConsoleWrite( "Time for AccVars_SafeArrayToArray to execute: " & TimerDiff( $hTimer ) & @CRLF & @CRLF ) _ArrayDisplayEx( $aArray2 ) $aArray2 = 0 ; --- Array of doubles --- ConsoleWrite( "Filling array of 16,777,216 doubles (~10 seconds) ..." & @CRLF ) Local $aArray3[2^24] ; 2^24 = 16,777,216, maximum number of elements for an array For $i = 0 To 2^24 - 1 $aArray3[$i] = 1234.5678 Next Local $pSafeArray3 $hTimer = TimerInit() AccVars_ArrayToSafeArray( $aArray3, $pSafeArray3 ) ConsoleWrite( "Time for AccVars_ArrayToSafeArray to execute: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray3 ) $aArray3 = 0 Local $aArray4 $hTimer = TimerInit() AccVars_SafeArrayToArray( $pSafeArray3, $aArray4 ) ConsoleWrite( "Time for AccVars_SafeArrayToArray to execute: " & TimerDiff( $hTimer ) & @CRLF & @CRLF ) _ArrayDisplayEx( $aArray4 ) $aArray4 = 0 ; --- Array of strings --- ConsoleWrite( "Filling array of 1,048,576 100-chars strings (~5 seconds) ..." & @CRLF ) Local $aArray5[2^20] ; 2^20 = 1,048,576 For $i = 0 To 2^20 - 1 $aArray5[$i] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ; 100 chars Next Local $pSafeArray5 $hTimer = TimerInit() AccVars_ArrayToSafeArray( $aArray5, $pSafeArray5 ) ConsoleWrite( "Time for AccVars_ArrayToSafeArray to execute: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray5 ) $aArray5 = 0 Local $aArray6 $hTimer = TimerInit() AccVars_SafeArrayToArray( $pSafeArray5, $aArray6 ) ConsoleWrite( "Time for AccVars_SafeArrayToArray to execute: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray6 ) $aArray6 = 0 EndFunc Output in SciTE console: Filling array of 16,777,216 integers (~10 seconds) ... Time for AccVars_ArrayToSafeArray to execute: 4493.62536677101 Time for AccVars_SafeArrayToArray to execute: 1164.59904458766 Filling array of 16,777,216 doubles (~10 seconds) ... Time for AccVars_ArrayToSafeArray to execute: 4549.6914981711 Time for AccVars_SafeArrayToArray to execute: 1159.73609940987 Filling array of 1,048,576 100-chars strings (~5 seconds) ... Time for AccVars_ArrayToSafeArray to execute: 2419.86058615209 Time for AccVars_SafeArrayToArray to execute: 675.048988223156 Note that a consequence of the conversions is that the input safearray for AccVars_SafeArrayToArray is deleted. If you need to use the same safearray several times you must make a copy of the safearray with SafeArrayCopy function in Includes\SafeArray.au3. In the example with fasm code in previous section we got this result: Time for FillArray to fill array: 2388.37594979003 In top of post we got this result for more or less the same code: Applied time to create and fill array (UDF and fasm): 797.855906880413 Three times as fast. This is a consequence of limiting conversions. In a few situations it may be possible to completely avoid conversions. Eg. if the data will be used to fill the rows in a virtual listview. In a virtual listview rows are filled through $LVN_GETDISPINFOW notifications. And for $LVN_GETDISPINFOW notifications it doesn't matter whether the data source is a native AutoIt array or the data structure in a safearray. Final UDF The whole idea of this UDF is to utilize the information we already know: We know that COM objects and methods are working very well in AutoIt. We also know that the only variable type as COM object methods are familiar with is variants (only completely true for IDispatch based objects and objects of Automation compatible types). But how is it possible for an object method to handle variables that are passed to the method as native AutoIt variables, when the only variable type as COM object methods are familiar with is variants? The only explanation is that native AutoIt input parameters must be converted to variants immediately before the method code is executed, and that variant output parameters must be converted to native AutoIt variables immediately after the method code has finished. These conversions must be carried out by internal AutoIt code. The final UDF (Includes\AccessingVariables.au3) makes it possible to pass native AutoIt arrays and simple variables as parameters to a function coded in another language eg. assembler, C, C++, C# or FreeBasic. This is done by executing the function inside an object method and passing the array and variable parameters as variant pointers. Internal AutoIt conversions ensures that AutoIt variables outside the method are properly converted to COM variants inside the method. And the other way around. In the test UDF (Tests\AccessingVariablesTest.au3) only one AutoIt variable is passed to the object method by AccessVariable function. The final UDF contains 30 functions named AccessVariables01 - AccessVariables30 where the number indicates the number of AutoIt variables passed to the object method. Manipulating AutoIt variables in another language (a compiled language) is especially relevant for arrays with a large number of elements, or smaller arrays where complex calculations are performed on the elements. AccessVariablesXY To use the UDF you call one of the AccessVariablesXY functions eg. AccessVariables01. AccessVariables01 is coded in this way: Func AccessVariables01( $hAccVars_Method, ByRef $vVariable01 ) $hAccVars_MethodFunc = $hAccVars_Method $oAccVars_Object.AccVars_VariableToVariant01( $vVariable01 ) EndFunc The first parameter is a function type parameter (the name of an AutoIt function). You must code this function yourself. The function is assigned to the global variable $hAccVars_MethodFunc. The second parameter is an AutoIt variable. Typically an array. This parameter is passed to the AccVars_VariableToVariant01 object method. AccessVariables01 is just a wrapper function to make it easier to call the object method. The description string for the object method looks like this: "AccVars_VariableToVariant01 hresult(variant*);" The AutoIt variable is passed to the method as a variant pointer. And the method is coded in this way: Func AccVars_VariableToVariant01( $pSelf, $pVariant01 ) $hAccVars_MethodFunc( $pVariant01 ) Return 0 ; $S_OK (COM constant) #forceref $pSelf EndFunc The first parameter $pSelf must be a pointer to the object $oAccVars_Object. This is a COM rule. The second parameter is the AutoIt variable you passed to the AccessVariables01 function. But inside AccVars_VariableToVariant01 this is not an AutoIt variable any more. Inside AccVars_VariableToVariant01 it's a pointer to a variant. Inside AccVars_VariableToVariant01 the $hAccVars_MethodFunc is called and the variant pointer is passed as a parameter. $hAccVars_MethodFunc is the function you passed to AccessVariables01. If you passed a native AutoIt array to AccessVariables01, you can extract this array as a pointer to a safearray inside $hAccVars_MethodFunc. And this pointer or a pointer directly to the safearray data area you can pass to a function coded in assembler, C, C++, C# or FreeBasic. When $hAccVars_MethodFunc is finished zero is returned to indicate that everything is OK. Restrictions Because the $vVariableXY parameters in AccessVariablesXY functions are ByRef parameters you cannot pass literal values as parameters. You must store the literal value in a variable and pass the variable. You cannot call an AccessVariablesXY function inside another AccessVariablesXY function. No nested function calls. This also means that you cannot call InspectVariable or InspectArray inside an AccessVariablesXY function. But you can call InspectSafeArray. See InspectVariable section below for info about Inspect-functions. Utility funcs Typically arrays are passed to the object methods. But this does not exclude the need to pass simple variables to the object methods. Eg. the number of rows and columns in the arrays. Simple variables are also passed to object methods as variant pointers. In most cases you probably want to treat simple variables as native AutoIt variables inside object methods and not as variant pointers. AccVars_VariantToVariable (Includes\AccVarsUtilities.au3) converts variant pointers to native AutoIt variables. Arrays are not converted. See "Examples\Demo examples\4) Other demo examples\2) Multiple parameters\Example1.au3". AccVars_VariableToVariant converts native AutoIt variables to variant pointers. Arrays are not converted. See "Examples\Demo examples\4) Other demo examples\6) Using the UDF\Example6.au3" AccVars_ArrayToSafeArray and AccVars_SafeArrayToArray are already mentioned above. AccVars_ArrayToSafeArray creates a pointer to a safearray from a native AutoIt array. AccVars_SafeArrayToArray creates a native AutoIt array from a pointer to a safearray. "Examples\Demo examples\4) Other demo examples\4) ArrayToSafearray\" includes a few examples of AccVars_ArrayToSafeArray. "Examples\Demo examples\4) Other demo examples\5) SafearrayToArray\" includes a few examples of AccVars_SafeArrayToArray. In both folders Example1.au3 shows how everything can be done manually without using the two functions. And Example2.au3 shows how it can be done using the two functions. Note that a consequence of the conversions is that the input safearray for AccVars_SafeArrayToArray is deleted. If you need to use the same safearray several times you must make a copy of the safearray with SafeArrayCopy function in Includes\SafeArray.au3. InspectVariable Includes\InspectVariable.au3 contains three functions: InspectVariable (corresponds to InspectVariableMtd), InspectArray (corresponds to InspectVariableMtd) and InspectSafeArray. The first two are used in many of the test examples. The last is used in the examples for AccVars_ArrayToSafeArray and AccVars_SafeArrayToArray. InspectVariable and InspectArray takes AutoIt variables as input parameters and prints information about the variables in SciTE console after they have been converted to variants. InspectSafeArray prints information about safearrays in SciTE console. Using the UDF Here are six small scripts which shows how to use the UDF. You can use these scripts as templates for your own code. The scripts are saved in "Examples\Demo examples\4) Other demo examples\6) Using the UDF\". The first four scripts is about creating and filling a new array. The array is in all cases a 1D-array with 2^24 (16,777,216) integer elements. In Example1.au3 an AutoIt integer structure is filled with data, the data in the structure is copied into a safearray which is converted to a native AutoIt array: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\..\Includes\AccVarsUtilities.au3" #include "..\..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example1() Func Example1() Local $hTimer = TimerInit() ; --- Create and fill structure of integers --- ; Create structure of integers Local $tIntegers = DllStructCreate( "int[" & 2^24 & "]" ) ; 2^24 = 16,777,216 elements Local $pIntegers = DllStructGetPtr( $tIntegers ) ; AutoIt code ;For $i = 0 To 2^24 - 1 ; DllStructSetData( $tIntegers, 1, $i, $i + 1 ) ;Next ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; Get fasm code Local $sFasmCode = @AutoItX64 ? "0xB80000000089024883C204FFC0E2F6C3" _ ; Example1-x64.asm : "0x5589E58B4D088B550CB800000000890283C20440E2F85DC20800" ; Example1-x86.asm Local $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Return ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code to fill structure DllCallAddress( "int", $pFasmCode, "int", 2^24, "ptr", $pIntegers ) ; --- Create and fill safearray --- ; Create safearray Local $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND ) DllStructSetData( $tSafeArrayBound, "cElements", 2^24 ) ; Number of elements in array DllStructSetData( $tSafeArrayBound, "lLbound", 0 ) ; Lower bound of array index Local $pSafeArray = SafeArrayCreate( $VT_I4, 1, $tSafeArrayBound ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; Create structure from safearray data area Local $tSafeArrayBytes = DllStructCreate( "byte[" & 4 * 2^24 & "]", $pSafeArrayData ) ; Fill safearray data area with data from $tIntegers DllStructSetData( $tSafeArrayBytes, 1, DllStructGetData( DllStructCreate( "byte[" & 4 * 2^24 & "]", $pIntegers ), 1 ) ) ; This technique only works with byte's, char's and wchar's SafeArrayUnaccessData( $pSafeArray ) $tIntegers = 0 ; --- Convert safearray to native AutoIt array --- Local $aArray AccVars_SafeArrayToArray( $pSafeArray, $aArray ) ConsoleWrite( "Time to fill array: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray ) $aArray = 0 EndFunc ; Time to fill array: 939.174260660843 This example is simple because you're not dealing with variants or safearrays at all in your assembler, C, C++, C# or FreeBasic code. In Example2.au3 the data area of a safearray is directly filled with integers and the safearray is converted to a native AutoIt array: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\..\Includes\AccVarsUtilities.au3" #include "..\..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example2() Func Example2() Local $hTimer = TimerInit() ; --- Create and fill safearray --- ; Create safearray Local $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND ) DllStructSetData( $tSafeArrayBound, "cElements", 2^24 ) ; Number of elements in array DllStructSetData( $tSafeArrayBound, "lLbound", 0 ) ; Lower bound of array index Local $pSafeArray = SafeArrayCreate( $VT_I4, 1, $tSafeArrayBound ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; AutoIt code ;For $i = 0 To 2^24 - 1 ; DllStructSetData( $tSafeArrayData, 1, $i, $i + 1 ) ;Next ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; Get fasm code Local $sFasmCode = @AutoItX64 ? "0xB80000000089024883C204FFC0E2F6C3" _ ; Example1-x64.asm : "0x5589E58B4D088B550CB800000000890283C20440E2F85DC20800" ; Example1-x86.asm Local $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Exit ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code to fill safearray DllCallAddress( "int", $pFasmCode, "int", 2^24, "ptr", $pSafeArrayData ) SafeArrayUnaccessData( $pSafeArray ) ; --- Convert safearray to native AutoIt array --- Local $aArray AccVars_SafeArrayToArray( $pSafeArray, $aArray ) ConsoleWrite( "Time to fill array: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray ) $aArray = 0 EndFunc ; Time to fill array: 805.347826773543 Example2 is more efficient than Example1 because you're avoiding the integer structure. The conversion in Example3.au3 is not performed with AccVars_SafeArrayToArray but with your own SafeArrayToArray method function which is executed through AccessVariables02: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\..\Includes\AccVarsUtilities.au3" #include "..\..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example3() Func Example3() Local $hTimer = TimerInit() ; --- Create and fill safearray --- ; Create safearray Local $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND ) DllStructSetData( $tSafeArrayBound, "cElements", 2^24 ) ; Number of elements in array DllStructSetData( $tSafeArrayBound, "lLbound", 0 ) ; Lower bound of array index Local $pSafeArray = SafeArrayCreate( $VT_I4, 1, $tSafeArrayBound ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; AutoIt code ;For $i = 0 To 2^24 - 1 ; DllStructSetData( $tSafeArrayData, 1, $i, $i + 1 ) ;Next ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; Get fasm code Local $sFasmCode = @AutoItX64 ? "0xB80000000089024883C204FFC0E2F6C3" _ ; Example1-x64.asm : "0x5589E58B4D088B550CB800000000890283C20440E2F85DC20800" ; Example1-x86.asm Local $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Exit ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code to fill safearray DllCallAddress( "int", $pFasmCode, "int", 2^24, "ptr", $pSafeArrayData ) SafeArrayUnaccessData( $pSafeArray ) ; --- Convert safearray to native AutoIt array --- Local $aArray AccessVariables02( SafeArrayToArray, $pSafeArray, $aArray ) ConsoleWrite( "Time to fill array: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray ) $aArray = 0 EndFunc Func SafeArrayToArray( $pSafeArray, $pArray ) ; --- Set $pArray to match an AutoIt array --- ; Set vt element to $VT_ARRAY + $VT_VARIANT DllStructSetData( DllStructCreate( "word", $pArray ), 1, $VT_ARRAY + $VT_VARIANT ) ; Set data element to safearray pointer DllStructSetData( DllStructCreate( "ptr", $pArray + 8 ), 1, AccVars_VariantToVariable( $pSafeArray ) ) ; <<<< On function exit $pArray (safearray contained in a variant) is converted to an AutoIt array >>>> EndFunc ; Time to fill array: 800.813455228795 Example3 is a more general example than Example2. Creation of the safearray is moved from AutoIt code to your assembler, C, C++, C# or FreeBasic code in Example4.au3: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\..\Includes\AccessingVariables.au3" #include "..\..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example4() Func Example4() ; --- Create and fill array --- Local $aArray Local $hTimer = TimerInit() AccessVariables01( CreateArray, $aArray ) ConsoleWrite( "Time to fill array: " & TimerDiff( $hTimer ) & @CRLF ) _ArrayDisplayEx( $aArray ) $aArray = 0 EndFunc Func CreateArray( $pArray ) ; --- Create and fill safearray --- ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; Create safearray Local $tSafeArrayBound = DllStructCreate( $tagSAFEARRAYBOUND ) DllStructSetData( $tSafeArrayBound, "cElements", 2^24 ) ; Number of elements in array DllStructSetData( $tSafeArrayBound, "lLbound", 0 ) ; Lower bound of array index Local $pSafeArray = SafeArrayCreate( $VT_I4, 1, $tSafeArrayBound ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; AutoIt code ;For $i = 0 To 2^24 - 1 ; DllStructSetData( $tSafeArrayData, 1, $i, $i + 1 ) ;Next ; Get fasm code Local $sFasmCode = @AutoItX64 ? "0xB80000000089024883C204FFC0E2F6C3" _ ; Example1-x64.asm : "0x5589E58B4D088B550CB800000000890283C20440E2F85DC20800" ; Example1-x86.asm Local $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Exit ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code to fill safearray DllCallAddress( "int", $pFasmCode, "int", 2^24, "ptr", $pSafeArrayData ) SafeArrayUnaccessData( $pSafeArray ) ; --- Set $pArray to match an AutoIt array --- ; Set vt element to $VT_ARRAY + $VT_VARIANT DllStructSetData( DllStructCreate( "word", $pArray ), 1, $VT_ARRAY + $VT_VARIANT ) ; Set data element to safearray pointer DllStructSetData( DllStructCreate( "ptr", $pArray + 8 ), 1, $pSafeArray ) ; <<<< On function exit $pArray (safearray contained in a variant) is converted to an AutoIt array >>>> EndFunc ; Time to fill array: 799.20854202792 If you code in assembler it's more advantageous to create the safearray in AutoIt code. The last two scripts is about manipulating an existing array. In both scripts a 1D-array with 2^23 (8,388,608) random integers is searched to find the minimum and maximum value. In Example5.au3 AccVars_ArrayToSafeArray is used to convert the AutoIt array to a safearray: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\..\Includes\AccVarsUtilities.au3" ;#include "..\..\..\..\Includes\InspectVariable.au3" #include "..\..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example5() Func Example5() ; --- Create test array of random integers --- ConsoleWrite( "Create test array (~10 seconds) ..." & @CRLF ) Local $aArray[2^23] ; 2^23 = 8,388,608 For $i = 0 To 2^23 - 1 $aArray[$i] = Random( 0, 2^31-1, 1 ) Next _ArrayDisplayEx( $aArray ) ; --- Convert native AutoIt array to safearray --- Local $hTimer = TimerInit() Local $pSafeArray AccVars_ArrayToSafeArray( $aArray, $pSafeArray ) ;InspectSafeArray( $pSafeArray ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; --- Test safearray for min/max values --- ; AutoIt code ;Local $iMin1 = 2^31-1, $iMax1 = 0 ;For $i = 0 To 2^23 - 1 ; If $aArray[$i] < $iMin1 Then $iMin1 = $aArray[$i] ; If $aArray[$i] > $iMax1 Then $iMax1 = $aArray[$i] ;Next ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; $iMin, $iMax storage for fasm code Local $tMinMax = DllStructCreate( "int iMin;int iMax" ) DllStructSetData( $tMinMax, "iMin", 2^31-1 ) DllStructSetData( $tMinMax, "iMax", 0 ) Local $pMinMax = DllStructGetPtr( $tMinMax ) ; iMin = $pMinMax, iMax = $pMinMax + 4 (int = 4 bytes) ; Get fasm code Static $sFasmCode = @AutoItX64 ? "0x4D8B4804418B003B420876038B4208443B4A087304448B4A084883C218E2E841890045894804C3" _ ; Example5-x64.asm : "0x5589E58B4D088B550C8B45108B58048B003B420876038B42083B5A0873038B5A0883C210E2EB8B55108902895A045DC20C00" ; Example5-x86.asm Static $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Exit ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code DllCallAddress( "int", $pFasmCode, "int", 2^23, "ptr", $pSafeArrayData, "ptr", $pMinMax ) ConsoleWrite( "$iMin, $iMax = " & DllStructGetData( $tMinMax, "iMin" ) & ", " & DllStructGetData( $tMinMax, "iMax" ) & @CRLF ) ConsoleWrite( "Time to test array: " & TimerDiff( $hTimer ) & @CRLF ) SafeArrayDestroy( $pSafeArray ) $aArray = 0 EndFunc ; $iMin, $iMax = 262, 2147483599 ; Time to test array: 2293.62874670214 In Example6.au3 the conversion is done in your own TestArray method function which is executed through AccessVariables03: expandcollapse popup;#AutoIt3Wrapper_UseX64=y #include "..\..\..\..\Includes\AccVarsUtilities.au3" #include "..\..\..\..\Includes\ArrayDisplayEx.au3" #include "..\..\..\..\Includes\FasmUtils.au3" Opt( "MustDeclareVars", 1 ) Example6() Func Example6() ; --- Create test array of random integers --- ConsoleWrite( "Create test array (~10 seconds) ..." & @CRLF ) Local $aArray[2^23] ; 2^23 = 8,388,608 For $i = 0 To 2^23 - 1 $aArray[$i] = Random( 0, 2^31-1, 1 ) Next _ArrayDisplayEx( $aArray ) ; --- Test array for min/max values --- ; AutoIt code ;Local $iMin1 = 2^31-1, $iMax1 = 0 ;For $i = 0 To 2^23 - 1 ; If $aArray[$i] < $iMin1 Then $iMin1 = $aArray[$i] ; If $aArray[$i] > $iMax1 Then $iMax1 = $aArray[$i] ;Next Local $hTimer = TimerInit() Local $iMin = 2^31-1, $iMax = 0 AccessVariables03( TestArray, $aArray, $iMin, $iMax ) ConsoleWrite( "$iMin, $iMax = " & $iMin & ", " & $iMax & @CRLF ) ConsoleWrite( "Time to test array: " & TimerDiff( $hTimer ) & @CRLF ) $aArray = 0 EndFunc Func TestArray( $pvArray, $pvMin, $pvMax ) ; <<<< On function entry $aArray is converted to $pvArray (safearray contained in a variant) >>>> ; Pointer to safearray Local $pData = $pvArray + 8 Local $tData = DllStructCreate( "ptr", $pData ) Local $pSafeArray = DllStructGetData( $tData, 1 ) ; Pointer to data Local $pSafeArrayData SafeArrayAccessData( $pSafeArray, $pSafeArrayData ) ; <<<< Execute your assembler, C, C++, C# or FreeBasic code at this point >>>> ; $iMin, $iMax storage for fasm code Local $tMinMax = DllStructCreate( "int iMin;int iMax" ) DllStructSetData( $tMinMax, "iMin", AccVars_VariantToVariable( $pvMin ) ) DllStructSetData( $tMinMax, "iMax", AccVars_VariantToVariable( $pvMax ) ) Local $pMinMax = DllStructGetPtr( $tMinMax ) ; iMin = $pMinMax, iMax = $pMinMax + 4 (int = 4 bytes) ; Get fasm code Static $sFasmCode = @AutoItX64 ? "0x4D8B4804418B003B420876038B4208443B4A087304448B4A084883C218E2E841890045894804C3" _ ; Example5-x64.asm : "0x5589E58B4D088B550C8B45108B58048B003B420876038B42083B5A0873038B5A0883C210E2EB8B55108902895A045DC20C00" ; Example5-x86.asm Static $pFasmCode = FasmGetBinaryString( $sFasmCode, 64 ) If Not $pFasmCode Then Exit ConsoleWrite( "$pFasmCode ERR" & @CRLF ) ; Execute fasm code DllCallAddress( "int", $pFasmCode, "int", 2^23, "ptr", $pSafeArrayData, "ptr", $pMinMax ) ; Get $iMin, $iMax AccVars_VariableToVariant( DllStructGetData( $tMinMax, "iMin" ), $pvMin ) AccVars_VariableToVariant( DllStructGetData( $tMinMax, "iMax" ), $pvMax ) SafeArrayUnaccessData( $pSafeArray ) EndFunc ; $iMin, $iMax = 39, 2147483610 ; Time to test array: 2182.54509368366 Examples There are two obvious uses for this: To create and fill a new array. And to manipulate one or more existing arrays. Demo examples "Examples\Demo examples\" contains a few simple demonstration examples. 1) Create and fill new array There are already examples above where a 1D-array is filled with integers. As a demonstration example of creating and filling out an array, a little more complex 2D-array is filled with integers. Example2.au3 is an optimized version of Example1.au3. See post 6. 2) Manipulate existing array As a demonstration example of manipulating a single existing array, minimum and maximum values are determined in a 1D-array of random integers. This is the same as Example5 and Example6 above. See post 6. 3) Concatenate 1D-arrays As a demonstration example of manipulating several existing arrays 2, 8 and 16 1D-arrays of integers with 2^20 (1,048,576) rows are concatenated into a 2D-array with 2, 8 and 16 columns. See post 6. In all three demonstration examples flat assembler (fasm) is used as the fast language to manipulate the arrays. Note that in the three examples the pure AutoIt code is doing relatively well compared to the fasm code. The reason is that the code in the array loops is very simple. For more complex code the AutoIt code will not do so well. Assembler code flat assembler (fasm) is available as a DLL-file. This means that all you need to create fasm code is this DLL and the DllCall command. DllCall is a native AutoIt command. The DLL-file is a 32 bit DLL, but it can generate both 32 and 64 bit fasm code. Furthermore, the assembled code (the executable machine code) is very compact. None of the example programs are larger than 64 bytes. There are several threads in these forums regarding fasm and development of fasm code. And there are threads in the German forums regarding fasm. For the example programs included here I'm just using the DLL-file, DllCall and a few AutoIt functions to make things easier. Nothing of this is included in the zip. Other demo examples These are small examples I used while I developed and tested the UDF. The examples are stored in "Examples\Demo examples\4) Other demo examples\". Create and fill array\ contains the the same examples as "Tests\Examples\3) Internal conversions\". Multiple parameters\ tests all 30 AccessVariablesXY functions. The functions are tested with simple variables where some variables are added and the result is stored in another variable. Example1.au3. Inside the AccessVariablesXY functions the variables are true variants. This is demonstrated by adding the variables with VarAdd API function. Example2.au3. Safearray dimensions\ shows how to handle safearrays of 1, 2 and 3 dimensions. ArrayToSafearray\ and SafearrayToArray\ is about the AccVars_ArrayToSafeArray and AccVars_SafeArrayToArray utility functions. Using the UDF\ contains the six examples in the previous section. Real examples The examples of fasm code that I've used for tests and demonstrations are almost too small to be realistic examples. But so far I've not finalized any more realistic examples. I'll add some examples when they are completed. Zip file At the top level the zip contains the following folders and files: Examples\ - Examples main section Includes\ - Final UDF main section Tests\ - The first four sections Example.au3 - Example in top of post Example-x64.asm - 64 bit fasm code Example-x86.asm - 32 bit fasm code You need AutoIt 3.3.10 or later. Tested on Windows 10/7 32/64 bit and Windows XP 32 bit. If you have less than 4GB of RAM in your PC, you should reduce the number of array rows by a factor of four (change 2^24, 2^23, 2^20 to 2^22, 2^21, 2^18 in .au3-files) in examples with fasm code. Comments are welcome. Let me know if there are any issues. AccessingVariables.7z Edited October 7, 2017 by LarsJ Bold link in top of post mLipok, Biatu, JohnOne and 14 others 17 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
ptrex Posted December 3, 2016 Share Posted December 3, 2016 Hey Lars Great tutorial makes me dive back into Au3. Rgds ptrex Contributions :Firewall Log Analyzer for XP - Creating COM objects without a need of DLL's - UPnP support in AU3Crystal Reports Viewer - PDFCreator in AutoIT - Duplicate File FinderSQLite3 Database functionality - USB Monitoring - Reading Excel using SQLRun Au3 as a Windows Service - File Monitor - Embedded Flash PlayerDynamic Functions - Control Panel Applets - Digital Signing Code - Excel Grid In AutoIT - Constants for Special Folders in WindowsRead data from Any Windows Edit Control - SOAP and Web Services in AutoIT - Barcode Printing Using PS - AU3 on LightTD WebserverMS LogParser SQL Engine in AutoIT - ImageMagick Image Processing - Converter @ Dec - Hex - Bin -Email Address Encoder - MSI Editor - SNMP - MIB ProtocolFinancial Functions UDF - Set ACL Permissions - Syntax HighLighter for AU3ADOR.RecordSet approach - Real OCR - HTTP Disk - PDF Reader Personal Worldclock - MS Indexing Engine - Printing ControlsGuiListView - Navigation (break the 4000 Limit barrier) - Registration Free COM DLL Distribution - Update - WinRM SMART Analysis - COM Object Browser - Excel PivotTable Object - VLC Media Player - Windows LogOnOff Gui -Extract Data from Outlook to Word & Excel - Analyze Event ID 4226 - DotNet Compiler Wrapper - Powershell_COM - New Link to comment Share on other sites More sharing options...
LarsJ Posted December 4, 2016 Author Share Posted December 4, 2016 Here's a little reading material. Maybe you can skip the Sunday paper. mLipok 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
AndyG Posted December 4, 2016 Share Posted December 4, 2016 Hi! Very interesting article! 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 expandcollapse popup$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 Link to comment Share on other sites More sharing options...
LarsJ Posted December 4, 2016 Author Share Posted December 4, 2016 (edited) 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 December 4, 2016 by LarsJ Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted December 5, 2016 Author Share Posted December 5, 2016 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. Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted December 7, 2016 Author Share Posted December 7, 2016 (edited) 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 March 5, 2017 by LarsJ Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
junkew Posted December 10, 2016 Share Posted December 10, 2016 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 arrayshttps://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 ;-) FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
junkew Posted December 10, 2016 Share Posted December 10, 2016 (edited) can be done also from W8.1 and upwards with jscript9.dll and no need for MS Edge. Edited December 10, 2016 by junkew FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
LarsJ Posted December 14, 2016 Author Share Posted December 14, 2016 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. Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted December 28, 2016 Author Share Posted December 28, 2016 (edited) 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 March 20, 2017 by LarsJ Examples copied to zip in first post Danyfirex 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted December 30, 2016 Author Share Posted December 30, 2016 The post above is updated. Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted December 31, 2016 Author Share Posted December 31, 2016 (edited) Internal conversions Limitations This code is based on a copy of Example5.au3 in "Examples\Demo examples\5) Simple numeric variables\". See post 11. expandcollapse popup;#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 March 20, 2017 by LarsJ Danyfirex 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
junkew Posted January 3, 2017 Share Posted January 3, 2017 (edited) https://msdn.microsoft.com/en-us/library/windows/desktop/ms221620(v=vs.85).aspx Safearrayaccessdata function helpfull? https://www.microsoft.com/msj/0696/activex0696.aspx Not behind my desktop but will try later Edited January 3, 2017 by junkew FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
LarsJ Posted January 6, 2017 Author Share Posted January 6, 2017 (edited) 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: expandcollapse popupExample1() 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: expandcollapse popupFunc 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 March 20, 2017 by LarsJ Examples included in zip in bottom of first post. Danyfirex 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted January 7, 2017 Author Share Posted January 7, 2017 (edited) VariantToRect is working with this code: expandcollapse popupExample3() 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 March 20, 2017 by LarsJ Zip deleted. Included in zip in bottom of first post. Danyfirex 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted January 9, 2017 Author Share Posted January 9, 2017 (edited) To get VariantToRect to work, UIAutomation.au3 UDF should be coded in this way: expandcollapse popup#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: expandcollapse popup;#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 March 5, 2017 by LarsJ Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
junkew Posted January 10, 2017 Share Posted January 10, 2017 @LarsJ: Nice. In which more functional scenario I should use this? So far I had no problem getting x,y,height,width of any UIA capable control. However I will adopt your changes in the CUIAutomation.au3 also FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
LarsJ Posted January 10, 2017 Author Share Posted January 10, 2017 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. Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted January 13, 2017 Author Share Posted January 13, 2017 (edited) 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 March 20, 2017 by LarsJ Zip deleted. Included in zip in bottom of first post. Beege 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now