代码之家  ›  专栏  ›  技术社区  ›  Eric

Excel是否从两个不同的AppDomain调用.NET自动化服务器?

  •  6
  • Eric  · 技术社区  · 14 年前

    我有一个Excel插件(用C_编写),其中静态变量位于单例数据缓存的核心:

    static DataCache _instance;
    

    这可以通过三种不同的代码路径访问:

    1. vsto功能区栏上的事件处理程序初始化实例,并读取该实例以在帮助程序对话框中显示
    2. RTD服务器(声明为[ComVisible]并实现IRTDServer接口的类)将数据用于RTD公式。
    3. 一组自动化调用(在声明为[ComVisible]的另一个类中实现)也对数据进行操作。这些都是通过单击Excel工作表上的按钮时调用的VBA代码来调用的。

    编辑(第3):

    根据这些代码路径首次调用的顺序,我发现我的代码在两个单独的AppDomain中运行。

    所有来自功能区栏事件处理程序的访问都发生在名为“myplugin.vsto”的AppDomain中。如果这是对我的COM对象的第一次访问,那么所有后续调用(包括RTD调用)都将发生在同一个AppDomain中。

    但是,如果第一次访问是通过RTD接口进行的,那么该调用和所有随后的RTD调用都发生在名为“DefaultDomain”的AppDomain中。(使用嵌入的RTD公式加载保存的文档时会发生这种情况。)通过工具栏初始化和操作数据缓存的后续调用仍在“myplugin.vsto”appdomain中发生。这意味着RTD公式始终像未初始化数据缓存一样运行(因为一个AppDomain中设置的静态变量在另一个AppDomain中保持未初始化状态)。

    当vsto初始化时,Excel或vsto似乎正在创建appdomain。在此初始化之前通过COM互操作创建的对象将在默认AppDomain中着陆,而随后创建的对象将在VSTO AppDomain中着陆。

    无论RTD服务器对象是在哪个AppDomain中创建的,我如何确保使用相同的数据缓存实例?

    2 回复  |  直到 14 年前
        1
  •  1
  •   Govert    14 年前

    您的静态变量肯定不会在AppDomain之间共享,因此考虑到不同的AppDomain,您看到的是预期的。

    我想它是这样工作的:

    vsto外接程序在其自己的appdomain中运行。如果缓存对象(或RTD服务器)的COM类工厂是从该AppDomain中创建的,则它将加载到调用的AppDomain中。对该COM类的后续访问将发现它已加载到进程中,并使用现有实例。

    但是,如果第一次激活是由Excel本身触发的,例如由RTD调用触发的,则.NET实现的COM对象将加载到进程的默认AppDomain中。除非生成非托管填充程序,否则无法控制加载过程的这一部分,因为在加载发生时“your code”不会运行。

    我的一些建议:

    1. 为从.NET外接程序中公开的RTD调用创建一些包装函数。这样,您可以确保在调用Excel的application.rtd进行真正的RTD设置之前加载RTD类。

    2. 通过用户定义的函数从RTD服务器访问实际缓存-这样,Excel将调用具有实际缓存的AppDomain,即使它不是RTD服务器所在的当前AppDomain。

    3. 尝试通过application.addins获取外接程序对象…有一种方法可以获取实际的外接程序COM对象,并在其上使用一些接口来访问缓存…

    4. 为您的RTD服务器创建一个非托管填充程序(在Web上搜索“COM填充程序向导”)。不知何故,找出如何加载VSTO AppDomain,然后将RTD服务器加载到该AppDomain中。

    5. 查看是否有方法将vsto外接程序加载到默认的appdomain中。我不知道,但可能有一个标志或开关告诉“Microsoft Office Systems Loader”(或现在调用的任何部分)不要创建独立的AppDomain。

    6. 正确答案:使用 Excel-Dna (免责声明:我是开发者)。它支持受管外接程序中的功能区、RTD和UDF,不需要注册,所有内容都会放入外接程序AppDomain中。它是免费的,但是移植你的东西需要花费一些时间和精力-RTD是微不足道的,但是如果你使用很多VSTO辅助对象来访问功能区和工作表(表等),你需要考虑一下。

    我希望这能给你一些建议。

    --政府

        2
  •  -2
  •   mhttk    14 年前

    首先,您可能希望将实例声明为:

    static DataCache _instance = new DataCache();
    

    通过这种方式(不是唯一一个确定的方法),您知道\u实例是线程安全生成的。关于线程安全单例的主题有很多报道,但这似乎是最简单的解决方案之一。

    第二件事可能是你想尝试使用的结构是

    Lock (_lockObject)
    {
    ...
    }
    

    读和写。这将使您的读写免受不同线程的影响。

    最后,但这纯粹是猜测,您可以尝试为驻留在STA中的COM调用创建一个单独的对象,并访问您的库。

    祝你好运!