首先,我想做什么:
我有一个现有的用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)
{
}
那么,我应该如何将这些C#方法呈现为(并且在VB6中使用时能够)一个具有参数的属性?
我尝试创建两个方法,一个名为“set_PropName”,另一个名为“get_PropName”,希望它们在被VB6使用时能被视为单个具有参数的属性,但是这并没有奏效。它们在VB6中被视为两个不同的方法调用。
我认为可能需要在C#中为它们应用一些属性,以便它们在COM和VB6中被视为单个参数化属性,但我找不到任何适当的属性。
我还尝试了重载这些方法,删除“get_”和“set_”,希望它们被视为单个属性,但这也不起作用。这会在VB6中生成此错误:“Property let procedure not defined and property get procedure did not 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会出现以下错误:
我认为问题在于.NET尝试将值设置给方法,而不是返回对象的默认属性,或类似的东西。如果在设置行中添加“.Value”,它就可以正常工作了。托管调试助手“FatalExecutionEngineError”在“blahblah.exe”中检测到问题。
其他信息:运行时遇到了致命错误。错误地址为0x734a60f4,在线程0x1694上。错误代码为0xc0000005。这个错误可能是CLR或用户代码中的不安全或不可验证部分中的错误。此错误的常见来源包括COM-Interop或PInvoke的用户编组错误,这可能会破坏堆栈。
更新#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);
}
}
}
第二,当使用 accessor 时,您需要将返回类型设置为对象:
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在这方面的帮助!