【问题标题】:Passing an Excel.Range from VBA to C# via Excel-DNA通过 Excel-DNA 将 Excel.Range 从 VBA 传递到 C#
【发布时间】:2015-09-11 08:51:58
【问题描述】:

我正在研究如何正确/最佳地通过 Excel-DNA 将 Excel.Range 对象从 Excel 中的 VBA 传递到 C#,然后通过传递的引用与 Excel 对象模型进行交互。

示例代码:

    '***************** VBA code ********************
    Sub test2()
        Dim rng As Excel.Range
        Set rng = Worksheets("test_output").Range("D9")

        Dim myDLL As New XLServer.MyClass
        Call myDLL.test_ExcelRangePassedAsObject(rng)
        Call myDLL.test_ExcelRangePassedAsRange(rng)
    End Sub



//**************** C# Code **********************
using System;
using ExcelDna.Integration;
using ExcelDna.ComInterop;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop;
using Excel = Microsoft.Office.Interop.Excel;

namespace XLServer
{
    [ComVisible(false)]
    class ExcelAddin : IExcelAddIn
    {
        public void AutoOpen()
        {
            ComServer.DllRegisterServer();
        }
        public void AutoClose()
        {
            ComServer.DllUnregisterServer();
        }
    }    

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class MyClass                            
    {
        public void test_ExcelRangePassedAsObject(object range)
        {
            Excel.Range rng = (Excel.Range)range;
            rng.Worksheet.Cells[1, 1].Value = "Specified range was: " + rng.Address.ToString();
        }

        public void test_ExcelRangePassedAsRange(Excel.Range range)
        {
            Excel.Range rng = range;
            rng.Worksheet.Cells[2, 1].Value = "Specified range was: " + rng.Address.ToString();
        }

当我运行它时,我得到了预期的输出:
A1:指定范围为:$D$9
A2:指定范围为:$D$9

在花费大量时间在 Google 上搜索 Excel-DNA 问题但一无所获后,我刚刚写了这篇文章,实际上很惊讶它没有出现任何问题。

所以我想我的问题是那些在这方面有经验的人......这是正确/最好的方法吗?

例如,我遇到过很多类似这样的帖子:
https://groups.google.com/forum/#!topic/exceldna/zqzEIos7ma0

....人们在哪里处理 ExcelReference 对象并且不得不使用非常晦涩的代码将它们转换为 Excel.Range 对象,感觉就像我缺少了什么?

【问题讨论】:

  • 这就是我来 Stack Overflow 而不是 Google Groups 的原因...
  • 这就是我不明白的。公平地说,这个帖子最初是在 2011 年开始的,但是 2014 年 10 月 14 日发布的问题仍然在为此苦苦挣扎,而且他来自 来自 c#,所以为什么他不能只获得 Range直接参考我不懂。也许他的插件是基于 Excel-DNA 而不是 VSTO?这甚至有意义吗?我只是在学习这个所以不知道。
  • 实际上,Govert 也不知道那家伙在说什么,因为我重读了它....lol

标签: c# vba vsto excel-dna


【解决方案1】:

是的,这是正确的做法,只要你:

  1. 确保您的库项目标记为“注册 COM 互操作”。您不想构建直接将 .dll 注册为XLServer.MyClass COM 类型的 COM 服务器的过程,而是让 .xll 作为 COM 服务器。这将确保您的 COM 对象与插件的其余部分位于相同的 AppDomain 中。

  2. ComServer 属性添加到.dna 文件中的<ExternalLibrary> 标记中,因此为

    <ExternalLibrary Path="XlServer.dll" ComServer="true" ... />.

  3. 注意所有 COM 接口版本控制规则。

关于该主题的最佳参考资料是 Mikael Katajamäki 的演练文章,我想你已经找到了:


现在介绍一些背景知识,为 Google 小组讨论提供一些背景信息:

大多数 Excel-DNA 内部结构都与 Excel C API(由 Excel SDK 定义)有关,该 API 非常适合制作高性能工作表 UDF。在此设置中,描述工作表引用的 C API 结构是 ExcelDna.Integration.ExcelReference 类型的包装器。这是一个包含工作表指针(称为 SheetId)和引用范围的小型数据结构。 ExcelReference 对象可以在多线程 UDF 中安全使用,并且可以来回传递给 C API。 Excel-DNA 工作表函数可以配置为在适当的时候接收ExcelReference 对象作为输入。

Range COM 对象是完全不同的东西。它是围绕此类工作表引用的 COM 对象包装器,受所有 COM 线程、生命周期和其他限制。 Microsoft 不支持在 .xll 中定义的 UDF 函数中使用 COM 对象模型(以及 Range 对象),虽然它大部分都有效,但尝试在多线程函数中使用这些可能会导致严重的问题。

在您放入 Excel-DNA 插件的宏或功能​​区回调(作为由 ExcelAsyncUtil.QueueAsMacro 启动的异步触发宏)内,您可以安全地使用 C API 和 COM 对象模型。

从一个瘦 ExcelReference 对象转换为 COM Range 对象非常困难,但这取决于您是否要支持多区域范围。如果没有,就这么简单:

static Range ReferenceToRange(ExcelReference xlref)
{
    string refText = (string)XlCall.Excel(XlCall.xlfReftext, xlref, true);
    dynamic app = ExcelDnaUtil.Application;
    return app.Range[refText];
} 

此帮助器不属于 ExcelReference 类型的唯一原因是 Excel-DNA 仍然支持 .NET 2.0,并且有意义的 COM 类型统一仅在 .NET 4.0 之后才可用

【讨论】:

  • 感谢您提供令人难以置信的全面答案,这正是我想知道的。我非常想了解更多关于您提到的特定 COM 考虑事项的信息,您可能有任何方便的链接将不胜感激。 (“您不想构建进程直接将您的 .dll 注册为 XLServer.MyClass COM 类型的 COM 服务器,而是让 .xll 作为 COM 服务器。这将确保您的 COM 对象存在于同一个AppDomain 作为插件的其余部分。")
  • 假设您想要在工作表 UDF 和 VBA 使用的 COM 对象之间共享某种缓存。如果 UDF 部分和 COM 对象在同一个 AppDomain 中,那么共享会容易得多。为自己注册 COM 访问的 .dll 将导致 .NET 运行时在默认 AppDomain 中激活它,而 Excel-DNA 加载项在其自己的 AppDomain 中运行。另外 - Excel-DNA 注册不需要管理员权限。
  • 是否有理由在功能区回调中使用 ExcelAynscUtil.QueueAsMacro?我想同时使用 COM 和 C API,并且在 QueueAsMacro 中包装对我的函数的调用允许我使用 XlCall.Excel( XlCall.xlfGetCell, 53, reference )。如果我不使用它,我就无法找到成功调用该函数的方法。我的 Ribbon/AddIn 挂钩到应用程序事件,并且需要在某些处理程序中保持一些状态,因此将我的所有回调设为静态以便 Application.Run() 可以在它们上使用变得太复杂了。
  • 另外,当 QueueAsMacro 委托抛出错误时,谁会捕获该异常?它似乎不是我的功能区回调,也不是我的 ExcelIntegration.RegisterUnhandledExceptionHandler() 方法。
猜你喜欢
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-09
  • 2013-12-07
  • 1970-01-01
相关资源
最近更新 更多