C#COM实现可枚举而不引用MSCORLIB

时间:2017-04-14 09:31:06

标签: c# com

我正在创建一个COM接口,它允许在Visual Basic脚本中使用$check_value = $_POST['my_checkbox_name'] ?? 0; ,在C ++中使用For Each。问题是我不希望C ++客户端应用程序需要导入mscorlib.tlb。

到目前为止我的界面是:

IEnumVariant

TlbExp吐出这段代码:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICars : System.Runtime.InteropServices.ComTypes.IEnumVARIANT
{
    int Count { get; }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Cars : ICars
{
    int ICars.Count => throw new NotImplementedException();

    int IEnumVARIANT.Next(int celt, object[] rgVar, IntPtr pceltFetched)
    {
        throw new NotImplementedException();
    }

    int IEnumVARIANT.Skip(int celt)
    {
        throw new NotImplementedException();
    }

    int IEnumVARIANT.Reset()
    {
        throw new NotImplementedException();
    }

    IEnumVARIANT IEnumVARIANT.Clone()
    {
        throw new NotImplementedException();
    }
}

我该如何避免这种情况?

即使我只有自定义界面和单个类(不使用任何.NET类型),引用仍然存在。

3 个答案:

答案 0 :(得分:2)

IEnumVARIANT类型声明必须来自某处。它不是每个编译器都知道的标准类型,如int。如果您自己编写IDL,那么您可以使用#import "oaidl.idl"来包含该定义。但由于类型库导出器不使用IDL,因此无法在.NET中运行。所以它来自出口商确实知道的地方,mscorlib.tlb

解决方法是将接口声明放在您自己的代码中,而不是使用mscorlib中的接口声明。从Reference Source或此处复制/粘贴它:

[Guid("00020404-0000-0000-C000-000000000046")]   
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
public interface IEnumVARIANT
{
    [PreserveSig]
    int Next(int celt, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0), Out] object[] rgVar, IntPtr pceltFetched);

    [PreserveSig]
    int Skip(int celt);

    [PreserveSig]
    int Reset();

    IEnumVARIANT Clone();
}

在ICars声明中使用YourNamespace.IEnumVARIANT。

声明自己的枚举器接口类型也是一种解决方案,IEnumVARIANT不会赢得任何奖品。你可以删除任何人都没用的好方法,你可以把它变成类型安全的。如果您也控制客户端代码或者不必使脚本语言中的foreach满意,那么这是一个可接受的替代方法。考虑:

[ComVisible(true)]
public interface ICarEnumerator {
    ICar Next();
}

在ICars界面中ICarEnumerator GetCars()

最后但并非最不重要的是,考虑根本不实现迭代器。只需使它看起来像客户端代码中的数组:

[ComVisible(true)]
public interface ICars
{
    int Count { get; }
    ICar this[int index] { get; }
}

答案 1 :(得分:0)

我有同样的问题/需要,我找到了这篇好文章。

https://limbioliong.wordpress.com/2011/10/28/exposing-an-enumerator-from-managed-code-to-com/

答案 2 :(得分:0)

“问题是我不希望C ++客户端应用程序需要导入mscorlib.tlb。”

这是不可能的,因为您正在使用.NET创建COM协同类,这会自动将mscorlib.tlb和mscoree.dll发挥作用。用一个只能加两个整数的简单对象尝试一下。

正如汉斯·帕桑(Hans Passant)所指出的,您根本不需要界面IEnumVARIANT。任何COM集合都必须基于C#集合,例如List<T>。此C#集合具有方法GetEnumeration(),该方法吐出IEnumeration对象,在COM中用作IEnumVARIANT。您所需要做的就是在界面中包含IEnumerator GetEnumerator();,并将实现委派给C#集合的GetEnumeration()方法。

我在一个完整的例子中展示了这一点。考虑一个管理帐户集合的银行共同类。我需要银行,帐户和AllAccounts集合的共同类。

我从至关重要的共同类AllAccounts开始:

//AllAccounts.cs:
using System;
using System.Collections;
using System.Runtime.InteropServices;

namespace BankServerCSharp
{
  [ComVisible(true)]  // This is mandatory.
  [InterfaceType(ComInterfaceType.InterfaceIsDual)]
  public interface IAllAccounts
  {
    int Count{ get; }

    [DispId(0)]
    IAccount Item(int i);

    [DispId(-4)]
    IEnumerator GetEnumerator();

    Account AddAccount();

    void RemoveAccount(int i);

    void ClearAllAccounts();
  }

  [ComVisible(true)]  // This is mandatory.
  [ClassInterface(ClassInterfaceType.None)]
  public class AllAccounts:IAllAccounts 
  {
    private AllAccounts(){ } // private constructor, coclass noncreatable
    private List<IAccount> Al = new List<IAccount>();
    public static AllAccounts MakeAllAccounts() { return new AllAccounts(); }
    //public, but not exposed to COM

    public IEnumerator GetEnumerator() { return Al.GetEnumerator();  }

