在使用C#编写的COM服务器的Windows上,可以为早绑定和晚绑定代码返回SAFEARRAY吗? [英] On Windows, with a C# authored COM server, can one return a SAFEARRAY both for early bound and late bound code?

查看:97
本文介绍了在使用C#编写的COM服务器的Windows上,可以为早绑定和晚绑定代码返回SAFEARRAY吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题很长,所以我将用项目符号格式化以便于讨论

The question is quite long, so I'll format with bullet points for easier discussion


  1. 我正在编写C#COM服务器。

  2. COM服务器既可以在早期绑定模式下也可以在后期绑定模式下在Excel VBA中使用。

  3. 我的绊脚石是如何返回可在早期和晚期绑定模式下工作的实例化类的SAFEARRAY;我得到错误。

  4. (整天)我已经做了很多工作:


    • 我已经做了一些诊断并设置了调试器来说明我遇到的错误。

    • 我做了一些相当详尽的谷歌搜索。

    • 我发现了一些不太令人满意的解决方法。

    • 我现在真的很沮丧,正在寻找COM Interop专家来帮助我寻求一个好的解决方案。

  1. I'm writing a C# COM Server.
  2. the COM server is for use in Excel VBA both in early binding and late binding modes.
  3. My stumbling block is how to return a SAFEARRAY of instantiated classes that works both in early and late binding mode; I get errors.
  4. I have done plenty of work on this (all day):
    • I have done some diagnostics and setup the debugger to shed light on the errors I get.
    • I have done some fairly exhaustive googling.
    • I found some workarounds which are less than satisfactory.
    • I am now genuinely stumped and looking for a COM Interop expert to help me get to a good solution please.



设置项目类型和项目属性



To set up the project type and project properties


  1. 创建一个新的C#类库项目。

  2. 我将其命名为LateBoundSafeArraysProblem,也将源文件重命名为LateBoundSafeArraysProblem.cs。

  3. 在AssemblyInfo.cs中将第20行修改为ComVisible(true),因此可见性是通用的(仍然需要公共关键字)。

  4. 设置项目属性:


    • 设置构建选项,在项目属性->构建->输出中,选中注册COM互操作复选框。

    • 设置调试选项以启动Excel并加载excel工作簿客户端:


      • 在项目属性->调试->开始操作中,选择单选按钮开始外部问题并输入Microsoft Excel的路径,对我来说,该路径是 C:\Program Files\Microsoft Office 15\root\office15\excel.exe。

      • 在项目属性->调试->启动选项中,输入启用了客户端Excel宏的工作簿的名称,对我来说是C:bookTemp\LateBoundSafeArraysProblemClient.xlsm。 †

  1. Create a new C# Class library project.
  2. I named mine LateBoundSafeArraysProblem, also I renamed the source file to be LateBoundSafeArraysProblem.cs.
  3. In AssemblyInfo.cs amend line 20 to ComVisible(true) , so visibility is universal (still needs public keywords).
  4. Set the Project Properties:
    • Set the build options, in Project Properties->Build->Output I check the 'Register for COM interop' checkbox.
    • Set the debug options to launch Excel and load an excel workbook client:
      • In Project Properties->Debug->Start Action and select radio button 'Start external problem' and enter path to Microsoft Excel which for me is 'C:\Program Files\Microsoft Office 15\root\office15\excel.exe'.
      • In Project Properties->Debug->Start Options enter the name of a client Excel macro enabled workbook, which for me C:\Temp\LateBoundSafeArraysProblemClient.xlsm. †



创建COM服务器源代码



To create the COM server source code


  1. 样式选择和决策


    • I成为一名优秀的COM公民,并将接口定义与类定义分开。


      • 我在类上使用[ClassInterface(ClassInterfaceType.None)]和[ComDefaultInterface(typeof(< interface>)))属性来实现此目的清楚的划分。


  • 苹果是编组的简单国家船只数据返回给客户端,除了getter和setter之外,没有任何方法。

  • FruitCounter是一个工作器类,它具有方法enumerateApples(),该方法将返回Apple实例的SAFEARRAY。

因此,Apples接口和类的源代码为:

So the source code for the Apples interface and class are:

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; }
}

上面的代码毫无争议,可以正常工作。

The above code is uncontentious and works fine.

FruitContainer接口和类的源代码为

The source code for the FruitContainer interface and class are

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();
    }
}

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

And this will work for early binding but not for late binding.


  1. 构建项目,应该有一个dll和tlb。


    • 一个输出将是LateBoundSafeArraysProblem.dll

    • 还将输出一个类型库LateBoundSafeArraysProblem.tlb•



Early Bound Excel VBA客户代码



The Early Bound Excel VBA client code


  1. 打开在调试启动选项中指定的工作簿(请参阅上面的†)

  2. 添加基本的标准模块(不是类模块)。 / li>
  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

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

When execution reached Stop then one can inspect the array contents for a successful marshalling. SUCCESS FOR EARLY BINDING!


  1. 我还使用Excel VBA中的后期绑定功能来使自己摆脱部署刮擦,也可以热交换dll,即在不关闭Excel的情况下安装新的COM服务器(我应该把这个技巧贴在SO上)。

  2. 从(1)开始,我将使用与测试后期绑定的平台相同的Excel VBA工作簿,并相应地更改声明。

  3. 在同一模块添加以下代码

  1. I also use late bounding in Excel VBA to get myself out of deployment scrapes, also I can hot swap dlls i.e. install a new COM server without closing Excel (I ought to post that trick on SO).
  2. From (1) I'm going to use the same Excel VBA workbook as the platform for testing the late binding and change the declarations accordingly.
  3. In the same module add the following code


    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




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

Running this code throws a Type Mismatch (VB error 13) at the marked line. So the same COM server code does not work in Excel VBA late binding mode. FAILURE FOR LATE BINDING!


  1. 因此,在调查期间,我用返回类型Object []编写了第二种方法,最初这没有用,因为生成的idl掉了星号。 idl来自

  1. So during investigation I wrote a second method with return type Object[], initially this didn't work because the generated idl dropped as asterisk. The idl went from


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


    // 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 


  • 使用MarshalAs属性固定了星号的数量

  • Using the MarshalAs attribute fixed the number of asterisks

    
        // 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);   
    

    可以用于后期装订,但不能用于耳环装订! Aaaargh!

    and that worked for late binding but NOT for earling binding! Aaaargh !



    摘要



    我有两种工作方法,一种用于早期绑定,一种用于后期绑定,这不能令人满意,因为这意味着将每种方法加倍。

    Summary

    I got two methods working, one for early binding and one for late binding which is unsatisfactory because it means doubling up every method.

    推荐答案

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

    I did a bit of testing on this by marshaling the returned value in to a Variant, and then dumped the memory of the returned VARIANT structure to see what the VARTYPE was. For the early bound call, it was returning a Variant with a VARTYPE of VT_ARRAY & VT_DISPATCH. For the late bound call, it was returning a VARTYPE of VT_ARRAY & VT_UNKNOWN. Apples should already be defined as implementing IDispatch in the tlb, but for some reason that eludes me, VBA is having difficulty handling an array of IUnknown from the late bound call. The work-around is to change the return type to object[] on the C# side...

    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上:

    ...and pull them into a Variant on the VBA side:

    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
    

    这篇关于在使用C#编写的COM服务器的Windows上,可以为早绑定和晚绑定代码返回SAFEARRAY吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