diff --git a/changelog.md b/changelog.md index b985af8..648d9df 100644 --- a/changelog.md +++ b/changelog.md @@ -484,3 +484,5 @@ The 2025-2026 entries were audited retrospectively against commits through `1bcb - 2026-11-01 `Tools/Testing` DEV - Add one manifest-driven command for aggregate, class and named-case tests, structured results, isolated Office workers, dialog/time limits and exact settings restoration. Move specialized test assets under root tests and register Python/native checks. Existing commands inside mixed build-tool folders remain compatibility shims. Split Callback and Regex into independently callable case groups; explicitly report unsupported hosts and legacy fixtures as skipped. - 2026-11-01 `stdLambda` BREAKING - Remove the production test-only protRunVMTests member and its private test helpers. Developers calling stdLambda.protRunVMTests must instead run `python Tools/Testing/run.py --suite stdLambda.vm --test Instructions`; the runner injects tests from tests/support into a disposable class copy. Normal Lambda evaluation APIs, defaults and callback contracts are unchanged. - 2026-11-01 `Tools/Testing` BREAKING - Remove the separate Tools/stdCallback-tests, stdDate-test, stdHTTP-tests, stdImage-tests stdWebSocket-tests and stdEnumerator-tests command folders. Maintainer scripts invoking those paths must migrate to `python Tools/Testing/run.py --suite `: stdCallback.probes, stdDate, stdHTTP.local, stdImage stdWebSocket.local or stdEnumerator.local respectively. Image host selection is now `--host Excel` or `--host Word` through the shared command (formerly `--hosts`). Test bodies and specialized harnesses now live under tests/harnesses. + +- 2026-11-01 `stdArray` WIP - Add experimental Windows CreateFromArrayByRef ownership transfer for non-empty dynamic one-dimensional Variant arrays. Consumes the source and uses DefaultBase; rejects fixed-size, locked, unallocated and multidimensional arrays. Existing copying APIs are unchanged; no breaking changes identified. Windows 32-bit/legacy VBA and Mac runtime support remain unverified (#122). diff --git a/docs.json b/docs.json index 426e03a..c23548e 100644 --- a/docs.json +++ b/docs.json @@ -1147,6 +1147,35 @@ ], "requires": [], "methods": [ + { + "name": "protInitFromArrayByRef", + "description": "Take ownership of array storage without copying elements.", + "remarks": [], + "examples": [], + "params": [ + { + "name": "arr", + "type": "Variant", + "description": "Non-empty dynamic one-dimensional Variant array; consumed on success.", + "optional": false, + "defaultValue": null, + "paramArray": false, + "referenceType": "ByRef" + } + ], + "returns": null, + "deprecation": { + "status": false, + "message": "" + }, + "isDefaultMember": false, + "devNotes": [], + "todos": [], + "isProtected": true, + "throws": [], + "requires": [], + "isStatic": false + }, { "name": "protInit", "description": "Initialise array", @@ -2540,6 +2569,46 @@ "requires": [], "isStatic": false }, + { + "name": "CreateFromArrayByRef", + "description": "Create a stdArray by taking ownership of a dynamic one-dimensional Variant array.", + "remarks": [ + "Experimental Windows API. Transfers ownership, rather than sharing storage. Existing copying APIs are unchanged." + ], + "examples": [ + "```vb\r\nDim values() As Variant: ReDim values(0 To 2)\r\nvalues(0) = \"hello\"\r\nDim list As stdArray: Set list = stdArray.CreateFromArrayByRef(values)\r\n'values is now unallocated; list.Item(list.Base) is \"hello\".\r\n```" + ], + "params": [ + { + "name": "arr", + "type": "Variant", + "description": "Non-empty dynamic Variant array. Becomes unallocated on success.", + "optional": false, + "defaultValue": null, + "paramArray": false, + "referenceType": "ByRef" + } + ], + "returns": { + "type": "stdArray", + "description": "A stdArray using the transferred storage and current DefaultBase." + }, + "deprecation": { + "status": false, + "message": "" + }, + "isDefaultMember": false, + "devNotes": [], + "todos": [], + "isProtected": false, + "throws": [ + { + "errNumber": null + } + ], + "requires": [], + "isStatic": false + }, { "name": "CreateFromCollection", "description": "Create a `stdArray` object from a collection", diff --git a/src/stdArray.cls b/src/stdArray.cls index 217508a..a5e21cd 100644 --- a/src/stdArray.cls +++ b/src/stdArray.cls @@ -207,6 +207,91 @@ Public Function CreateFromArray(ByVal arr As Variant) As stdArray RaiseEvent AfterCreate(Create) End Function +'Create a stdArray by taking ownership of a dynamic one-dimensional Variant array. +'@constructor +'@param arr - Non-empty dynamic Variant array. Becomes unallocated on success. +'@returns - A stdArray using the transferred storage and current DefaultBase. +'@remark Experimental Windows API. Transfers ownership, rather than sharing storage. Existing copying APIs are unchanged. +'@throws stdArray::CreateFromArrayByRef - Error 5 for fixed-size, locked, multidimensional, empty or unallocated arrays; source remains unchanged. +'@example ```vb +'Dim values() As Variant: ReDim values(0 To 2) +'values(0) = "hello" +'Dim list As stdArray: Set list = stdArray.CreateFromArrayByRef(values) +''values is now unallocated; list.Item(list.Base) is "hello". +'``` +Public Function CreateFromArrayByRef(ByRef arr As Variant) As stdArray + Set CreateFromArrayByRef = New stdArray + Call CreateFromArrayByRef.protInitFromArrayByRef(arr) + RaiseEvent AfterCreate(CreateFromArrayByRef) +End Function + +'Take ownership of array storage without copying elements. +'@protected +'@param arr - Non-empty dynamic one-dimensional Variant array; consumed on success. +Public Sub protInitFromArrayByRef(ByRef arr As Variant) +#If Mac Then + Err.Raise 5, "stdArray::CreateFromArrayByRef", "Windows required" +#Else + If This.Initialised Then Err.Raise 5, "stdArray::CreateFromArrayByRef", "Already initialised" + #If VBA7 Then + Dim sourceSlot As LongPtr, targetSlot As LongPtr, descriptor As LongPtr, nullPointer As LongPtr + #Else + Dim sourceSlot As Long, targetSlot As Long, descriptor As Long, nullPointer As Long + #End If + sourceSlot = ArrayPointerSlot(arr) + Call CopyMemory(VarPtr(descriptor), sourceSlot, LenB(descriptor)) + If descriptor = 0 Then Err.Raise 5, "stdArray::CreateFromArrayByRef", "Allocated array required" + Dim dimensions As Integer, flags As Integer, locks As Long + Call CopyMemory(VarPtr(dimensions), descriptor, 2) + Call CopyMemory(VarPtr(flags), descriptor + 2, 2) + Call CopyMemory(VarPtr(locks), descriptor + 8, 4) + If dimensions <> 1 Or (flags And &H17) <> 0 Or locks <> 0 Then + Err.Raise 5, "stdArray::CreateFromArrayByRef", "Dynamic unlocked one-dimensional array required" + End If + Dim count As Long, firstIndex As Long, boundsOffset As Long + #If Win64 Then + boundsOffset = 24 + #Else + boundsOffset = 16 + #End If + Call CopyMemory(VarPtr(count), descriptor + boundsOffset, 4) + If count <= 0 Then Err.Raise 5, "stdArray::CreateFromArrayByRef", "Non-empty array required" + This.Base = stdArray.DefaultBase + This.Length = count + This.ProxyLength = count + This.Chunking = 10 + targetSlot = ArrayPointerSlot(This.BaseArray) + firstIndex = 1 + 'Validation is complete; no callbacks run while ownership changes. + Call CopyMemory(descriptor + boundsOffset + 4, VarPtr(firstIndex), 4) + Call CopyMemory(sourceSlot, VarPtr(nullPointer), LenB(nullPointer)) + Call CopyMemory(targetSlot, VarPtr(descriptor), LenB(descriptor)) + This.Initialised = True +#End If +End Sub + +'Locate the SAFEARRAY pointer slot of a typed array passed through a Variant. +#If VBA7 Then +Private Function ArrayPointerSlot(ByRef value As Variant) As LongPtr +#Else +Private Function ArrayPointerSlot(ByRef value As Variant) As Long +#End If +#If Mac Then + Err.Raise 5, "stdArray::ArrayPointerSlot", "Windows required" +#Else + Dim vt As Integer + Call CopyMemory(VarPtr(vt), VarPtr(value), 2) + If vt = (vbArray Or vbVariant) Then + ArrayPointerSlot = VarPtr(value) + 8 + Exit Function + End If + If vt <> (vbArray Or vbVariant Or &H4000) Then + Err.Raise 5, "stdArray::ArrayPointerSlot", "Typed Variant array required" + End If + Call CopyMemory(VarPtr(ArrayPointerSlot), VarPtr(value) + 8, LenB(ArrayPointerSlot)) +#End If +End Function + 'Create a `stdArray` object from a collection '@constructor '@param col - Collection to create a `stdArray` object from. diff --git a/tests/stdArrayTests.bas b/tests/stdArrayTests.bas index 31b4e71..505e902 100644 --- a/tests/stdArrayTests.bas +++ b/tests/stdArrayTests.bas @@ -53,6 +53,7 @@ Sub testAll() Test.Assert "Property Arr 4 item 3 equal", vArr(3) = arr.item(3) end if + Call testCreateFromArrayByRef Call testDefaultBase Call testArrLet @@ -354,3 +355,72 @@ Failed: stdArray.DefaultBase = originalBase Err.Raise failure, "stdArrayTests.testDefaultBase", description End Sub + +Public Sub testCreateFromArrayByRef() + Test.Topic "stdArray ownership transfer" + Dim values() As Variant: ReDim values(-2 To 0) + values(-2) = "hello" + Dim obj As Collection: Set obj = New Collection + obj.Add "alive" + Set values(-1) = obj + values(0) = Array(3, 4) + Dim list As stdArray: Set list = stdArray.CreateFromArrayByRef(values) + Test.Assert "Moved length", list.Length = 3 + Test.Assert "Moved string", list.Item(list.Base) = "hello" + Test.Assert "Moved object identity", list.Item(list.Base + 1) Is obj + Dim nested As Variant: nested = list.Item(list.Base + 2) + Test.Assert "Moved nested array", nested(1) = 4 + Dim n As Long, failure As Long + On Error Resume Next + n = LBound(values) + failure = Err.Number + Err.Clear + On Error GoTo 0 + Test.Assert "Source unallocated", failure = 9 + ReDim values(0 To 1) + values(0) = "independent" + Test.Assert "Source reusable", list.Item(list.Base) = "hello" + list.Push 5 + Test.Assert "Moved array grows", list.Length = 4 + Test.Assert "Moved array clones", list.Clone.Length = 4 + Set list = Nothing + Test.Assert "Object survives cleanup", obj.Item(1) = "alive" + Dim fixed(0 To 2) As Variant + fixed(0) = "retained" + On Error Resume Next + Set list = stdArray.CreateFromArrayByRef(fixed) + failure = Err.Number + Err.Clear + On Error GoTo 0 + Test.Assert "Reject fixed", failure = 5 + Test.Assert "Rejected source unchanged", fixed(0) = "retained" + Dim matrix() As Variant: ReDim matrix(0 To 1, 0 To 1) + On Error Resume Next + Set list = stdArray.CreateFromArrayByRef(matrix) + failure = Err.Number + Err.Clear + On Error GoTo 0 + Test.Assert "Reject multidimensional", failure = 5 + Dim unallocated() As Variant + On Error Resume Next + Set list = stdArray.CreateFromArrayByRef(unallocated) + failure = Err.Number + Err.Clear + On Error GoTo 0 + Test.Assert "Reject unallocated", failure = 5 + Dim v As Variant: v = Array(8, 9) + Set list = stdArray.CreateFromArrayByRef(v) + Test.Assert "Variant containing array", list.Join = "8,9" + v = Array(10) + Test.Assert "Variant source reusable", list.Join = "8,9" + Dim locked() As Variant: ReDim locked(0 To 1) + Dim element As Variant + For Each element In locked + On Error Resume Next + Set list = stdArray.CreateFromArrayByRef(locked) + failure = Err.Number + Err.Clear + On Error GoTo 0 + Test.Assert "Reject locked array", failure = 5 + Next +End Sub