我正在创建一个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类型),引用仍然存在。
答案 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