“关键问题在于我不希望C++客户端应用程序需要导入mscorlib.tlb。”
这是不可能的,因为您正在使用.NET创建您的COM coclass,它会自动引入mscorlib.tlb和mscoree.dll。尝试使用一个只能将两个整数相加的简单对象进行测试。
正如Hans Passant所指出的,您根本不需要接口。任何COM集合都必须基于C#集合,例如List<T>。该C#集合有一个方法GetEnumeration(),它会输出一个作为COM中的IEnumVARIANT的IEnumeration对象。您所要做的就是在接口中包含IEnumerator GetEnumerator();并将实现委派给C#集合的GetEnumeration()方法。
我在一个完整的示例中展示了这一点。考虑一个管理账户集合的Bank coclass。我需要用于Bank、Account和AllAccounts集合的coclass。
我从关键的AllAccounts coclass开始:
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace BankServerCSharp
{
[ComVisible(true)]
[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)]
[ClassInterface(ClassInterfaceType.None)]
public class AllAccounts:IAllAccounts
{
private AllAccounts(){ }
private List<IAccount> Al = new List<IAccount>();
public static AllAccounts MakeAllAccounts() { return new AllAccounts(); }
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
值 0 和 -4 是默认 Item
方法和 GetEnumerator()
方法所必需的。另外两个文件是:
Account.cs:
using System.Runtime.InteropServices;
namespace BankServerCSharp
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAccount
{
double Balance { get; }
void Deposit(double b);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Account:IAccount
{
private double mBalance = 0;
private Account() { }
public static Account MakeAccount() { return new Account(); }
public double Balance { get { return mBalance; } }
public void Deposit(double b) { mBalance += b; }
}
}
Bank.cs:
using System.Runtime.InteropServices;
namespace BankServerCSharp
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IBank { IAllAccounts Accounts { get; } }
[ComVisible(true)]
[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"
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);
AllPtr->RemoveAccount(1);
}
catch (const _com_error& e)
{
MessageBoxW(NULL, L"Oops! Index out of range!", L"Error", MB_OK);
}
CoUninitialize();
return 0;
}
备注:在C++中,Item
是一个向量。我不知道如何将其更改为通常的函数形式,即使用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
ICarEnumerator
接口,但当我调用For Each car in cars.GetCars()
时,VBscript 报错:对象不支持此属性或方法。有什么想法吗? - peval27