我有一个遗留的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中实现这样的属性吗?
答案 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
的新类中。请注意,回调参数(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对象。
使用上面的示例,并假设您有一个名为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。