COM Array - Byref Output (a122) Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
CptRootBeard
Posts: 26
Joined: 16 Nov 2020, 14:47
Contact:

COM Array - Byref Output (a122)

Post by CptRootBeard » 17 Nov 2020, 14:56

**Using a122
I've had a similar issue recently with byref outputs and scintilla messages, and I'm sure it's because I don't totally grasp the memory operations taking place.
I'm using COM to interact with AutoCAD, and I've been able to send values through COM to CAD no problem. Output from CAD->AHK has been another story. I need to use the following method to retrieve an entity's XData (VBA signature shown):

Code: Select all

object.GetXData AppName, XDataType, XDataValue
Where AppName is an input string, XDataType is an output array of shorts, and XDataValue is an output array of variants.

I've tried this (among several other variations):
Spoiler

To the following results:
Spoiler

I know this specific case isn't easily testable, as it requires AutoCAD. I'm hoping my mistake will be glaringly obvious to someone with a little more COM knowledge than myself.
---------------------------------------------------------
Link to the AutoCAD API:
http help.autodesk.com /view/OARX/2019/ENU/?guid=GUID-330FCA31-A2A9-47F0-972D-6915B6B98426

Pages with examples that I've tried to adapt to my use:
https lexikos.github.io /v2/docs/commands/ComObject.htm#ExByRef
https lexikos.github.io /v2/docs/commands/ComObjArray.htm

The VT Enum Class I'm using:
Spoiler
CptRootBeard
Posts: 26
Joined: 16 Nov 2020, 14:47
Contact:

Re: COM Array - Byref Output (a122)

Post by CptRootBeard » 20 Nov 2020, 09:21

More generally, here's where I could use guidance:

I need a safearray of a given type of data (e.g. VT_VARIANT or VT_I2).
This has to be passed to a COM object's method byref to be used as an output -> I need a pointer (in a COM acceptable form) to the safearray.
I've seen the examples that call BufferAlloc() to create a buffer of appropriate size, then wrap the buffer.ptr in a ComObject(VT_BYREF|etc.) call before passing it to the method.
My data type or buffer size is probably incorrect, so I'm not having any luck.

