Variant class needed?

Get help with using AutoHotkey and its commands and hotkeys
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Variant class needed?

03 Apr 2015, 19:14

I have been looking at IUIAutomation code written by Jethro and Nepter a few years back, and noticed custom functions in those files for handling COM object variants. I am considering creating a variant class that contains methods like Create(), GetValue(), GetType(), but can't believe this hasn't been done already by someone. So I suspect there's a good reason for that.

Do the AHK "ComObj*" functions handle working with variants? I have read the help, but don't understand those functions very well. Basically, I need to prepare a variant for use with DllCall(), and then extract its return value afterwards.

Regards,
Dan
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Variant class needed?

03 Apr 2015, 23:05

The minimum you need is:

Code: Select all

VarSetCapacity(variant, 24, 0)

;DllCall(..., "ptr", &variant)

MsgBox % ComObject(0x400C, &variant)[]
If you need to initialize the variant with a non-empty value...

Code: Select all

VarSetCapacity(variant, 24, 0)
variant_ref := ComObject(0x400C, &variant)
variant_ref[] := "Non-empty value"

;DllCall(..., "ptr", &variant)

MsgBox % variant_ref[]
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed?

25 Apr 2015, 18:06

Thanks Lexikos, your example is working great. However, is any memory clean up required for the data that is assigned to the variant? (i.e. Should I use the Win32 VariantClear() function after the variant is no longer needed, and pass it &variant_ref[] ?)
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Variant class needed?

25 Apr 2015, 18:46

&variant_ref[] will not work. &variant returns the address of the structure.

You can just do variant_ref[] := 0. Numeric values don't need freeing (however, floating-point literals in v1 are stored as strings).
User avatar
jethrow
Posts: 188
Joined: 30 Sep 2013, 19:52
Location: Iowa

Re: Variant class needed?

26 Apr 2015, 02:24

UppyDan wrote:I have been looking at IUIAutomation code written by Jethro and Nepter a few years back ...
Note that a lack of understanding on my part around variants & memory was one reason I put this project on hold - and at this point it's unlikely I'll finish the library.
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed?

26 Apr 2015, 23:35

jethrow wrote:
UppyDan wrote:I have been looking at IUIAutomation code written by Jethro and Nepter a few years back ...
Note that a lack of understanding on my part around variants & memory was one reason I put this project on hold - and at this point it's unlikely I'll finish the library.
You're not alone understanding variants. But you did a good job on UIA_Interface.ahk and I've learned a lot from it.

BTW - If you get a chance to update that file, I think the implementation of UIA_CreatePropertyCondition() and UIA_CreatePropertyConditionEx() leak memory when VT_BSTR (i.e. 8) is passed in for the type. UIA_Variant() gets called, but nothing ever calls UIA_VariantClear() to clear the memory for the string. Should UIA_VariantClear() be called after DllCall() and before returning the UIA_PropertyCondition object?

Something like this maybe ...

Code: Select all

CreatePropertyCondition(propertyId, ByRef var, type="Variant") {
	if (type!="Variant")
		UIA_Variant(var,type,var)
	hresult := DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId, "ptr",&var, "ptr*",out)
	if (type!="Variant")  ; <-------
		UIA_VariantClear(&var)  ; <-------
	return UIA_Hr(hresult)? new UIA_PropertyCondition(out):
}
But I'm terrible with this stuff, so I could be wrong about the leak. I was actually trying to figure out why creating property conditions threw an exception when compiled as a 32-bit EXE. But I only succeeded in finding other implementations that also throw the exception.
Elgin
Posts: 118
Joined: 30 Sep 2013, 09:19

Re: Variant class needed?

28 Apr 2015, 08:58

I can't claim to really understand variants either but I did find out a good while back that CreatePropertyCondition does not work with a pointer to a variant in 32 bit but needs the variant structure itself in the call. This implementation of CreatePropertyCondition works for me under 32 bit:

Code: Select all

	CreatePropertyCondition(propertyId, ByRef var, type="Variant") 
	{
		If (A_PtrSize=8)
		{
			if (type!="Variant")
			UIA_Variant(var,type,var)
			return UIA_Hr(DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId, "ptr",&var, "ptr*",out))? new UIA_PropertyCondition(out):
		}
		else
		{
			if (type<>8)
				return UIA_Hr(DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId, "int64",type, "int64", var, "ptr*",out))? new UIA_PropertyCondition(out):
			else
			{			
				vart:=DllCall("oleaut32\SysAllocString", "wstr",var,"ptr")
				return UIA_Hr(DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId, "int64",type, "ptr", vart, "ptr", 0, "ptr*",out))? new UIA_PropertyCondition(out):
			}		
		}
	}
