在 Excel VBA 中如何在 'state loss' 上保留关键变量(不写入单元格或文件)?

In Excel VBA how to persist key variables over a 'state loss' (without writing to a cell or a file)?

Excel VBA 是一个灵活的开发环境。它是伪编译的。但是,有时在开发过程中可能会出现 "state loss"。 A "state loss" 是所有变量都被拆除的时候。事实上,VBA 有一个 "Notify before state loss" 选项用于分类。不能在所有情况下编辑并继续代码也就不足为奇了。 但是,有时在生产 运行 时会发生状态丢失,因为打开其他一些工作簿可能会对您的应用程序会话造成创伤(相信我,它会发生!)

我知道可以将数据保存到工作表单元格甚至文件中,但这不适合尝试保留 class 的实例,尤其是当它是整个对象图的锚点时。

那么在坚持使用内存变量的情况下,如何在状态丢失时保持状态?

正在尝试回答我自己的问题...

解决方案是有一个简单的容器,我选择 Scripting.Dictionary,编译成 DLL 并使 VBA 使用 COM 可以访问。在过去,人们可以使用 VB6。

如今,人们也可以使用 C++,但在这里我提供了一个 C# 解决方案(使用 COM 互操作)。

using System.Runtime.InteropServices;

namespace VBAStateLossProofStorageLib
{
    // Code curated by S Meaden from Microsoft documentation

    // 1. C# Shared Class library
    // 2. In AssemblyInfo.cs set ComVisible(true)
    // 3. In Project Properties->Build check 'Register for Interop'
    // 4. Add Com reference to Microsoft Scripting Runtime

    public interface IVBAStateLossProofStorage
    {
        Scripting.Dictionary getGlobalDictionary();
    }

    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IVBAStateLossProofStorage))]
    public class VBAStateLossProofStorage : IVBAStateLossProofStorage
    {
        public Scripting.Dictionary getGlobalDictionary()
        { return CVBAStateLossProofStorage.m_dictionary; }
    }


    // https://msdn.microsoft.com/en-gb/library/79b3xss3.aspx
    // "a static class remains in memory for the lifetime of the application domain in which your program resides. "
    [ComVisible(false)]
    static class CVBAStateLossProofStorage
    {
        public static Scripting.Dictionary m_dictionary;

        static CVBAStateLossProofStorage()
        {
            m_dictionary = new Scripting.Dictionary();
        }
    }
}

这里是一些用于演示的客户端 VBA 代码。需要工具 -> 对与 Dll 一起创建的类型库(.tlb 文件)的引用。

Option Explicit

Public gdicLossy As New Scripting.Dictionary
Public gdicPermanent As Scripting.Dictionary

Sub RunFirst()

    Set gdicLossy = New Scripting.Dictionary
    gdicLossy.add "Greeting", "Hello world!"

    Dim o As VBAStateLossProofStorageLib.VBAStateLossProofStorage
    Set o = New VBAStateLossProofStorageLib.VBAStateLossProofStorage

    Set gdicPermanent = o.getGlobalDictionary
    gdicPermanent.RemoveAll '* clears it down
    gdicPermanent.add "Greeting", "Bonjour!"

    End '* THIS PROVOKES A STATE LOSS - ALL VARIABLES ARE TORN DOWN - EVENT HANDLERS DISAPPEAR
End Sub

Sub RunSecond()

    Debug.Assert gdicLossy.Count = 0  '* sadly we have lost "Hello world!" forever

    Dim o As VBAStateLossProofStorageLib.VBAStateLossProofStorage
    Set o = New VBAStateLossProofStorageLib.VBAStateLossProofStorage

    Set gdicPermanent = o.getGlobalDictionary
    Debug.Assert gdicPermanent.Count = 1 '* Happily we have retained "Bonjour!" as it was safe in its compiled Dll
    Debug.Assert gdicPermanent.Item("Greeting") = "Bonjour!"

End Sub

在 Excel 的生命周期内保持数据持久的一种方法是将它们存储在附加到实例的默认 .Net 域中:

Sub Usage()
    Dim dict As Object
    Set dict = GetPersistentDictionary()
End Sub
Public Function GetPersistentDictionary() As Object
    ' References:
    '  mscorlib.dll
    '  Common Language Runtime Execution Engine

    Const name = "weak-data"
    Static dict As Object

    If dict Is Nothing Then
      Dim host As New mscoree.CorRuntimeHost
      Dim domain As mscorlib.AppDomain
      host.Start
      host.GetDefaultDomain domain

      If IsObject(domain.GetData(name)) Then
        Set dict = domain.GetData(name)
      Else
        Set dict = CreateObject("Scripting.Dictionary")
        domain.SetData name, dict
      End If
    End If

    Set GetPersistentDictionary = dict
End Function