我正在创建一个 COM 接口(interface),它应该允许在 Visual Basic 脚本中使用 For Each
,在 C++ 中使用 IEnumVariant
。问题是我不希望 C++ 客户端应用程序需要导入 mscorlib.tlb。
到目前为止,我的界面是:
[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();
}
}
TlbExp 吐出这段代码:
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: carsIEnumerator.tlb
[
uuid(3BBCEAA2-9498-48BF-8053-1CEFB3C1C86F),
version(1.0),
custom(90883F05-3D28-11D2-8F17-00A0C9A6186D, "ClassLibraryIEnumerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
]
library ClassLibraryIEnumerator
{
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface ICars;
[
odl,
uuid(ABD2A9E4-D5C5-3ED9-88AF-4C310BD5792D),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibraryIEnumerator.ICars")
]
interface ICars : IDispatch {
[id(0x60020000), propget]
HRESULT Count([out, retval] long* pRetVal);
};
我怎样才能避免这种情况?
即使我只有我的自定义接口(interface)和一个类(不使用任何 .NET 类型),引用仍然存在。
最佳答案
IEnumVARIANT 类型声明必须来自某个地方。它不是每个编译器都知道的像 int
这样的标准类型。如果您自己编写 IDL,那么您将使用 #import "oaidl.idl"
来包含定义。但这不能在 .NET 中工作,因为类型库导出器不使用 IDL。所以它来自一个导出商确实知道的地方,mscorlib.tlb
解决方法是将接口(interface)声明放在您自己的代码中,而不是在 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。
声明您自己的枚举器接口(interface)类型也是一种解决方案,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; }
}
关于C# COM 实现可枚举而不引用 MSCORLIB,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43408872/