Hope it helps...

Is there any way in AHK to pass a variant more neatly? Is there any way to easily check for memory leaks?
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed?

28 Apr 2015, 17:49

Thanks Elgin! It seems to work for me as well. Very interesting, however. Not only did you change the variant's parameter type, but the DllCall() for 32-bit scripts actually has more parameters. Did you have to dig through the Microsoft SDK header files to figure that out?
Elgin
Posts: 118
Joined: 30 Sep 2013, 09:19

Re: Variant class needed?

29 Apr 2015, 02:40

I'm not really changing anything but merely creating a variant data structure directly inside the dll-call instead of passing it as a pointer as in the 64bit call. The extra parameters and the int64s are needed to get the total size of the whole up to 16 bit and everything in the right place.

I'm not a big fan of the header files. They have too many back references to other files and lack information about the padding. I prefer to look at the type libraries, MSDN, and ultimately try stuff out in Visual C++ and analyze the resulting data from there with SizeOf and OffsetOf if all else fails.
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed? (Fix for CreatePropertyCondition()

29 Apr 2015, 13:52

In case it helps someone else, I'm posting my implementations of the UIA_Interace CreatePropertyCondition() and CreatePropertyConditionEx() methods. They incorporate Elgin's solution for the exception caused in 32-bit builds. (I took a guess on the CreatePropertyConditionEx() code, but it seems to be working. Elgin, I would appreciate a quick code review on it!)

I also changed the logic around to prevent suspected memory leaks from the original version of UIA_Interface.ahk which, BTW, can be downloaded from https://github.com/jethrow/UIA_Interface. I tested these methods on 64-bit Windows 7, for 64-bit and 32-bit compiled EXE versions of an AHK script.

Code: Select all

    CreatePropertyCondition(propertyId, ByRef var, type := "Variant") 
    {
        ; CREDITS: Elgin, http://ahkscript.org/boards/viewtopic.php?f=5&t=6979&p=43985#p43985
        ; Parameters:
        ;   propertyId  - An ID number of the property to check.
        ;   var         - The value to check for.  Can be a variant, number, or string.
        ;   type        - The data type.  Can be the string "Variant", or one of standard 
        ;                 variant type such as VT_I4, VT_BSTR, etc.
        local out:="", hr, bstr
        If (A_PtrSize = 8)
        {
            if (type!="Variant")
                UIA_Variant(var,type,var)
            hr := DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId, "ptr",&var, "ptr*",out)
            if (type!="Variant")
                UIA_VariantClear(&var)
            return UIA_Hr(hr)? new UIA_PropertyCondition(out):        
        }
        else ; 32-bit.
        {
            if (type <> 8)
                return UIA_Hr(DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId
                            , "int64",type, "int64",var, "ptr*",out))? new UIA_PropertyCondition(out):
            else ; It's type is VT_BSTR.
            {           
                bstr := DllCall("oleaut32\SysAllocString", "wstr",var, "ptr")
                hr := DllCall(this.__Vt(23), "ptr",this.__Value, "int",propertyId
                            , "int64",type, "ptr",bstr, "ptr",0, "ptr*",out)
                DllCall("oleaut32\SysFreeString", "ptr", bstr)
                return UIA_Hr(hr)? new UIA_PropertyCondition(out):
            }
        }
    }

    CreatePropertyConditionEx(propertyId, ByRef var, type := "Variant", flags := 0x1) 
    {
        ; PropertyConditionFlags_IgnoreCase = 0x1
        local out:="", hr, bstr
        
        If (A_PtrSize = 8)
        {
            if (type!="Variant")
                UIA_Variant(var,type,var)
            hr := DllCall(this.__vt(24), "ptr",this.__Value, "int",propertyId
                        , "ptr",&var, "uint",flags, "ptr*",out)
            if (type!="Variant")
                UIA_VariantClear(&var)
            return UIA_Hr(hr)? new UIA_PropertyCondition(out):
        }
        else ; 32-bit.
        {
            if (type <> 8)
                return UIA_Hr(DllCall(this.__vt(24), "ptr",this.__Value, "int",propertyId
                            , "int64",type, "int64",var
                            , "uint",flags, "ptr*",out))? new UIA_PropertyCondition(out):
            else ; It's type is VT_BSTR.
            {
                bstr := DllCall("oleaut32\SysAllocString", "wstr",var, "ptr")
                hr := DllCall(this.__vt(24), "ptr",this.__Value, "int",propertyId
                            , "int64",type, "ptr",bstr, "ptr",0, "uint",flags, "ptr*",out)
                DllCall("oleaut32\SysFreeString", "ptr", bstr)
                return UIA_Hr(hr)? new UIA_PropertyCondition(out):
            }
        }
    }
