通过 Excel-DNA 将 Excel.Range 从 VBA 传递到 C#

Passing an Excel.Range from VBA to C# via Excel-DNA

我正在研究 proper/best 如何通过 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

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

所以我想我的问题是那些在这方面有经验的人...这是proper/best解决问题的方法吗?

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

..人们在处理 ExcelReference 对象并且不得不使用令人难以置信的晦涩代码将它们转换为 Excel.Range 对象时,我几乎感觉好像缺少了什么?

是的,这是正确的方法,只要您:

  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 定义)有关,这对于制作高 -性能工作sheet UDF。在此设置中,描述 worksheet 引用的 C API 结构是 ExcelDna.Integration.ExcelReference 类型的包装器。这是一个包含 sheet 指针(称为 SheetId)和引用范围的小型数据结构。 ExcelReference 对象可以在多线程 UDF 中安全使用,并且可以来回传递给 C API。 Excel-DNA worksheet 函数可以配置为在适当的时候接收 ExcelReference 对象作为输入。

Range COM 对象完全不同。它是围绕 sheet 引用的 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