在 MS-Access VBA 中的 AppDomain 中设置数据后,为什么 Access 拒绝正常关闭?

After setting data in AppDomain within MS-Access VBA why does Access refuse to shutdown properly?

已编辑注释:感谢 Mathieu Guindon 让我知道了 TempVars 集合!在那个和数组的本地表之间,我将能够跳过 appdomain 的使用。

我正在使用附加代码将全局变量保存在 MS-Access 中。但是,在 Access 关闭后,它将无法正常重新打开,因为它从未完全离开任务管理器。

我可以在 VBA 内做些什么来确保它正常关闭吗?我正在使用 MS Access 2010 作为 SQL Server 2012 后端的前端。

我得出的结论是,只有当我添加代码以利用 DefaultAppDomain 来存储脚本字典时才会出现此问题,并希望我可以在域端执行一些清理代码以允许应用程序完全关闭。

Private Function GetDomainDict() As Object

    Const vName = "dict-data"
    Static vDict As Object

    If vDict Is Nothing Then
      Dim vDomain As mscorlib.AppDomain
      Dim vHost As New mscoree.CorRuntimeHost
      'get the vDomain from .net
      vHost.Start
      vHost.GetDefaultDomain vDomain

      'Get the Disctionary for this app from .net or create it
      If IsObject(vDomain.GetData(vName)) Then
        Set vDict = vDomain.GetData(vName)
      Else
        Set vDict = CreateObject("Scripting.Dictionary")
        vDomain.SetData vName, vDict
      End If
    End If

    Set GetDomainDict = vDict
End Function

Public Sub StGV(vVarName As String, vVar As Variant)
    'Set Global Variable Dictionary to the dictionary saved in .net for this Application
    Set pGlobals = GetDomainDict()
    'Check if variable exists and set value either way
    If pGlobals.Exists(vVarName) Then
        pGlobals(vVarName) = vVar
    Else
        pGlobals.Add vVarName, vVar
    End If
End Sub

Public Function GtGv(vVarName As String)
    'Set Global Variable Dictionary to the dictionary saved in .net for this Application
    Set pGlobals = GetDomainDict()
    'Check if variable exists return its value or null if nonexistant
    If pGlobals.Exists(vVarName) Then
        GtGv = pGlobals(vVarName)
    Else
        GtGv = Null
    End If
End Function

评论前

我设计了一个 taskkill 来确保关闭访问。那就是关闭应用程序的原因。我宁愿不依赖那个。

评论后...

如果我不使用 "as new" 变量似乎不会加载并且我得到对象或变量未设置错误,但我确实添加了 set = nothing。 我实施了 .stop 并尝试在退出前端时执行卸载,但出现自动化错误 -2146234347。之后我通过单击数据库 window 关闭按钮强制关闭,但访问仍然没有关闭。 新代码


Dim vDomain As mscorlib.AppDomain
Dim vHost As New mscoree.CorRuntimeHost

'get the vDomain from .net
vHost.Start
vHost.GetDefaultDomain vDomain
'folowing line errors out automation error
vHost.UnloadDomain vDomain
vHost.Stop
Set vDomain = Nothing
Set vHost = Nothing

DoCmd.Quit


End Sub
Private Function GetDomainDict() As Object

    Const vName = "dict-data"
    Static vDict As Object

    If vDict Is Nothing Then
      Dim vDomain As mscorlib.AppDomain
      Dim vHost As New mscoree.CorRuntimeHost
      'get the vDomain from .net
      vHost.Start
      vHost.GetDefaultDomain vDomain
      'Get the Disctionary for this app from .net or create it
      If IsObject(vDomain.GetData(vName)) Then
        Set vDict = vDomain.GetData(vName)
      Else
        Set vDict = CreateObject("Scripting.Dictionary")
        vDomain.SetData vName, vDict
      End If
        vHost.Stop
        Set vDomain = Nothing
        Set vHost = Nothing
   End If

    Set GetDomainDict = vDict
