4

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.

CSDev
  • 3,177
  • 6
  • 19
  • 37
Chris J
  • 30,688
  • 6
  • 69
  • 111
  • I wouldn't be converting line by line. The Net Library has better interfaces to databases than the Recordset method. What database are you using? – jdweng Nov 01 '16 at 10:36
  • The database is SQL Server and yes we could use SqlClient. However we have a large VB6 tax, so cannot move everything in one hit. So we're attacking the legacy module by module. The lower-level stuff needs COM compatibility with existing VB6 objects, whilst other bits of the legacy stack will be replaced completely removing that aspect of the COM legacy. I'm fully aware I don't want a line-by-line conversion, however, I want to find out what's possible and what isn't so that an actual migration path can be mapped out. – Chris J Nov 01 '16 at 10:43
  • Changing the interface library won't change the format of the database. I would use SQLClient in the c# code. – jdweng Nov 01 '16 at 12:12
  • 1
    @jdweng - then I'd have to marshal a DataSet to an ADO recordset so that the class is still usable by legacy ASP and unchanged COM objects. I figured that rather than bugger about trying to map between ADO, .NET and SqlType, then iterating over set dataset (as no library function exists, I'd have to roll my own: I started doing this - things don't map 1:1 simply), it's much simpler to call the existing ADO functionality. If there wasn't a requirement to support legacy code with this, then your suggestion would make sense. – Chris J Nov 01 '16 at 14:25
  • VS has both oledb and odbc for legacy applications. Go to www.ConnectionStrings.com is you have any questions about the connection strings. – jdweng Nov 01 '16 at 14:42
  • I'm not sure I understand your ultimate goal. If the goal is to have a 100% same IDL between VB and C#, I don't think it's possible. But is this really what you need? – Simon Mourier Nov 01 '16 at 16:48
  • @Simon - see (lengthy) edit. – Chris J Nov 02 '16 at 08:21

1 Answers1

0

Use the Type Library Importer (Tlbimp.exe) to generate an interop assembly, and then decompile the generated assembly with Redgate Reflector or a like tool. Reflector (at least) will generate interfaces correctly decorated with their requisite interop attributes, and the code can be copy-and-pasted into your own source files.

R.J. Dunnill
  • 2,049
  • 3
  • 10
  • 21