In short:
1. How much memory do I buffer for an array? Perhaps (# of entries)*(bytes per entry)?
2. Would COMObject(VT_BYREF|VT_ARRAY|VT_VARIANT, buff.ptr) denote a reference to an array of variants? If not, what is the appropriate type here?

Any help is much appreciated.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: COM Array - Byref Output (a122)

Post by lexikos » 30 Jan 2021, 06:49

I assume "Type: Variant (array of shorts)" and "Access: Output-only" mean that you should pass a reference to a Variant, and that Variant will be assigned an array upon return.

The ComObject.htm#ExByRef shows how to allocate a Variant. You can use the ComVar class as is, as shown in the example. I would leave the default value (which is VT_EMPTY by virtue of zero-initialization); i.e. do not assign a value, as in var[] := "in value". Retrieve the array only once, with something like arr := var[], as I think you would get a separate ComObjArray object each time you reference var[].

You do not "buffer memory for an array" unless you are creating the array entirely from scratch yourself. When you use ComObjArray, the backing memory for the array is allocated by COM (via the SafeArrayCreate function). I don't think you should be creating the array in this case anyway.
CptRootBeard
Posts: 26
Joined: 16 Nov 2020, 14:47
Contact:

Re: COM Array - Byref Output (a122)  Topic is solved

Post by CptRootBeard » 21 Apr 2021, 13:43

Thanks for your help, and sorry for the bump/delayed response. I figured I would update this in case someone needs it later.

I tried a few different approaches in a122, including your suggestions, but couldn't get it working. However, I recently updated to a130 and have been able to do a lot using the reference operator (&).
The only caveat I've found so far is that "optional" arguments from the AutoCAD API can be left blank but are, in fact, required. So expect your calls to look like "acObj.Method(x, y,,,,)" or similar.

The following script works in a130:

Code: Select all

#SingleInstance Force

;;Get a reference to the active CAD instance using the appropriate version
ACAD_VERSION := 23
ACAD := ComObjActive("AutoCAD.Application." . ACAD_VERSION)

;;For use with <AcadSelectionSet>.Select()
class acSelectionSet {
	static Window[] => 0
	static Crossing[] => 1
	static Previous[] => 3
	static Last[] => 4
	static All[] => 5
}

;; Delete the selection set named 'ALL' if it exists
;; If it doesn't exist, fail quietly (dangerous game, I know) and continue
try {
	ACAD.ActiveDocument.SelectionSets("ALL").Delete()
}

;;Now create the set named 'ALL'
allEnts := ACAD.ActiveDocument.SelectionSets.Add("ALL")

;;Select all mtext on layer 0 in model space
;;VBA Int type is VT_I2 (which evaluates to 2)
dxfType := ComObjArray(VT_I2:=2,3)
dxfType[0] := 410
dxfType[1] := 0
dxfType[2] := 8

;;Strings will need a variant -> VT_VARIANT evaluates to 12
dxfValue := ComObjArray(VT_VARIANT:=12,3)
dxfValue[0]:= "Model"
dxfValue[1]:= "MTEXT"
dxfValue[2]:= "0"

allEnts.Select(acSelectionSet.All,,,dxfType,dxfValue)

;;Loop through the selected ents and set (then get) the xdata
for e in allEnts {
	dataType := ComObjArray(VT_I2, 4)
	dataValue := ComObjArray(VT_VARIANT, 4)
	
	;;This bracket structure mirrors the way the 'xdata' express tool command attaches xdata from within autocad
	dataType[0] := 1001
	dataType[1] := 1002
	dataType[2] := 1000
	dataType[3] := 1002
	
	dataValue[0] := "TEST_APP"
	dataValue[1] := "{"
	dataValue[2] := "Hello world."
	dataValue[3] := "}"
	
	;;Set the XDATA for a TEST_APP application
	e.setXData(dataType, dataValue)
	
	;;Predeclare our output variables.
	;;Not necessary, just personal preference.
	retDataType := ""
	retDataValue := ""
	
	;;Get the XDATA and msgbox it
	e.getXData("TEST_APP", &retDataType, &retDataValue)
	msgbox(retDataValue[2], "XData says...")
	
	;;If you want to delete the data associated with TEST_APP, call setXData with only the app name
	;;Set this flag to TRUE to see it in action. Use the XDLIST command to check the results.
	flag_deleteXData := FALSE
	if(flag_deleteXData){
		dataType := ComObjArray(VT_I2, 1)
		dataType[0] := 1001
		dataValue := ComObjArray(VT_VARIANT, 1)
		dataValue[0] := "TEST_APP"
		e.setXData(dataType, dataValue)
	}
}
Figured I would put this out in the wild, as I've seen very little AutoCAD COM with anything other than VBA or VL.
Thanks again for the help, lexikos. V2 is looking better every day.
Bubo_Bubo
Posts: 8
Joined: 03 Oct 2014, 01:47

COM Array - Byref in Autocad

Post by Bubo_Bubo » 08 Oct 2023, 11:46

Here's how to draw things by coordinates in autocad, use GetBoundingBox and SelectionSets. Parts from this answer ported to v2 with VBA equivalents:

Code: Select all

#Requires AutoHotkey v2.0

cad := ComObjActive("AutoCAD.Application")

; AddText
point := Point3d(789.72, 1500.45)
textObj := cad.ActiveDocument.ModelSpace.AddText("AutoHotkey", point, 80.77)
textObj.Update

Point3d(d1, d2, d3:=0) {
    ; VBA: Dim point(0 To 2) As Double
	pt := ComObjArray(5, 3) ; VT_R8 = 5 see https://www.autohotkey.com/docs/v2/lib/ComObjType.htm#vt
	pt[0] := d1
	pt[1] := d2
	pt[2] := d3
	return pt
}

; AddLine
point1 := Point3d(489.72, 1913.45)
point2 := Point3d(1069.86, 2180.80)
lineObj := cad.ActiveDocument.ModelSpace.AddLine(point1, point2)
lineObj.Update

; GetBoundingBox
        ; VBA: 
        ; Dim p1 As Variant
        ; Dim p2 As Variant
        ; obj.GetBoundingBox p1, p2
p1 := ComValue(0x400C, (vbuf1:=Buffer(24, 0)).ptr) ; 0x400C = VT_BYREF|VT_VARIANT
p2 := ComValue(0x400C, (vbuf2:=Buffer(24, 0)).ptr) ; https://www.autohotkey.com/docs/v2/lib/ComValue.htm#ByRef
lineObj.GetBoundingBox(p1, p2)
Msgbox "(" p1[0] ", " p1[1] ", " p1[2] ")`n"
    .  "(" p2[0] ", " p2[1] ", " p2[2] ")"



; Select all lines using DXF group code

; https://www.autohotkey.com/boards/viewtopic.php?t=45906#p207458

    ; VBA:
    ;Dim fType(0) As Integer, fData(0), ss As AcadSelectionSet
    ;Set ss = ActiveDocument.SelectionSets.Add("my_lines")
    ;filterType(0) = 0
    ;filterData(0) = "Line"
    ;ss.Select acSelectionSetAll, , , filterType, filterData
    
; create proper COM objects to use as parameters
filterType := ComObjArray(2, 1)	; array of VT_I2 = 2  (16-bit signed int)
filterData := ComObjArray(12, 1) ; array of VT_VARIANT = 0xC  (VARIANT data type)
filterType[0] := 0
filterData[0] := "Line"

try ; delete My_Lines selection set if it exists
	cad.ActiveDocument.SelectionSets("my_lines").Delete
ss := cad.ActiveDocument.SelectionSets.Add("my_lines")
ss.Select acSelectionSetAll:=5, , , filterType, filterData ; populate selectionset
ss.Highlight(true) ; highlight visually on screen 
Msgbox "Number of lines: " ss.Count
About SelectionSet: viewtopic.php?t=45906#p207458
Post Reply

Return to “Ask for Help (v2)”