cas*_*olf 4 outlook vba dictionary outlook-vba
在VBA中创建一个字典,我遇到了一些我很好奇的东西.
当我将Outlook Calendar Item对象添加到字典时ByRef,但当我添加一个灰色的Integer时,它就是ByVal.
我的两个问题是:
ByRef?我看了一下:Objects ByRef的VB字典,但它只讨论了对象的情况而不是整数的情况.
以下代码显示了发生的情况:
Sub checkbyref()
Dim gCal As Items
Dim dict As New Scripting.Dictionary
Set dict = New Scripting.Dictionary
Dim intCheck As Integer
intCheck = 5
Set gCal = GetFolderPath("\\GoogleSync\GoogleSyncCal").Items 'gets caledar items based on path
strMeetingStart = "01/5/2019 12:00 AM"
strGSearch = "[Start] >= '" & strMeetingStart & "'"
gCal.Sort "[Start]"
Set gCal = gCal.Restrict(strGSearch)
Debug.Print intCheck 'prints "5"
Debug.Print gCal(1).Start 'prints 1/7/2019 9:30:00 AM"
dict.Add "check", intCheck
dict.Add "cal", gCal(1)
'direction 1
dict("check") = 4
dict("cal").Start = "1/1/2020 9:00 AM"
Debug.Print intCheck 'prints "5"
Debug.Print gCal(1).Start 'prints "1/1/2020 9:00:00 AM"
'direction 2
intCheck = 6
gCal(1).Start = "1/1/2021 9:00 AM"
Debug.Print dict("check") 'prints "4"
Debug.Print dict("cal").Start 'prints "1/1/2021 9:00:00 AM"
End Sub
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,intCheck不受dict中的更改影响,但gCal(1)是.
tldr; 不,你不能添加内在类型Scripting.Dictionary ByRef.VBA与.NET的管理环境类型不同(使用分代垃圾收集而不是引用计数),因此不可能进行适当的内存管理.请注意,.NET Dictionary也不能以内在类型的方式工作.
对于问题的第二部分,要记住的是DictionaryCOM对象 - 当您在项目中引用它(或调用CreateObject其中一种类型)时,它会启动一个COM服务器以供scrrun.dll提供Scripting对象给调用者.这意味着当您对其中一个成员进行任何调用时,您将通过COM编组器传递所有参数.该Add方法是IDictionary接口的成员,并具有此接口描述:
[id(0x00000001), helpstring("Add a new key and item to the dictionary."), helpcontext(0x00214b3c)]
HRESULT Add(
[in] VARIANT* Key,
[in] VARIANT* Item);
Run Code Online (Sandbox Code Playgroud)
请注意,Key和Item指针都是Variant.传递Integer(或任何其他内在类型)时,运行时首先执行转换为a Variant,然后将结果Variant指针传递给Add方法.此时,Dictionary它全权负责管理副本的内存.VARIANT具有内在类型的A 包含其数据区域中的内在值 - 而不是指向底层内存的指针.这意味着当marshaller投射它时,唯一最终传递的是值.对比这个Object.一个Object包装在一个Variant具有指向其IDispatch在数据区的界面,以及编组具有递增引用计数,当它缠绕它.
将内部类型作为指针传递的固有问题是,当事务超出范围时,COM事务的任何一方都无法知道谁负责释放内存.考虑以下代码:
Dim foo As Scripting.Dictionary 'Module level
Public Sub Bar()
Dim intrinsic As Integer
Set foo = New Scripting.Dictionary
foo.Add "key", intrinsic
End Sub
Run Code Online (Sandbox Code Playgroud)
问题是,intrinsic在执行时会在堆栈上分配内存Bar,但foo在过程退出时不会释放 - intrinsic是.如果运行时intrinsic作为引用传递,那么在foo释放该内存后,您将留下存储在其中的错误指针.如果您稍后尝试在其他过程中使用它,则在重新使用内存或访问冲突时,您将获得垃圾值.
现在比较一下传递Object:
Dim foo As Scripting.Dictionary 'Module level
Public Sub Bar()
Dim obj As SomeClass
Set foo = New Scripting.Dictionary
Set obj = New SomeClass
foo.Add "key", obj
End Sub
Run Code Online (Sandbox Code Playgroud)
VBA中的对象是引用计数,引用计数决定它们的寿命.运行时只会在引用计数为零时释放它们.在这种情况下,当你Set obj = New SomeClass,它将引用计数增加到1.obj(局部变量)包含指向该创建对象的指针.当您调用时foo.Add "key", obj,编组器将对象包装在a中Variant并再次递增引用计数以考虑其指向对象的指针.当程序退出时,obj丢失范围并且引用计数减少,计数为1.运行时知道某些东西有一个指向它的指针,所以它不会拆除对象,因为它有可能会稍后访问.它不会再次递减引用计数,直到foo被销毁并且对象的最后一次引用递减为止.
至于你的第一个问题,在VBA中做这样的事情的唯一方法是提供你自己的对象包装器来"装箱"这个值:
'BoxedInteger.cls
Option Explicit
Private boxed As Integer
Public Property Get Value() As Integer
Value = boxed
End Property
Public Property Let Value(rhs As Integer)
boxed = rhs
End Property
Run Code Online (Sandbox Code Playgroud)
如果你想使用它,你可以成为Value 默认成员.然后你的代码会看起来像这样:
Dim dict As New Scripting.Dictionary
Set dict = New Scripting.Dictionary
Dim check As BoxedInteger
Set check = New BoxedInteger
check.Value = 5
dict.Add "check", check
Run Code Online (Sandbox Code Playgroud)