在 Windows 上,使用 C# 编写的 COM 服务器,可以为早期绑定和后期绑定代码返回 SAFEARRAY 吗?

2024-04-04

问题很长,所以我将用要点格式化以便于讨论

介绍

  1. 我正在编写一个 C# COM 服务器。
  2. COM 服务器可在 Excel VBA 中以早期绑定和后期绑定模式使用。
  3. 我的绊脚石是如何返回在早期和后期绑定模式下都可以工作的实例化类的 SAFEARRAY;我收到错误。
  4. I have done plenty of work on this (all day):
    • 我已经完成了一些诊断并设置了调试器以阐明我收到的错误。
    • 我已经做了一些相当详尽的谷歌搜索。
    • 我发现了一些不太令人满意的解决方法。
    • 我现在真的很困惑,正在寻找 COM 互操作专家来帮助我找到一个好的解决方案。

设置项目类型和项目属性

  1. 创建一个新的 C# 类库项目。
  2. 我将我的文件命名为 LateBoundSafeArraysProblem,并将源文件重命名为 LateBoundSafeArraysProblem.cs。
  3. 在 AssemblyInfo.cs 中将第 20 行修改为 ComVisible(true) ,因此可见性是通用的(仍然需要 public 关键字)。
  4. Set the Project Properties:
    • 设置构建选项,在“项目属性”->“构建”->“输出”中,选中“注册 COM 互操作”复选框。
    • Set the debug options to launch Excel and load an excel workbook client:
      • 在“项目属性”->“调试”->“启动操作”中,选择单选按钮“启动外部问题”,然后输入 Microsoft Excel 的路径,对我来说是“C:\Program Files\Microsoft Office 15\root\office15\excel.exe”。
      • 在“项目属性”->“调试”->“启动选项”中,输入启用客户端 Excel 宏的工作簿的名称,对于我来说,该名称为 C:\Temp\LateBoundSafeArraysProblemClient.xlsm。 †

创建 COM 服务器源代码

  1. Style choices and decisions
    • I'm being a good COM citizen and dividing the interface definitions from the class definitions.
      • 我在类上使用 [ClassInterface(ClassInterfaceType.None)] 和 [ComDefaultInterface(typeof())] 属性来实现这种清晰的划分。
    • 由于客户端是 Excel VBA,我们需要坚持自动化兼容类型,因此 SAFEARRAY
  2. The two C# classes/ com classes:
    • Apples 是一个简单的状态容器,用于将数据编组回客户端,除了 getter 和 setter 之外不携带任何方法。
    • FruitCounter 是一个工作类,它有一个 enumerateApples() 方法,该方法返回 Apple 实例的 SAFEARRAY。

所以苹果接口和类的源代码是:

public interface IApples 
{
    string variety { get; set; }
    int quantity { get; set; }
}

[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IApples))]
public class Apples : IApples
{
    public string variety { get; set; }
    public int quantity { get; set; }
}

上面的代码没有争议并且工作正常。

FruitContainer 接口和类的源代码是

public interface IFruitCounter
{
    Apples[] enumerateApples();
}

[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IFruitCounter))]
public class FruitCounter : IFruitCounter
{
    public Apples[] enumerateApples()
    {
        List<Apples> applesList = new List<Apples>();

        //* Add some apples - well, one in fact for the time being 
        Apples app = new Apples();
        app.variety = "Braeburn";
        app.quantity = 4;
        applesList.Add(app);

        // * finished adding apples want to convert to SAFEARRAY 
        return applesList.ToArray();
    }
}

这适用于早期绑定,但不适用于后期绑定。

  1. Build project, one should have a dll and a tlb.
    • 一种输出是 LateBoundSafeArraysProblem.dll
    • 还将输出一个类型库,LateBoundSafeArraysProblem.tlb ‡

早期绑定的 Excel VBA 客户端代码

  1. 打开调试启动选项中指定的工作簿(参见上文 †)
  2. 添加基本​​标准模块(不是类模块)。
  3. 转到“工具”->“参考”并选中生成的类型库的复选框(参见上文 ‡)
  4. 添加以下代码


    Sub TestEarlyBound()
        'Tools -> References to type library LateBoundSafeArraysProblem.tlb 
        Dim fc As LateBoundSafeArraysProblem.FruitCounter
        Set fc = New LateBoundSafeArraysProblem.FruitCounter

        Dim apples() As LateBoundSafeArraysProblem.apples
        apples() = fc.enumerateApples()

        Stop

    End Sub
  