Last edited by UppyDan on 02 May 2015, 22:16, edited 2 times in total.
Elgin
Posts: 118
Joined: 30 Sep 2013, 09:19

Re: Variant class needed?

29 Apr 2015, 14:19

Have the kindness to fix the typo in my nick, please ;-) Otherwise it looks fine to me (which isn't saying much about the code, though; maybe Lexikos could take a more experienced look).

Btw. I've stumbled across a thread which explains the 32/64 bit issue here quite well:
http://www.autohotkey.com/board/topic/7 ... -win7-x64/

In essence in 64bit every parameter with a size >8 byte has to be passed as a reference whereas in 32 bit it can or must be passed directly. That cleared a lot of things up for me...
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed?

29 Apr 2015, 16:40

Elgin wrote:Have the kindness to fix the typo in my nick, please ;-)
Oops. Good catch. Sorry about that, my fingers sometimes decide what to do all on their own. :D
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Variant class needed?

29 Apr 2015, 18:51

It seems that on 64-bit, type can be left at its default value of "Variant" if var is a pointer to a variant, but on 32-bit, that would not work since var is passed as the second half of the variant. I think that floating-point values won't work either, since var is passed as int64.

If you use the code I posted earlier, the caller can just pass a value of any type supported by AutoHotkey, or a ComObject(vartype, value) if they need to specify the type. You would use "int64", NumGet(variant, 0, "int64"), "int64", NumGet(variant, 8, "int64") to pass the variant by value on 32-bit.

It is an error to write x ? y :, but in the current version of AutoHotkey, if x is false, the expression is silently aborted, having the same effect as returning "".
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed?

04 May 2015, 19:47

I finally had time to experiment with this again and incorporated Lexikos's suggestion into a new version of the CreatePropertyCondition() functions. However, VT_BOOL types unexpectedly caused an exception when the returned condition variable was used by IUIAutomationElement::FindFirst(). The problem went away after I wrapped VT_BOOLs by calling ComObject().

As before, this implementation works in 64-bit and 32-bit AHK executables, and requires UIA_Interface.ahk - available at https://github.com/jethrow/UIA_Interface .