    public int Count { get { return Al.Count; } }

    public IAccount Item(int i)  { return (IAccount)Al[i - 1];  }

    public Account AddAccount() { Account acc = Account.MakeAccount();
                                        Al.Add(acc); return acc; }

    public void RemoveAccount(int i) {  Al.RemoveAt(i - 1);  }

    public void ClearAllAccounts() { Al.Clear(); }

  }
}

默认的DispId方法和Item方法要求GetEnumerator()值为0和-4。其他两个文件是:

Account.cs:
using System.Runtime.InteropServices;

namespace BankServerCSharp
{
  [ComVisible(true)]  // This is mandatory.
  [InterfaceType(ComInterfaceType.InterfaceIsDual)]
  public interface IAccount
  {
    double Balance { get; } // A property
    void Deposit(double b); // A method
  }

  [ComVisible(true)]  // This is mandatory.
  [ClassInterface(ClassInterfaceType.None)]
  public class Account:IAccount
  {
    private double mBalance = 0;
    private Account() { }     // private constructor, coclass noncreatable

    public static Account MakeAccount() { return new Account(); }
    //MakeAccount is not exposed to COM, but can be used by other classes

    public double Balance  { get {  return mBalance; } }
    public void Deposit(double b) { mBalance += b; }
  }
}

Bank.cs:
using System.Runtime.InteropServices;

namespace BankServerCSharp
{
  [ComVisible(true)]  // This is mandatory.
  [InterfaceType(ComInterfaceType.InterfaceIsDual)]
  public interface IBank { IAllAccounts Accounts { get; }  }

  [ComVisible(true)]  // This is mandatory.
  [ClassInterface(ClassInterfaceType.None)]
  public class Bank:IBank
  {
    private readonly AllAccounts All;
    public Bank() {  All = AllAccounts.MakeAllAccounts(); } 
    public IAllAccounts Accounts {  get { return All; } }
  }
}

您必须使用x64版本的Regasm注册服务器。

使用C ++对服务器进行测试:

#include "stdafx.h"
#include <string>
#import  "D:\Aktuell\CSharpProjects\BankServerCSharp\BankServerCSharp\bin\Release\BankServerCSharp.tlb"
//this is the path of my C# project's bin\Release folder
inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_errorex(x, nullptr, ID_NULL);}
int main()
{
  try
  {
    TESTHR(CoInitialize(0));
    BankServerCSharp::IBankPtr BankPtr = nullptr;
    TESTHR(BankPtr.CreateInstance("BankServerCSharp.Bank"));
    BankServerCSharp::IAllAccountsPtr AllPtr = BankPtr->Accounts;
    BankServerCSharp::IAccountPtr FirstAccountPtr = AllPtr->AddAccount();
    TESTHR(FirstAccountPtr->Deposit(47.11));
    AllPtr->AddAccount();
    TESTHR(AllPtr->Item[2]->Deposit(4711));

    CStringW out, add;
    for (int i = 1; i <= AllPtr->Count; i++)
    {
      add.Format(L"Balance of account %d: %.2f.\n", i, AllPtr->Item[i]->Balance);
      out += add;
    }

    out += L"\n";
    AllPtr->RemoveAccount(1);
    for (int i = 1; i <= AllPtr->Count; i++)
    {
      add.Format(L"Balance of account %d: %.2f.\n", i, AllPtr->Item[i]->Balance);
      out += add;
    }

    AllPtr->ClearAllAccounts();
    add.Format(L"Number of accounts: %ld.\n", AllPtr->Count);
    out += L"\n" + add;
    MessageBoxW(NULL, out, L"Result", MB_OK);

    //Raise an exception:
    AllPtr->RemoveAccount(1);
  }
  catch (const _com_error& e)
  {
    MessageBoxW(NULL, L"Oops! Index out of range!", L"Error", MB_OK);
  }
  CoUninitialize();// Uninitialize COM
  return 0;
}

备注:Item是C ++中的向量。我不知道如何将其更改为通常的功能形式,即Item(i)而不是Item[i]

在VBA中,您可以使用心爱的For Each循环:

Sub CSharpBankTest()
 On Error GoTo Oops

  Dim Out As String
  Dim Bank As New BankServerCSharp.Bank            'New!

  Dim AllAccounts As BankServerCSharp.AllAccounts  'No New!
  Set AllAccounts = Bank.Accounts

  Dim AccountOne As BankServerCSharp.Account       'No New
  Set AccountOne = AllAccounts.AddAccount
  AccountOne.Deposit 47.11

  AllAccounts.AddAccount
  AllAccounts(2).Deposit 4711

  Dim i As Long
  Dim ac As BankServerCSharp.Account
  For Each ac In AllAccounts
    i = i + 1
    Out = Out & "Balance of account " & i & ": " & ac.Balance & vbNewLine
  Next
  Exit Sub
Oops:
  MsgBox "Error Message : " & Err.Description, vbOKOnly, "Error"
End Sub