【发布时间】:2016-01-07 22:32:09
【问题描述】:
我对这个问题进行了相当多的研究,虽然我发现了很多关于 C# 和参数化属性的信息(使用索引器是唯一的方法),但我还没有找到我的问题的实际答案。
首先,我要做什么:
我有一个用 VB6 编写的现有 COM DLL,我正在尝试创建一个使用类似接口的 C# DLL。我之所以这么说是因为 VB6 DLL 仅用于后期绑定,因此它不必为调用具有相同的 GUID(即,它不必是“二进制兼容的”)。这个 VB6 COM DLL 在一些地方使用了参数化属性,我知道 C# 不支持这些属性。
当使用带有参数化属性的 VB6 COM DLL 时,C# 中的引用将以“get_PropName”和“set_PropName”形式访问它们。但是,我正朝着相反的方向前进:我不是要在 C# 中访问 VB6 DLL,而是要使 C# COM DLL 与 VB6 DLL 兼容。
所以,问题是:如何在 C# COM DLL 中创建 getter 和 setter 方法,以便在 VB6 使用时显示为单个参数化属性?
例如,假设 VB6 属性定义如下:
Public Property Get MyProperty(Param1 As String, Param2 as String) As String
End Property
Public Property Let MyProperty(Param1 As String, Param2 As String, NewValue As String)
End Property
C# 中的等价物是这样的:
public string get_MyProperty(string Param1, string Param2)
{
}
public void set_MyProperty(string Param1, string Param2, ref string NewValue)
{
}
那么,当 VB6 使用这些 C# 方法时,我如何使这些方法看起来(和功能)是单个参数化属性?
我尝试创建两种方法,一种称为“set_PropName”,另一种称为“get_PropName”,希望在 VB6 使用时它们应该是单个参数化属性,但这不起作用;它们作为来自 VB6 的两种不同的方法调用出现。
我认为可能需要在 C# 中将某些属性应用于它们,以便它们在 COM 和 VB6 中被视为单个参数化属性,但我找不到任何似乎合适的属性。
我还尝试重载方法,删除“get_”和“set_”,希望它会将它们视为单个属性,但这也不起作用。那个在 VB6 中产生了这个错误:“Property let procedure not defined and property get procedure didn't return an object”。
我几乎肯定应该有办法做到这一点,但我似乎无法找到它。有谁知道怎么做?
更新:
我采纳了 Ben 的建议并添加了一个访问器类,看看这是否可以解决我的问题。但是,现在我遇到了另一个问题...
首先,这是我正在使用的 COM 接口:
[ComVisible(true),
Guid("94EC4909-5C60-4DF8-99AD-FEBC9208CE76"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ISystem
{
object get_RefInfo(string PropertyName, int index = 0, int subindex = 0);
void set_RefInfo(string PropertyName, int index = 0, int subindex = 0, object theValue);
RefInfoAccessor RefInfo { get; }
}
这是访问器类:
public class RefInfoAccessor
{
readonly ISystem mySys;
public RefInfoAccessor(ISystem sys)
{
this.mySys = sys;
}
public object this[string PropertyName, int index = 0, int subindex = 0]
{
get
{
return mySys.get_RefInfo(PropertyName, index, subindex);
}
set
{
mySys.set_RefInfo(PropertyName, index, subindex, value);
}
}
}
这是实现:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid(MySystem.ClassId)]
[ProgId("MyApp.System")]
public class MySystem : ISystem
{
internal const string ClassId = "60A84737-8E96-4DF3-A052-7CEB855EBEC8";
public MySystem()
{
_RefInfo = new RefInfoAccessor(this);
}
public object get_RefInfo(string PropertyName, int index = 0, int subindex = 0)
{
// External code does the actual work
return "Test";
}
public void set_RefInfo(string PropertyName, int index = 0, int subindex = 0, object theValue)
{
// External code does the actual work
}
private RefInfoAccessor _RefInfo;
public RefInfoAccessor RefInfo
{
get
{
return _RefInfo;
}
}
}
这是我在 VB6 中测试它的方法,但出现错误:
Set sys = CreateObject("MyApp.System")
' The following statement gets this error:
' "Wrong number of arguments or invalid property assignment"
s = sys.RefInfo("MyTestProperty", 0, 0)
但是,这是可行的:
Set sys = CreateObject("MyApp.System")
Set obj = sys.RefInfo
s = obj("MyTestProperty", 0, 0)
似乎它正在尝试使用属性本身的参数并收到错误,因为该属性没有参数。如果我在自己的对象变量中引用 RefInfo 属性,那么它会正确应用索引器属性。
关于如何安排它以使其知道将参数应用于访问器的索引器而不是尝试将其应用于属性的任何想法?
另外,我如何进行 +1?这是我在 StackOverflow 上的第一个问题 :-)
更新 #2:
只是为了看看它是如何工作的,我还尝试了默认值方法。下面是访问器现在的样子:
public class RefInfoAccessor
{
readonly ISystem mySys;
private int _index;
private int _subindex;
private string _propertyName;
public RefInfoAccessor(ISystem sys, string propertyName, int index, int subindex)
{
this.mySys = sys;
this._index = index;
this._subindex = subindex;
this._propertyName = propertyName;
}
[DispId(0)]
public object Value
{
get
{
return mySys.get_RefInfo(_propertyName, _index, _subindex);
}
set
{
mySys.set_RefInfo(_propertyName, _index, _subindex, value);
}
}
}
这非常适合“获取”。但是,当我尝试设置该值时,.NET 会出现以下错误:
托管调试助手“FatalExecutionEngineError”检测到 'blahblah.exe' 中的问题。
附加信息:运行时遇到致命错误。这 错误地址位于线程 0x1694 上的 0x734a60f4。错误 代码是 0xc0000005。此错误可能是 CLR 或 用户代码的不安全或不可验证部分。这个的常见来源 错误包括 COM-interop 或 PInvoke 的用户编组错误,其中 可能会损坏堆栈。
我假设问题是 .NET 尝试将值设置为方法,而不是返回对象的默认属性,或类似的东西。如果我将“.Value”添加到设置行,它可以正常工作。
更新 #3:成功!
我终于得到了这个工作。然而,有一些事情需要寻找。
首先,访问器的默认值必须返回一个缩放器,而不是一个对象,如下所示:
public class RefInfoAccessor
{
readonly ISystem mySys;
private int _index;
private int _subindex;
private string _propertyName;
public RefInfoAccessor(ISystem sys, string propertyName, int index, int subindex)
{
this.mySys = sys;
this._index = index;
this._subindex = subindex;
this._propertyName = propertyName;
}
[DispId(0)]
public string Value // <== Can't be "object"
{
get
{
return mySys.get_RefInfo(_propertyName, _index, _subindex).ToString();
}
set
{
mySys.set_RefInfo(_propertyName, _index, _subindex, value);
}
}
}
其次,在使用访问器时,需要将返回类型设为对象:
public object RefInfo(string PropertyName, int index = 0, int subindex = 0)
{
return new RefInfoAccessor(this,PropertyName,index,subindex);
}
这将使 C# 高兴,因为默认值是 COM 事物(dispid 0)而不是 C# 事物,因此 C# 期望返回 RefInfoAccessor,而不是字符串。由于可以将 RefInfoAccessor 强制转换为对象,因此不会出现编译错误。
在 VB6 中使用时,以下内容现在都可以使用:
s = sys.RefInfo("MyProperty", 0, 0)
Debug.Print s
sys.RefInfo("MyProperty", 0, 0) = "Test" ' This now works!
s = sys.RefInfo("MyProperty", 0)
Debug.Print s
非常感谢 Ben 在这方面的帮助!
【问题讨论】: