Обходной путь для VB.Net - параметр свойства ByRef - PullRequest
2 голосов
/ 19 марта 2009

У меня есть устаревший COM-компонент с интерфейсом, объявляющим свойство, подобное этому (нотация IDL):

interface IPasswordCallback : IUnknown {
    [propget] HRESULT Password( [in] VARIANT_BOOL ownerNeeded, [in, out]
        VARIANT_BOOL* isResultValid, [out, retval] BSTR* password );
}

Этот интерфейс реализован в вызывающем приложении, написанном на VB6, например:

Public Property Get IPasswordCallback_Password(ByVal ownerNeeded As Boolean,
    ByRef isResultValid As Boolean) As String
    'implementation cut
End Property

Все работает нормально, пока я не попытаюсь сделать то же самое в VB.Net. VB.Net отказывается компилировать код, в котором параметр свойства передается ByRef. MSDN сообщает VB.Net не допускает такие параметры.

Можно ли как-нибудь реализовать такое свойство в VB.Net?

Ответы [ 3 ]

2 голосов
/ 19 марта 2009

Создайте новый интерфейс вокруг старого интерфейса, у которого свойство правильно реализовано. Вам не нужен оригинальный источник, вы можете просто сослаться на него из VB6 и создать новую ActiveX DLL / OCX для выполнения этой работы. Немного боли я понимаю.

Я сам столкнулся с этим в своем проекте преобразования и должен был вернуться к старому коду VB6 и убедиться, что все параметры свойства были объявлены ByVal. Проблема заключалась в том, что нам пришлось ждать серьезного изменения версии в коде VB6 (когда мы нарушаем двоичную совместимость). Это делает новую DLL несовместимой со старой DLL.

1 голос
/ 20 марта 2009

Все ответы очень полезны, но все они предполагают, что я создаю этот перенаправитель обратного вызова в VB6, что не очень удобно, поскольку мне может потребоваться перераспределить среду выполнения VB6 с клиентским приложением.

Вот расширенная версия решения. Перенаправитель может быть написан на C #, так как C # не жалуется на прохождение через ref. Он может быть реализован как библиотека классов и распространяться как сборка .dll. Таким образом, любой, у кого установлен .Net Framework, может использовать такой перенаправитель. А те, кто использует VB.Net, наверняка установили .Net Framework.

1 голос
/ 20 марта 2009

Если вы не можете изменить исходный код для устаревшего компонента COM, вам придется обойти его. Одной из возможностей является создание нового компонента VB6, который реализует IPasswordCallback и который выполняет фактическую работу по получению пароля путем обратного вызова вашего кода .NET. Таким образом, код VB6 может работать с параметром ByRef, а код .NET его не увидит.

Большая часть кода для его работы будет представлять собой код VB6, который вы можете вставить в новый проект ActiveX DLL и использовать в своем проекте .NET.

Вот схема различных необходимых классов и интерфейсов:

  • Класс ByValPasswordCallbackWrapper (VB6): Этот класс является классом-оболочкой, который скрывает параметр ByRef от кода .NET. Когда свойство Password этого класса вызывается устаревшим компонентом COM, этот класс будет вызывать вспомогательный класс (написанный на .NET), который будет выполнять реальную функцию обратного вызова. Затем класс VB6 возьмет результаты из вспомогательного класса и вернет их устаревшему компоненту COM.

  • Класс PasswordCallbackArgs (VB6): Этот класс используется для передачи параметров из вызова в IPasswordCall_Password в вспомогательный класс .NET, который будет выполнять реальную работу.

  • Интерфейс IPasswordCallbackProvider (VB6): это интерфейс, который будет реализован в вашем .NET-коде вместо непосредственной реализации IPasswordCallback.

Код

Ниже приведен список кодов для каждого из компонентов VB6, упомянутых выше. Вы можете добавить этот код в новый проект ActiveX DLL и скомпилировать его для использования из своего кода .NET. Каждый файл указан отдельно. Кроме того, убедитесь, что для каждого из этих классов для свойства Instancing установлено значение "5-MultiUse".


Файл : PasswordCallbackArgs.cls

'Used to pass arguments around'

Public OwnerNeeded As Boolean
Public IsValidResult As Boolean

