VB.Net的变通方法 - 属性的ByRef参数

时间:2009-03-19 16:32:11

标签: .net vb.net vb6 properties

我有一个遗留的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 says VB.Net不允许这样的参数。

有什么办法可以在VB.Net中实现这样的属性吗?

3 个答案:

答案 0 :(得分:2)

围绕旧接口创建一个新接口,该接口具有正确实现的属性。您不需要原始源,您可以从VB6引用它并创建一个新的ActiveX DLL / OCX来完成这项工作。我意识到有点痛苦。

我自己在转换项目中遇到了这个问题,并且必须回到旧的VB6代码并确保所有属性参数都声明为ByVal。问题是我们不得不等待VB6代码中的主要版本更改(当我们打破二进制兼容性时)。做使新DLL与旧DLL不兼容。

答案 1 :(得分:1)

如果您无法更改旧版COM组件的源代码,则必须解决此问题。一种可能性是创建一个实现IPasswordCallback的新VB6组件,它通过回调您的.NET代码来完成获取密码的实际工作。这样,VB6代码可以处理ByRef参数,.NET代码也不会看到它。

使其工作的大部分代码都是VB6代码,您可以将其放入一个新的ActiveX DLL项目并从.NET项目中引用。

以下是所需的不同类和接口的概述:

  • ByValPasswordCallbackWrapper(VB6):此类是一个包装类,它隐藏.NET代码中的ByRef参数。当遗留COM组件调用此类的Password属性时,此类将调用将执行实际回调工作的帮助程序类(用.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

如何使用此代码

将上述代码编译成ActiveX DLL后,从.NET项目中添加对它的引用。

接下来,将您已放入IPasswordCallback实现的.NET代码放入实现IPasswordCallbackProvider的新类中。请注意,回调参数(ownerNeededisResultValid)会传递到PasswordCallbackArgs对象中的提供者类,因此您必须使用args.ownerNeededargs.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对象。

使用上面的示例,并假设您有一个名为LegacyComObject的旧COM组件的实例,您可以执行以下操作来设置回调:

' 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实现了传统COM对象期望接收的IPasswordCallback接口。 ByValPasswordCallbackWrapper反过来提供了一种通过IPasswordCallbackProvider接口挂钩回调过程的方法。 IPasswordCallbackProvider COM接口与.NET兼容,因此您可以使用VB.NET编写实现类。 ByValPasswordCallbackWrapper调用您的IPasswordCallbackProvider来完成获取密码的工作,并确保将值重新放回ByRef参数。

答案 2 :(得分:1)

所有答案都非常有用,但他们都建议我在VB6中创建这个回调重定向器,这不太方便,因为我可能不得不用客户端应用程序重新分发VB6运行时。

这是解决方案的扩展版本。重定向器可以用C#编写,因为C#不会抱怨传递ref。它可以实现为类库并作为.dll程序集分发。这样,任何安装了.Net Framework的人都可以使用这样的重定向器。那些使用VB.Net的人肯定安装了.Net Framework。