Code: Select all

    ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ; Method:       _setVariant
    ; Description:  A helper method used internally by this class.  It stores a
    ;               variable type and value into a variant.  Any memory allocated
    ;               to the variant by this method is automatically freed when the
    ;               variant variable goes out of scope.
    ; Parameters:
    ;   variant     - The variant to store the 'varType' and 'val' parameters into.
    ;   varType     - The variable type, indicating the type of data held by the 'val'
    ;                 parameter.  It can be one of the standard variant types such as 
    ;                 VT_I4, VT_BSTR, or VT_BOOL.
    ;   val         - The value to store in the 'variant' parameter.
    ;
    ; Returns: Nothing.  The variant parameter is updated as described above.
    ;
    _setVariant(ByRef variant, varType, val)
    {
        static VT_BYREF := 0x4000, VT_VARIANT := 0xC, VT_BOOL := 0xB
            , SIZEOF_VARIANT := 8 + (2 * A_PtrSize)
        local variant_ref

        ; Other IUIAutomation functions only work if VT_BOOL types are wrapped by
        ; calling ComObject().  Also, make sure the value for true is -1 (VARIANT_TRUE).
        if (varType = VT_BOOL)
            val := ComObject(VT_BOOL, !val ? 0 : -1)
  
        ; Update the 'variant' parameter.
        VarSetCapacity(variant, SIZEOF_VARIANT, 0)
        variant_ref := ComObject(VT_BYREF|VT_VARIANT, &variant)
        variant_ref[] := val
    }
    
    ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ; Method:       CreatePropertyCondition
    ; Description:  Overloads the base class's implemention to fully support 32-bit builds.
    ; Credits:      Elgin, http://ahkscript.org/boards/viewtopic.php?f=5&t=6979&p=43985#p43985
    ; Parameters:
    ;   propertyId  - An ID number of the property to check.
    ;   val         - The value to compare the property too.  Can be a variant, number, or string.
    ;   varType     - The variable type.  Can be the string "Variant", or one of standard 
    ;                 variant types such as VT_I4, VT_BSTR, or VT_BOOL.
    ;
    ; Returns: An IUIAutomationCondition based on the supplied parameters.
    ;
    CreatePropertyCondition(propertyId, ByRef val, varType := "Variant")
    {
        local variant := "", out := ""
        
        this._setVariant(variant, varType, val)
        if (A_PtrSize >= 8) ; 64-bit or more.
            return UIA_Hr(DllCall(this.__vt(23), "ptr",this.__Value, "int",propertyId
                            , "ptr",&variant
                            , "ptr*",out)) ? new UIA_PropertyCondition(out) : ""
        else ; 32-bit.
            return UIA_Hr(DllCall(this.__vt(23), "ptr",this.__Value, "int",propertyId
                            , "int64", NumGet(variant, 0, "int64"), "int64", NumGet(variant, 8, "int64")
                            , "ptr*",out)) ? new UIA_PropertyCondition(out) : ""
    }
    
    ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ; Method:       CreatePropertyConditionEx
    ; Description:  Overloads the base class's implemention to fully support 32-bit builds.
    ; Parameters:
    ;   propertyId  - An ID number of the property to check.
    ;   val         - The value to compare the property too.  Can be a variant, number, or string.
    ;   varType     - The variable type.  Can be the string "Variant", or one of standard 
    ;                 variant types such as VT_I4, VT_BSTR, or VT_BOOL.
    ;   flags       - 0 = case sensitive comparisons.  
    ;                 1 = ignore case (PropertyConditionFlags_IgnoreCase).
    ;
    ; Returns: An IUIAutomationCondition based on the supplied parameters.
    ;
    CreatePropertyConditionEx(propertyId, ByRef val, varType := "Variant", flags := 0x1)
    {
        local variant := "", out := ""
        
        this._setVariant(variant, varType, val)
        if (A_PtrSize >= 8) ; 64-bit or more.
            return UIA_Hr(DllCall(this.__vt(24), "ptr",this.__Value, "int",propertyId
                            , "ptr",&variant
                            , "uint",flags, "ptr*",out)) ? new UIA_PropertyCondition(out) : ""
        else ; 32-bit.
            return UIA_Hr(DllCall(this.__vt(24), "ptr",this.__Value, "int",propertyId
                            , "int64", NumGet(variant, 0, "int64"), "int64", NumGet(variant, 8, "int64")
                            , "uint",flags, "ptr*",out)) ? new UIA_PropertyCondition(out) : ""
    }
    
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Variant class needed?

04 May 2015, 20:19

If you were not wrapping it with ComObject() or directly building the VARIANT, then I suppose you would just be assigning an integer, not actually using the VT_BOOL type code. variant_ref[] := true would be the same as variant_ref[] := 1. I see that you don't use the varType parameter if it is anything other than VT_BOOL.
SIZEOF_VARIANT := 8 + (2 * A_PtrSize)
The thing about calculating the struct size is that the code and additional variable take up memory (and the code takes negligible time to execute). That's why I just write 24, although I suppose "SIZEOF_VARIANT" is more expressive.
UppyDan
Posts: 17
Joined: 03 May 2014, 15:23

Re: Variant class needed?

05 May 2015, 15:05

Good point on the VT_BOOL. That's probably why FindAll() was unhappy. I decided to keep varType in the parameter list even before using it for VT_BOOL, just so the signature matched the original method written by Jethro. That way folks could drop in the new function and not break any of their existing code.

The SIZEOF_VARIANT thing comes from my programming roots. When I worked for Kodak, we had coding convention that required using constants for any value other than 0 or 1. Additionally, computing the value in this case provides compatibility down the road if operating systems go to 128 bits. However, I'm not familiar with AHK's memory limitations (if any). I had assumed integers and typical strings would be trivial to declare with the size of memory in computers today. Please let me know if this can cause problems.

My habit of extensive comments is another thing I learned while at Kodak. In AHK, I have already seen that comment lines throw off line numbers when an exception specifies the line causing an error (at least for include files). I have been wondering if too many comments can cause other issues.

Thanks again for the feedback Lexikos. I always enjoy learning better ways to do something.
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Variant class needed?

05 May 2015, 20:13

