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

后端 未结 2 620
长情又很酷
长情又很酷 2020-12-06 08:17

Excel VBA is a flexible development environment. It is pesudo-compiled. However, sometimes during development a \"state loss\" can occur. A \"state loss\" is when all var

相关标签:
2条回答
  • 2020-12-06 08:48

    Attempting answer of my own question ...

    The solution is to have a simple container, I choose Scripting.Dictionary, compiled into a DLL and make accessible to VBA using COM. In the old days one could have used VB6.

    These days, one can also use C++ but here I present a C# solution (uses COM interop).

    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();
            }
        }
    }
    

    And here is some client VBA code to demonstrate. Requires a Tools->Reference to the type library (.tlb file) created alongside the Dll.

    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
    
    0 讨论(0)
  • 2020-12-06 08:51

    One way to keep the data persistent during the lifetime of Excel is to store them on the default .Net domain attached to the instance:

    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
    
    0 讨论(0)
提交回复
热议问题