当执行到达停止时,人们可以检查数组内容是否成功编组。 早期绑定成功!

后期绑定 Excel VBA 客户端代码

  1. 我还使用 Excel VBA 中的后期绑定来摆脱部署麻烦,我还可以热交换 dll,即安装新的 COM 服务器而不关闭 Excel(我应该在 SO 上发布该技巧)。
  2. 从 (1) 开始,我将使用相同的 Excel VBA 工作簿作为测试后期绑定的平台,并相应地更改声明。
  3. 在同一模块中添加以下代码

        Sub TestFruitLateBound0()
    
    
        Dim fc As Object 'LateBoundSafeArraysProblem.FruitCounter
        Set fc = CreateObject("LateBoundSafeArraysProblem.FruitCounter")
    
        Dim apples() As Object 'LateBoundSafeArraysProblem.apples
        apples() = fc.enumerateApples()   '<==== Type Mismatch thrown
    
        Stop
    
    End Sub
    

运行此代码会在标记行处引发类型不匹配(VB 错误 13)。因此,相同的 COM 服务器代码在 Excel VBA 后期绑定模式下不起作用。 后期绑定失败!

解决方法

  1. 因此,在调查过程中,我编写了第二个返回类型为 Object[] 的方法,最初这不起作用,因为生成的 idl 作为星号丢弃。 idl 来自

    
    
        // Return type Apples[] works for early binding but not late binding
        // works
        HRESULT enumerateApples([out, retval] SAFEARRAY(IApples*)* pRetVal);
      

    to

    
    
        // Return type Object[] fails because we drop a level of indirection 
        // (perhaps confusion between value and reference types)
        // does NOT work AT ALL (late or early)
        HRESULT enumerateApplesLateBound([out, retval] SAFEARRAY(VARIANT)* pRetVal);      // dropped as asterisk becomes SAFEARRAY to value types, no good 
      
  2. 使用 MarshalAs 属性固定星号的数量

    
    
        // Still with Object[] but using MarshalAs
        // [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = System.Runtime.InteropServices.VarEnum.VT_UNKNOWN)]
        // works for late-bound but not early bound !!! Aaaargh !!!!
        HRESULT enumerateApplesLateBound([out, retval] SAFEARRAY(IDispatch*)* pRetVal);   
      

    这适用于后期装订,但不适用于耳环装订!啊啊啊!

Summary

我使用了两种方法,一种用于早期绑定,一种用于后期绑定,这并不令人满意,因为这意味着每种方法都要加倍。

如何让一种方法同时适用于早期和后期绑定?


我对此进行了一些测试,将返回值编组到Variant,然后转储返回的 VARIANT 结构的内存以查看 VARTYPE 是什么。对于早期绑定调用,它返回一个VariantVARTYPE 为VT_ARRAY & VT_DISPATCH。对于后期绑定调用,它返回的 VARTYPE 为VT_ARRAY & VT_UNKNOWN。苹果should已经被定义为实施IDispatch在 tlb 中,但由于某种我无法理解的原因,VBA 很难处理一系列IUnknown来自后期绑定呼叫。解决方法是将返回类型更改为object[]在 C# 方面...

public object[] enumerateApples()
{
    List<object> applesList = new List<object>();

    //* Add some apples - well, one in fact for the time being 
    Apples app = new Apples();
    app.variety = "Braeburn";
    app.quantity = 4;
    applesList.Add(app);

    // * finished adding apples want to convert to SAFEARRAY 
    return applesList.ToArray();
}

...并将它们拉入Variant在 VBA 方面:

Sub TestEarlyBound()
    'Tools -> References to type library LateBoundSafeArraysProblem.tlb
    Dim fc As LateBoundSafeArraysProblem.FruitCounter
    Set fc = New LateBoundSafeArraysProblem.FruitCounter

    Dim apples As Variant
    apples = fc.enumerateApples()

    Debug.Print apples(0).variety   'prints "Braeburn"
End Sub

Sub TestFruitLateBound0()
    Dim fc As Object
    Set fc = CreateObject("LateBoundSafeArraysProblem.FruitCounter")

    Dim apples As Variant
    apples = fc.enumerateApples()

    Debug.Print apples(0).variety   'prints "Braeburn"