End Function

Public Sub StGV(vVarName As String, vVar As Variant)
    'Set Global Variable Dictionary to the dictionary saved in .net for this Application
    Set pGlobals = GetDomainDict()
    'Check if variable exists and set value either way
    If pGlobals.Exists(vVarName) Then
        pGlobals(vVarName) = vVar
    Else
        pGlobals.Add vVarName, vVar
    End If
End Sub

Public Function GtGv(vVarName As String)
    'Set Global Variable Dictionary to the dictionary saved in .net for this Application
    Set pGlobals = GetDomainDict()
    'Check if variable exists return its value or null if nonexistant
    If pGlobals.Exists(vVarName) Then
        GtGv = pGlobals(vVarName)
    Else
        GtGv = Null
    End If
End Function `


据我所知,这可能是 GetDomainDict 的实现(尽管名称令人困惑):

Public Function GetDomainDict() As Object
    Static dict As Object
    If dict Is Nothing Then Set dict = CreateObject("Scripting.Dictionary")
    Set GetDomainDict = dict
End Function

启动 .NET 应用程序域是完全不必要的步骤 - 特别是考虑到 Scripting.DictionaryMicrosoft Scripting Runtime COM 类型库中定义的数据类型时。 . 首先与 .NET 没有任何关系。

这里不需要涉及任何.NET。即使您想使用 System.Collections.IDictionary,也无需创建 AppDomain 即可使用它。

也就是说...

Dim vHost As New mscoree.CorRuntimeHost

这将创建一个 auto-instantiated 对象,这意味着如果您这样做:

Set vHost = Nothing

对象引用将设置为 Nothing 并且应用 应该 正确关闭。除非你再引用它:

Debug.Print vHost Is Nothing ' will always be False

避免As New尤其是您不拥有的对象。

就是说,ICorRuntimeHost 接口有一个 Stop 方法可能应该在某个时候调用,还有一个 UnloadDomain 方法应该用于卸载 AppDomain 对象- 假设有一个实际的理由来生成并且 Start 它首先出现。

只是为了提供一种替代方法,使其保持纯粹 VBA 并避免对 .NET 的新依赖,您可以考虑使用 Access 2007 中引入的 TempVars 集合。这将在 VBIDE 中继续存在重启。请注意,它只能存储原始数据类型(例如字符串、数字、日期,但不能存储对象)并且它是全局可用的。

您也可以考虑使用本地 Access table 作为数据存储,然后 read/write 改为使用它,这也可以在重置后继续存在,也可以在重新启动后继续存在,这可能是可取的或不可取的,取决于您需要变量的用途。如果您希望它在重启后无法存活,只需清除 table 即可。

我喜欢使用 AppDomain 提供 in-memory 数据存储的想法,它不会在重启后继续存在,但会在重置后但确实感觉 heavy-handed,特别是如果这是唯一的用途.NET CLR 你有。我也有点担心评论:

The reason I use this is that any untrapped error in this causes all global variables to clear in access vba.

这不是正常情况。通常,未捕获的错误会显示 VBIDE 错误消息,您可以在其中调试或结束。需要注意的是,点击End按钮是重置状态,就像点击停止按钮或执行Stop语句一样。对于开发,使用调试按钮并正常退出程序(例如,将黄色箭头移至退出)将避免状态重置。对于用户的体验,他们永远不会看到这样的 "reset",除非在运行时模式下使用 ACCDE 或 运行 Access,这意味着没有 VBIDE 可用于调试,因此唯一有意义的事情很好,结束事情并退出。在那种情况下,这意味着错误 should 首先被捕获。

如果错误处理是一个足够大的问题,您可能需要考虑使用商业 third-party add-in,例如 vbWatchDog,这对改善错误 UX 有很大帮助,尤其是对于ACCDE 或运行时环境。

IOW,我有点担心 AppDomain 的想法正在解决 developer-only 问题。