UppyDan wrote:That way folks could drop in the new function and not break any of their existing code.
Unless they specify a varType, expecting it to actually be used...
Additionally, computing the value in this case provides compatibility down the road if operating systems go to 128 bits.
There's no telling how the struct might change on some other platform which doesn't exist yet, or even whether that platform will support COM/ActiveX.
I had assumed integers and typical strings would be trivial to declare with the size of memory in computers today.
Yes, but despite this some users obsess over optimizations which are actually ineffective, like "saving" 8 bytes on a VARIANT struct by calculating the size. (I see your reasons for doing it are different, though.)
In AHK, I have already seen that comment lines throw off line numbers when an exception specifies the line causing an error (at least for include files).
No they don't...
I have been wondering if too many comments can cause other issues.
Not at all. The script is pre-parsed or "semi-compiled" when it first starts up, before it executes, and comments are stripped out at this early stage. However, line numbers are based on the count of "physical" lines read from the file, not the post-processed form of the code.
User avatar
jethrow
Posts: 188
Joined: 30 Sep 2013, 19:52
Location: Iowa

Re: Variant class needed?

25 May 2015, 03:03

I wrote:... lack of understanding on my part around variants & memory ...
I read through handle VARIANT structures - shouldn't ComVar be all that's needed for working with variants? Passing the variant and getting the value is just the following - & the object delete function should clean everything up?

Code: Select all

Variant = ComVar()

; pass ByRef:
ComObjValue(Variant.ref)

; get Value:
Variant[]
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Variant class needed?

25 May 2015, 08:16

Yes, but using VarSetCapacity and VT_BYREF directly is faster. Just as fast as using ComObjArray directly, but easier to understand; though with ComObjArray, you get automatic clean-up.

That said, ComVar() can be made faster. If you get/set the var a few times, most of the time is still spent creating and destroying it. Actually, most of the time is spent calling SafeArrayAccessData/SafeArrayUnaccessData. In retrospect, I think it's probably useless - I know of two reasons to use SafeArrayAccessData:
  1. You want to prevent other code from freeing or resizing the array (by locking it).

    According to MSDN, locking an array also "places a pointer to the array data in pvData", but I think in reality the pointer is always there. I've read that it's a holdover from 16-bit Windows.
  2. You want to treat a SafeArray as an opaque data structure.

    I don't think there's really any benefit to that; it's not as though the structure will ever change.
This version is shorter and faster:

Code: Select all

; ComVar: Creates an object which can be used to pass a value ByRef.
;   ComVar[] retrieves the value.
;   ComVar[] := Val sets the value.
;   ComVar.ref retrieves a ByRef object for passing to a COM function.
ComVar(Type:=0xC)
{
    static base := { __Get: "ComVarGet", __Set: "ComVarSet" }
    ; Create an array of 1 VARIANT.  This method allows built-in code to take
    ; care of all conversions between VARIANT and AutoHotkey internal types.
    arr := ComObjArray(Type, 1)
    ; Retrieve a pointer to the VARIANT.
    arr_data := NumGet(ComObjValue(arr)+8+A_PtrSize)
    ; Store the array and an object which can be used to pass the VARIANT ByRef.
    return { ref: ComObject(0x4000|Type, arr_data), _: arr, base: base }
}

ComVarGet(cv, p*) { ; Called when script accesses an unknown field.
    if p.MaxIndex() = "" ; No name/parameters, i.e. cv[]
        return cv._[0]
}

ComVarSet(cv, v, p*) { ; Called when script sets an unknown field.
    if p.MaxIndex() = "" ; No name/parameters, i.e. cv[]:=v
        return cv._[0] := v
}
I've explored various ways of making it even faster. One such way is to eliminate the p.MaxIndex() checks by relying on the fact that meta-functions are ignored in v1 if they require too many parameters:

Code: Select all

ComVar(Type:=0xC) {
    static base := { __Get: "ComVarDontGet", __Set: "ComVarDontSet", base: { __Get: "ComVarGet", __Set: "ComVarSet" } }
    ;<omitted for brevity>
}
ComVarDontGet(cv, p) {
    return
}
ComVarDontSet(cv, p, v) {
    return
}
ComVarGet(cv) {
    return cv._[0]
}
ComVarSet(cv, v) {
    return cv._[0] := v
}
Unfortunately, this won't work on v2. Of course, you could just omit the MaxIndex checks from the original code and assume no-one will use anything other than cv[] or cv.ref (and expect a sane result). In that case, cv.value, cv.fubar and cv[1,2,3,4,5] are all the same as cv[], but cv.value := x wouldn't work without this minor change:

Code: Select all

