如何使C#COM类支持VB6中的参数化属性
我对这个问题做了很多研究,虽然我发现了很多关于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属性定义如下:如何使C#COM类支持VB6中的参数化属性,c#,.net,dll,com,vb6,C#,.net,Dll,Com,Vb6,我对这个问题做了很多研究,虽然我发现了很多关于C#和参数化属性的知识(使用索引器是唯一的方法),但我还没有找到我的问题的实际答案 首先,我想做的是: 我有一个用VB6编写的现有COM DLL,我正在尝试创建一个使用类似接口的C#DLL。我之所以说类似,是因为VB6 DLL只用于后期绑定,因此调用不必使用相同的GUID(也就是说,它不必是“二进制兼容的”)。这个VB6 COM DLL在一些地方使用参数化属性,我知道C#不支持这些属性 当使用带有参数化属性的VB6 COM DLL时,C#中的引用将以
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_u3;”和“set_3;”,希望它能将它们视为一个单独的属性,但这也不起作用。其中一个在VB6中生成此错误:“未定义Property let过程,Property get过程未返回对象”
我几乎可以肯定的是,应该有办法做到这一点,但我就是找不到。有人知道怎么做吗
更新:
我采纳了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属性,那么它将正确应用索引器属性
关于如何安排它,使它知道如何将参数应用于访问器的索引器,而不是试图将其应用于属性,有什么想法吗
还有,我怎么做a+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互操作或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);
}
}
}
这会让C#感到高兴,因为默认值是COM对象(dispid 0),而不是C#对象,所以C#希望返回refinfo访问器,而不是字符串。由于refinfo访问器可以强制为对象,因此没有编译器错误
在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
非常感谢本在这方面的帮助 C#可以创建索引属性,但这些属性必须使用具有索引器的帮助器类来实现。此方法适用于早期绑定的VB,但不适用于后期绑定的VB:
using System;
class MyClass {
protected string get_MyProperty(string Param1, string Param2)
{
return "foo: " + Param1 + "; bar: " + Param2;
}
protected void set_MyProperty(string Param1, string Param2, string NewValue)
{
// nop
}
// Helper class
public class MyPropertyAccessor {
readonly MyClass myclass;
internal MyPropertyAccessor(MyClass m){
myclass = m;
}
public string this [string param1, string param2]{
get {
return myclass.get_MyProperty(param1, param2);
}
set {
myclass.set_MyProperty(param1, param2, value);
}
}
}
public readonly MyPropertyAccessor MyProperty;
public MyClass(){
MyProperty = new MyPropertyAccessor(this);
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var mc = new MyClass();
Console.WriteLine(mc.MyProperty["a", "b"]);
}
}
这里有一个教程:
// VB Syntax: PropName could either be an indexed property or a function
varName = obj.PropName(index1).Value
obj.PropName(index1).Value = varName
// But if Value is the default property of obj.PropName(index1)
// this is equivalent:
varName = obj.PropName(index1)
obj.PropName(index1) = varName
这意味着,与其这样做,不如:
//Property => Object with Indexer
// C# syntax
obj.PropName[index1];
我们可以这样做:
// C# syntax
obj.PropName(index1).Value
下面是示例代码,只有一个参数
class HasIndexedProperty {
protected string get_PropertyName(int index1){
// replace with your own implementation
return string.Format("PropertyName: {0}", index1);
}
protected void set_PropertyName(int index1, string v){
// this is an example - put your implementation here
}
// This line provides the indexed property name as a function.
public string PropertyName(int index1){
return new HasIndexedProperty_PropertyName(this, index1);
}
public class HasIndexedProperty_PropertyName{
protected HasIndexedProperty _owner;
protected int _index1;
internal HasIndexedProperty_PropertyName(
HasIndexedProperty owner, int index1){
_owner = owner; _index1 = index1;
}
// This line makes the property Value the default
[DispId(0)]
public string Value{
get {
return _owner.get_PropertyName(_index1);
}
set {
_owner.set_PropertyName(_index1, value);
}
}
}
}
局限性
限制在于,要工作,这取决于在结果强制为非对象类型的上下文中进行的调用。比如说
varName = obj.PropName(99)
由于未使用Set
关键字,VB知道它必须获取默认属性才能在此处使用
同样,当传递给以
varName = obj.PropName(99)
interface ISomething : IDispatch {
[id(0x68030001), propget]
HRESULT IndexedProp(
[in, out] BSTR* a, // Index 1
[in, out] BSTR* b, // Index 2
[out, retval] BSTR* );
[id(0x68030001), propput]
HRESULT IndexedProp(
[in, out] BSTR* a, // Index 1
[in, out] BSTR* b, // Index 2
[in, out] BSTR* );
[id(0x68030000), propget]
HRESULT PlainProp(
[out, retval] BSTR* );
[id(0x68030000), propput]
HRESULT PlainProp(
[in, out] BSTR* );
};