Файл : IPasswordCallbackProvider.cls

' This is the interface that your .NET                  '
' class should implement (instead of IPasswordCallback) '

Public Function GetPassword(ByVal args As PasswordCallbackArgs)

End Function

Файл : ByValPasswordCallbackWrapper.cls

Implements IPasswordCallback

Private m_callbackProvider As IPasswordCallbackProvider

Public Property Set CallbackProvider(ByVal callbackProvider As IPasswordCallbackProvider)

   Set m_callbackProvider = callbackProvider

End Property

Private Property Get IPasswordCallback_Password( _
   ByVal ownerNeeded As Boolean, _
   ByRef isResultValid As Boolean) As String

   IPasswordCallback_Password = DoGetPassword(ownerNeeded, isResultValid)

End Property

Private Function DoGetPassword(
   ByVal ownerNeeded As Boolean, _
   ByRef isResultValid As Boolean) As String

   If m_callbackProvider Is Nothing Then
      Err.Raise 5,,"No callback provider. DoGetPassword failed."
   End If

   'Wrap the arguments in a PasswordCallbackArgs object.'
   'Do not need to fill args.IsResultValid here - the callback provider will do that'

   Dim args As New PasswordCallbackArgs
   args.OwnerNeeded = ownerNeeded

   'Get the password and a value to put back into our ByRef isResultValid'
   DoGetPassword = m_callbackProvider.GetPassword(args)
   isResultValid = args.IsResultValid

End Sub

Как использовать этот код

Как только приведенный выше код был скомпилирован в DLL ActiveX, добавьте ссылку на него из вашего проекта .NET.

Затем поместите код .NET, который вы поместили бы в реализацию IPasswordCallback, в новый класс, реализующий IPasswordCallbackProvider. Помните, что параметры обратного вызова (ownerNeeded и isResultValid) передаются вашему классу провайдера в объекте PasswordCallbackArgs, поэтому вам придется использовать args.ownerNeeded и args.isResultValid в вашем классе .NET для ссылки на них.

Вот класс заглушки для начинающих:

Файл : MyPasswordCallbackProvider.vb (VB.NET)

' A stub implementation of an IPasswordCallbackProvider '

Public Class MyPasswordCallbackProvider Implements IPasswordCallbackProvider

   Public Function GetPassword(PasswordCallbackArgs args) As String _
      Implements IPasswordCallbackProvider.Password

      Dim password As String = ""
      Dim resultWasValid As Boolean

      If args.OwnerNeeded Then
         'do stuff'
      Else
         'do other stuff'
      End If

      'do even more stuff'

      'set whether the result was valid or not'
      args.ResultValid = resultWasValid

      Return password

   End Property

End Class

Чтобы передать действительный IPasswordCallback в ваш устаревший COM-объект, вам нужно будет создать ByValPasswordCallbackWrapepr, установить его свойство CallbackProvider, а затем передать объект-оболочку в устаревший COM-объект.

Используя приведенный выше пример и предполагая, что у вас есть экземпляр вашего устаревшего COM-компонента с именем LegacyComObject, вы должны сделать что-то вроде следующего для настройки обратного вызова:

' Create the callback wrapper '
ByValPasswordCallbackWrapper wrapper = New ByValPasswordCallbackWrapper()

' Tell the wrapper to call our custom IPasswordCallbackProvider '
wrapper.CallbackProvider = New MyPasswordCallbackProvider()

''" Pass the call back wrapper to the legacy COM object ''"
''" (not sure how this is done in your scenario. ''"
''" I'm just pretending it's a property since I don't know... "''
LegacyComObject.PasswordCallback = wrapper

Почему это работает

Это работает, потому что ByValPasswordCallbackWrapper реализует интерфейс IPasswordCallback, который ожидает получить устаревший COM-объект. ByValPasswordCallbackWrapper, в свою очередь, предоставляет возможность подключиться к процессу обратного вызова через интерфейс IPasswordCallbackProvider. Интерфейс IPasswordCallbackProvider COM совместим с .NET, поэтому вы можете написать реализующий класс, используя VB.NET. ByValPasswordCallbackWrapper вызывает ваш IPasswordCallbackProvider для получения пароля и гарантирует возврат значения в параметр ByRef.

...