ComVarSet(cv, p*) {
    return cv._[0] := p.Pop()
}
With this change instead of the "Dont" functions, it's even faster - but less strict.

If you don't care about retaining the ComVar() syntax, you can make it faster by taking advantage of properties:

Code: Select all

; ComVar2: Creates an object which can be used to pass a value ByRef.
;   ComVar.val retrieves the value.
;   ComVar.val := new_val sets the value.
;   ComVar.ref retrieves a ByRef object for passing to a COM function.
ComVar2(Type:=0xC)
{
    ; Create an array of 1 VARIANT.  This method allows built-in code to take
    ; care of all conversions between VARIANT and AutoHotkey internal types.
    arr := ComObjArray(Type, 1)
    ; Retrieve a pointer to the VARIANT.
    arr_data := NumGet(ComObjValue(arr)+8+A_PtrSize)
    ; Store the array and an object which can be used to pass the VARIANT ByRef.
    return { ref: ComObject(0x4000|Type, arr_data), _: arr, base: ComVariant_ }
}

class ComVariant_
{
    val {
        get {
            return this._[0]
        }
        set {
            return this._[0] := value
        }
    }
}
Here's another version, manually constructing the VARIANT instead of using a SafeArray:

Code: Select all

; ComVar: Creates an object which can be used to pass a value ByRef.
;   ComVar.val retrieves the value.
;   ComVar.val := new_val sets the value.
;   ComVar.ref retrieves a ByRef object for passing to a COM function.
ComVar(Type:=0xC)
{
    return new ComVariant_(Type)
}

class ComVariant_
{
    __new(Type:=0xC) {
        ObjSetCapacity(this, 1, 24)
        this.ref := ComObject(0x4000|Type, NumPut(0, ObjGetAddress(this, 1), "short") - 2)
    }
    val {
        get {
            return this.ref[]
        }
        set {
            return this.ref[] := value
        }
    }
    __delete() {
        this.ref[] := 0
    }
}
This is slightly faster to access, but slower to create/destroy, even using the following tricks (which do help):
  • Use the key 1 instead of a string like _buffer to avoid having to allocate a copy of the key.
  • Initialize by writing VT_EMPTY to the vt field, not by calling RtlZeroMemory (my first idea) or VariantInit (my second idea).
  • Avoid a temporary variable for the address by utilizing the return value of NumPut.
  • Initialize in __New rather than in ComVar() directly. Somehow this affected benchmarks considerably; probably a sign that what I'm benchmarking is too fast for the benchmarks to be reliable. Well, it is only 3µs...
If you're actually concerned (I mean, obsessed) with performance, using the VT_BYREF object directly is faster:

Code: Select all

value := cv.ref[]
cv.ref[] := "new value"
But don't do something like ref := ComVar().ref, as the variant would be immediately deleted.
ComObjValue(Variant.ref)
You would use Variant.ref for ByRef parameters of dispatch methods and ComObjValue(Variant.ref) when you need a pointer to a VARIANT (not exactly what I'd call "ByRef").
User avatar
jethrow
Posts: 188
Joined: 30 Sep 2013, 19:52
Location: Iowa

Re: Variant class needed?

30 May 2015, 20:23

Thanks for the info/examples ... that's pretty much everything I needed :)
lexikos wrote:Here's another version, manually constructing the VARIANT instead of using a SafeArray ... This is slightly faster to access, but slower to create/destroy ...
I hadn't even considered reusing the save variant structure (internally) until you mentioned this. I think this example is the most useful for me, though I'd prolly add it to my script & bypass the function call ... and add a type property:

Code: Select all

; new ComVariant: Creates an object which can be used to pass a value ByRef.
;   ComVar.val retrieves the value.
;   ComVar.val := new_val sets the value.
;   ComVar.ref retrieves a ByRef object for passing to a COM function.
class ComVariant {
	;~ http://ahkscript.org/boards/viewtopic.php?p=46265#p46265
	__new(Type:=0xC) {
		ObjSetCapacity(this, 1, 24)
		this.ref := ComObject(0x4000|Type, NumPut(0, ObjGetAddress(this, 1), "short") - 2)
	}
	val {
		get {
			return this.ref[]
		}
		set {
			return this.ref[] := value
		}
	}
	type {
		get {
			return NumGet(ComObjValue(this.ref), 0, "ushort")
		}
	}
	__delete() {
		this.ref[] := 0
	}
}

Return to “Ask For Help”

Who is online

Users browsing this forum: Bing [Bot], Google [Bot], hasantr, Xeo786 and 34 guests