Как настроить свойства класса С# COM с параметризованными свойствами из VB6

Я изучил этот вопрос совсем немного, и хотя я нашел много о С# и параметризованных свойствах (с использованием индексатора - единственный способ), я не нашел реального ответа на мой вопрос.

Во-первых, что я пытаюсь сделать:

У меня есть существующая COM-библиотека, написанная на VB6, и я пытаюсь создать С# DLL, которая использует аналогичный интерфейс. Я говорю так же, потому что VB6 DLL используется только с поздним связыванием, поэтому ему не нужно иметь одинаковые идентификаторы GUID для вызовов (т.е. Он не должен быть "бинарным совместимым" ). Эта библиотека VB6 COM DLL использует параметризованные свойства в нескольких местах, которые, как я знаю, не поддерживаются С#.

При использовании VB6 COM DLL с параметризованными свойствами ссылка в С# будет обращаться к ним как к методам в форме "get_PropName" и "set_PropName". Тем не менее, я иду в противоположном направлении: я не пытаюсь получить доступ к VB6 DLL в С#, я пытаюсь сделать С# COM DLL совместимым с VB6 DLL.

Итак, вопрос: Как создать методы getter и setter в С# COM DLL, которые отображаются как однопараметрированное свойство при использовании 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

Эквивалент в С# будет примерно таким:

public string get_MyProperty(string Param1, string Param2)
{
}

public void set_MyProperty(string Param1, string Param2, ref string NewValue)
{
}

Итак, как я могу заставить эти методы С# выглядеть как (и функции вроде) одно параметризованное свойство при использовании VB6?

Я попытался создать два метода, один из которых называется "set_PropName", а другой "get_PropName", надеясь, что он выяснит, что они должны быть единственным параметризованным свойством при использовании VB6, но это не сработало; они появились как два разных вызова метода от VB6.

Я подумал, что, возможно, некоторые атрибуты должны применяться к ним на С#, чтобы их можно было рассматривать как одно параметризованное свойство в COM и VB6, но я не мог найти подходящего.

Я также попытался перегрузить методы, удалив "get_" и "set_", надеясь, что он увидит их как одно свойство, но это тоже не сработало. Это вызвало эту ошибку в VB6: "Property let procedure not defined и свойство get procedure не возвратили объект".

Я почти уверен, что должен быть способ сделать это, но я просто не могу найти его. Кто-нибудь знает, как это сделать?

Update:

Я взял совет Бена и добавил класс accessor, чтобы узнать, может ли это решить мою проблему. Однако теперь я столкнулся с другой проблемой...

Во-первых, здесь используется интерфейс 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; }

}

Здесь класс accessor:

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);
        }
    }
}

Это отлично работает для "get". Однако, когда я пытаюсь установить значение,.NET переливается со следующей ошибкой:

Управляемый помощник по отладке "FatalExecutionEngineError" обнаружил проблема в "blahblah.exe".

Дополнительная информация: среда выполнения столкнулась с фатальной ошибкой. адрес ошибки был равен 0x734a60f4, в потоке 0x1694. Ошибка код 0xc0000005. Эта ошибка может быть ошибкой в ​​CLR или в небезопасные или не поддающиеся проверке части кода пользователя. Общие источники этого ошибка включает ошибки маршалинга пользователя для COM-interop или PInvoke, которые может повредить стек.

Я предполагаю, что проблема в том, что .NET попытался установить значение для метода, а не по умолчанию для свойства возвращаемого объекта или что-то подобное. Если я добавлю ".Value" к заданной строке, он отлично работает.

Обновление # 3: Успех!

Наконец-то я получил эту работу. Однако есть несколько вещей, которые нужно искать.

Во-первых, значение по умолчанию для accessor должно возвращать масштабирующее устройство, а не объект, например:

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);
    }

Это сделает С# счастливым, так как значением по умолчанию является COM-объект (dispid 0), а не С#, поэтому С# ожидает возврата 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

Большое спасибо Бен за помощь в этом!

+3
09 окт. '15 в 21:23
источник поделиться
2 ответа

С# могут выполнять индексированные свойства, но они должны быть реализованы с использованием вспомогательного класса с индексом. Этот метод будет работать с ранним 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 с ограниченной привязкой

Это обходное решение, в котором используются два факта о VB. Один из них заключается в том, что оператор индекса массива совпадает с оператором вызова функции - круглыми скобками (parens). Другим является то, что VB позволит нам опустить имя свойства по умолчанию.

Свойства только для чтения

Если свойство get-only, вам не нужно беспокоиться об этом. Просто используйте функцию, и это будет вести себя так же, как доступ к массиву для позднего кода.

Свойства чтения-записи

Используя приведенные выше факты, мы можем видеть, что они эквивалентны в VB

// 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 знает, что он должен получить свойство по умолчанию для использования здесь.

Опять же, при переходе к функции, которая принимает, например, строку, это будет работать. Внутренне VariantChangeType будет вызываться для преобразования объекта в правильный тип, который, если принудительное обращение к не-объекту получит доступ к свойству по умолчанию.

Проблема может возникать при передаче непосредственно в качестве параметра функции, которая принимает вариант как аргумент. В этом случае объект доступа будет передан. Как только объект используется в контексте, отличном от объекта (например, назначение или преобразование в строку), будет выбрано свойство по умолчанию. Однако это будет значение в момент его преобразования, а не время, когда он был первоначально доступен. Это может быть или не быть проблемой.

Однако эта проблема может быть решена с помощью кэша объекта accessor, возвращаемого значением, чтобы гарантировать, что это значение, как на момент создания аксессора.

+2
12 окт. '15 в 13:52
источник

Эта функция, которую вы ищете, обычно называется "индексированными свойствами". Вкус, который использует VB6, - это аромат, поддерживаемый интерфейсами COM.

Этот фрагмент IDL похож на то, что будет генерировать VB6, и показывает, что происходит под капотом:

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* );
};

IndexedProp - это свойство String, которое в качестве индексов принимает два параметра String. Контраст с PlainProp, который, конечно же, является неиндексированным обычным свойством.

К сожалению, С# имеет очень ограниченную поддержку индексированных свойств в стиле COM.

С# 4.0 поддерживает использование COM-объектов (написанных в другом месте), которые реализуют COM-интерфейс с индексированными свойствами. Это было добавлено для улучшения взаимодействия с серверами COM Automation, такими как Excel. Тем не менее, он не поддерживает объявление такого интерфейса или создание объекта, реализующего такой интерфейс COM, даже если его законно объявлено в другом месте.

Ben answer рассказывает вам, как создавать индексированные свойства в С#, или, по крайней мере, что-то, что приводит к эквивалентному синтаксису кода С#. Если вы просто хотите использовать синтаксический вкус при написании кода С#, это отлично работает. Но, конечно, это не индексированное свойство COM-стиля.

Это ограничение языка С#, а не платформы .NET. VB.NET поддерживает индексированные свойства COM, потому что у них есть мандат на замену VB6 и, следовательно, необходимо пройти лишнюю милю.

Если вам действительно нужны индексированные свойства COM, вы можете подумать о написании COM-версии своего объекта в VB.NET и переслать этот объект в вашу реализацию С#. Для меня это звучит очень много. Или переносите весь свой код на VB.NET. Это действительно зависит от того, насколько сильно вы этого хотите.

Ссылки

Но эта функция доступна только для COM-взаимодействия; вы не можете создавать свои собственные индексированные свойства в С# 4.0.

+2
13 окт. '15 в 2:10
источник

Посмотрите другие вопросы по меткам или Задайте вопрос