I'm looking at migrating some legacy COM code written in VB6 to .NET, however this needs to generate a typelib that's fairly close to the original.
I've bumped into a problem passing in an array of parameters when early bound to other VB6 components. In the original VB6, the signature looked like this:
Public Function ExecSPReturnRS(ByVal strProcAs String, _
ParamArray varParams() As Variant) As Recordset
and generated MIDL that looks like this:
[id(0x60030009), vararg]
HRESULT ExecSPReturnRS([in] BSTR strProc,
[in, out] SAFEARRAY(VARIANT)* varParams,
[out, retval] _Recordset** );
With C#, I can't determine the right declaration to generate the same MIDL. I'm either missing the vararg
declaration, or the varParams parameter is declared as SAFEARRAY(VARIANT)
rather than SAFEARRAY(VARIANT)*
.
So if in C# I declare as:
Recordset ExecSPReturnRS(string storedProc, ref object[] arguments);
...I get SAFEARRAY(VARIANT)*
, but no vararg
. However if I declare as
Recordset ExecSPReturnRS(string storedProc, params object[] arguments);
...then I get the vararg
but the SAFEARRAY is not declared as by reference.
I expect that MarshalAsAttribute
may be the way to go, but the best I've been able to come up with so far is:
Recordset ExecSPReturnRS(string storedProc,
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
object[] arguments);
But this gives me the same MIDL as if I declared as params
without the MarshalAs.
In essence, I want the MIDL to reflect as if I'd specified both ref
and params
, which is illegal under C#. What's the incantation I'm missing?
Edit: it seems there's some confusion on what the ultimate goal is. In essence we have an application that consists of many legacy VB6 components. In order to remove the legacy debt, we need to be able to move the VB components over to .NET bit by bit. Where that component is a dependency on other components, the .NET needs to be usable with existing VB6 and classic ASP, ideally with no code change. Some legacy components will be completely refactored and end up with no COM dependency. Many of the VB6 components use early binding.
As it stands at the moment, when I use the param
declaration in .NET to take the place of a VB6 ParamArray, building VB6 components against that object result in a Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic error in VB. If I use ref
instead, I get the build error Type mismatch: array or user-defined type expected.
Calls in VB6 to the particular component I'm looking at look something like:
Set rs = dbconn.ExecSPReturnRS("dbo.StoredProc", _
Array("@param1", value), _
Array("@param2", value))
I've assumed the issue is a typelib issue, as my understanding is that VB will be using that at build time to validate the calls when early binding. However I've since discovered that if I late bind everything, then VB builds are successful and calls appear to work. This however will require source code changes of the legacy VB6, and other than a loss of auto-complete when late binding, I expect I'm also losing build-time validation of parameters. But it might be the best option we have.
One final note: currently this is research/PoC to find out what's required and what isn't. Ultimately, this is not intended to be a line-by-line conversion of all objects. Some objects will need to expose a COM interface, others won't, and others will be refactored or dropped. However there will be places we will need to sustain a COM compatible interface for backwards compatibility, at least for the short to medium term whilst the migration proceeds.