我可以将整数项目添加到VBA词典byRef

时间:2019-02-09 16:28:43

标签: vba dictionary outlook outlook-vba

在VBA中创建字典,发现了一些我觉得很奇怪的东西。

当我将Outlook Calendar Item对象添加到字典中时,它是ByRef,但是当我添加灰色的整数时,它是ByVal

我的两个问题是:

  1. 是否可以添加变暗的整数ByRef
  2. 为什么这两个项目的添加方式有所不同(我知道一个是对象,一个是基本类型,需要更多细节)?

我看了看:VB dictionary of Objects ByRef,但它只讨论对象大小写而不是整数大小写。

这是显示发生情况的代码:

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

您可以看到intCheck不受dict更改的影响,而gCal(1)受此影响。

2 个答案:

答案 0 :(得分:5)

tldr; 不,您不能向Scripting.Dictionary ByRef添加固有类型。 VBA与.NET的托管环境类型不同(.NET使用世代垃圾收集而不是引用计数),因此无法进行适当的内存管理。请注意,.NET Dictionary不适用于固有类型。


对于问题的第二部分,要记住的是Dictionary是一个COM对象-当您在项目中引用它(或以其中一种类型调用CreateObject时) ,它将启动COM服务器,以供scrrun.dll向调用者提供Scripting对象。这意味着,当您对其成员之一进行 any 调用时,您将通过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);

请注意,KeyItem都是指向Variant的指针。当您传递Integer(或任何其他内部类型)时,运行时首先将强制转换为Variant,然后将结果Variant指针传递给{{1} } 方法。此时,Add专门负责管理副本的内存。具有内在类型的VARIANT在其数据区域中包含内在的 value -而不是指向基础内存的指针。这意味着当编组器对其进行转换时,最终唯一通过的是值。将此与Dictionary进行对比。封装在Object中的Object在数据区域中具有指向其Variant接口的指针,并且封送程序在包装时必须增加引用计数。


将内在类型作为指针传递时的内在问题是,COM事务的任何一方都无法知道谁在超出范围时负责释放内存。考虑以下代码:

IDispatch

问题在于,在执行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 时,已为intrinsic分配了堆栈上的内存,但是在过程退出时Bar未被释放-foo被释放。如果运行时通过了intrinsic作为参考,则在释放该内存后,您将剩下一个错误的指针存储在intrinsic中。如果稍后尝试在其他过程中使用它,则如果重新使用内存,则会得到一个垃圾值,或者访问冲突。


现在将其与传递foo进行比较:

Object

VBA中的对象被引用计数,引用计数确定它们的寿命。仅当引用计数为零时,运行时才会释放它们。在这种情况下,当您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 时,它会将引用计数增加到1。 Set obj = New SomeClass(局部变量)保存指向该创建对象的指针。当您调用obj时,编组器将对象包装在foo.Add "key", obj中,并再次增加引用计数以说明其指向该对象的指针。当过程退出时,Variant失去作用域,并且引用计数递减,保留计数为1。运行时知道某个对象有指向它的指针,因此它不会删除该对象,因为存在有可能稍后再访问。直到obj被销毁并且对该对象的最后一个引用减少之后,它才会再次减少引用计数。


关于您的第一个问题,在VBA中执行此类操作的唯一方法是提供您自己的对象包装器以“装箱”值:

foo

如果您想了解它,可以制作'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 the default member。然后您的代码将看起来像这样:

Value

答案 1 :(得分:3)

Dictionary的Add方法仅将键和项对添加到Dictionary对象。对于dict.Add "check", intCheck,此处的项目是整数值。这意味着没有添加引用,只是整数值。如果要回溯原始变量,则需要按照Comintern的建议将其包装在一个类中,或者必须同时更新字典和原始整数变量。示例:

Sub checkbyref()
    Dim dict As New Scripting.Dictionary
    Set dict = New Scripting.Dictionary
    Dim intCheck As Integer

    intCheck = 5
    Debug.Print intCheck 'prints "5"
    dict.Add "check", intCheck

    ' dict("check") = 4
    SetDictionaryWithInteger dict, "check", 4, intCheck
    Debug.Print dict("check") & "|" & intCheck

    ' intCheck = 6
    SetDictionaryWithInteger dict, "check", 6, intCheck
    Debug.Print dict("check") & "|" & intCheck

End Sub

Private Sub SetDictionaryWithInteger( _
    dic As Scripting.Dictionary, _
    key As String, _
    newVal As Integer, _
    ByRef source As Integer)
    dic(key) = newVal
    source = newVal
End Sub

注意:ByRef是Visual Basic中的default,此处仅用于强调intCheck的整数值将被修改。

并回答您的问题: 1.不,像值一样添加整数 2.它们的添加方式没有不同,它们的添加方式相同,这意味着其值已添加。但是在intCheck的情况下,它是值本身(例如5),仅此而已;在gCal的情况下,该值是数据结构的地址,因此仍可以通过该地址来访问