End Sub
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Windows 上,使用 C# 编写的 COM 服务器,可以为早期绑定和后期绑定代码返回 SAFEARRAY 吗? 的相关文章

  • 如何检查图像对象与资源中的图像对象是否相同?

    所以我试图创建一个简单的程序 只需在单击图片框中更改图片即可 我目前只使用两张图片 所以我的图片框单击事件函数的代码 看起来像这样 private void pictureBox1 Click object sender EventArgs
  • 无法使用已与其底层 RCW 分离的 COM 对象。在 oledb 中

    我收到此错误 但我不知道我做错了什么 下面的代码在backrgroundworker中 将异常详细信息复制到剪贴板 System Runtime InteropServices InvalidComObjectException 未处理 通
  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • 指针问题(仅在发布版本中)

    不确定如何描述这一点 但我在这里 由于某种原因 当尝试创建我的游戏的发布版本进行测试时 它的敌人创建方面不起作用 Enemies e level1 3 e level1 0 Enemies sdlLib 500 2 3 128 250 32
  • C - 找到极限之间的所有友好数字

    首先是定义 一对友好的数字由两个不同的整数组成 其中 第一个整数的除数之和等于第二个整数 并且 第二个整数的除数之和等于第一个整数 完美数是等于其自身约数之和的数 我想做的是制作一个程序 询问用户一个下限和一个上限 然后向他 她提供这两个限
  • C#:如何防止主窗体过早显示

    在我的 main 方法中 我像往常一样启动主窗体 Application EnableVisualStyles Application SetCompatibleTextRenderingDefault false Application
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 如果使用 SingleOrDefault() 并在数字列表中搜索不在列表中的数字,如何返回 null?

    使用查询正数列表时SingleOrDefault 当在列表中找不到数字时 如何返回 null 或像 1 这样的自定义值 而不是类型的默认值 在本例中为 0 你可以使用 var first theIntegers Cast
  • WPF TabControl,用C#代码更改TabItem的背景颜色

    嗨 我认为这是一个初学者的问题 我搜索了所有相关问题 但所有这些都由 xaml 回答 但是 我需要的是后台代码 我有一个 TabControl 我需要设置其项目的背景颜色 我需要在选择 取消选择和悬停时为项目设置不同的颜色 非常感谢你的帮助
  • Web API - 访问 DbContext 类中的 HttpContext

    在我的 C Web API 应用程序中 我添加了CreatedDate and CreatedBy所有表中的列 现在 每当在任何表中添加新记录时 我想填充这些列 为此目的我已经覆盖SaveChanges and SaveChangesAsy
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • 从路径中获取文件夹名称

    我有一些路c server folderName1 another name something another folder 我如何从那里提取最后一个文件夹名称 我尝试了几件事 但没有成功 我只是不想寻找最后的 然后就去休息了 Thank
  • Qt表格小部件,删除行的按钮

    我有一个 QTableWidget 对于所有行 我将一列的 setCellWidget 设置为按钮 我想将此按钮连接到删除该行的函数 我尝试了这段代码 它不起作用 因为如果我只是单击按钮 我不会将当前行设置为按钮的行 ui gt table
  • 如何使我的表单标题栏遵循 Windows 深色主题?

    我已经下载了Windows 10更新包括黑暗主题 文件资源管理器等都是深色主题 但是当我创建自己的 C 表单应用程序时 标题栏是亮白色的 如何使我自己的桌面应用程序遵循我在 Windows 中设置的深色主题 你需要调用DwmSetWindo
  • 32 位到 64 位内联汇编移植

    我有一段 C 代码 在 GNU Linux 环境下用 g 编译 它加载一个函数指针 它如何执行并不重要 使用一些内联汇编将一些参数推送到堆栈上 然后调用该函数 代码如下 unsigned long stack 1 23 33 43 save
  • 如何在 C++ BOOST 中像图形一样加载 TIFF 图像

    我想要加载一个 tiff 图像 带有带有浮点值的像素的 GEOTIFF 例如 boost C 中的图形 我是 C 的新手 我的目标是使用从源 A 到目标 B 的双向 Dijkstra 来获得更高的性能 Boost GIL load tiif
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我

随机推荐