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

情到浓时终转凉″ 提交于 2019-11-26 23:17:26

问题


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 variables are torn down. Indeed, VBA has an option "Notify before state loss" option for triage. It is unsurprising that one cannot Edit and Continue code in all cases. However, sometimes state losses happen whilst running in production because opening some other workbook may cause trauma to your application session (trust me, it happens!)

I know one can persist data to a worksheet cell or even a file but this is inappropriate for trying to retain an instance of a class, especially if that is the anchor for a whole object graph.

So in the case where one insists on a memory held variable how do you persist state over a state loss?


回答1:


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



回答2:


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


来源:https://stackoverflow.com/questions/38952243/in-excel-vba-how-to-persist-key-variables-over-a-state-loss-without-writing-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!