COM Interop and Arrays

Some of you know that what I am doing at work involves migrating an existing Client-Server application, written predominently in VB6, to be dominated by .NET using an enterprise architecture. Rather than toss out the existing code and re-write, we are driving the changes we are making through the requests for additional business value. As new features are requested and bugs are being fixed, we are swapping out the existing items to follow these patterns. Often it even still remains a mix of existing VB6 functionality behind the scenes combined with the new functionality being requested (due to time constraints and not wanting to re-invent the wheel). To decrease our dependency on VB6, mediate the risk of eventually switching to a .NET client (either WinForms or SmartClient), and to increase the surface area for integration with third party applications, we are using the Passive View Pattern and having our controller access information via a web services proxy (soon to be WCF Web Services). I have written a bit about this previously.
 
One of the difficult things we have had to address is how to have our controller pass arrays of values to our view (to, say, populate a drop-down control).
 
PROBLEM
C#
using System.Runtime.InteropServices;
[Guid("68BA0314-67AC-410b-A458-D106C625A1D2")]
public interface IClientInterface {
    string[] Strings { get; set; }
}
 
The IL code that this translates to is:
 
get_Strings : string[]()
set_Strings : void(string[])
 
When you generate the type library (tlb) from your assembly, you get the following:
 
interface IClientInterface : IDispatch {
    [id(0x60020000), propget]
    HRESULT Strings([out, retval] SAFEARRAY(BSTR)* pRetVal);
    [id(0x60020000), propput]
    HRESULT Strings([in] SAFEARRAY(BSTR) pRetVal);
};
 
VB6
Option Explicit
Implements IClientInterface
Private stringArray() As String
 
Public Property Get IClientInterface_Strings() As String()
    IClientInterface_Strings = stringArray
End Property
 
Public Property Let IClientInterface_Strings(value() As String)
     stringArray = value
End Property
 
This, though, causes the VB6 compiler to crash when you are compiling. All arrays must be explicitly passed by reference.
 
EXPLANATION
From the MSDN article Default Marshaling for Arrays:
 
"Although you can apply the size_is or length_is attributes to an array in Interface Definition Language (IDL) source to convey the size to a client, the Microsoft Interface Definition Language (MIDL) compiler does not propagate that information to the type library. Without knowing the size, the interop marshaling service cannot marshal the array elements. Consequently, variable-length arrays are imported as reference arguments."
 
What it’s saying is that the generated IL does not look like this (that would work): 
 
get_Strings : string[]()
set_Strings : void(ref string[])
 
 
WORK-AROUND
The easiest work-around to implement is to drop the properties all together and implement the code in the same way that the IL would translate it to (minus the underscores.. interop does not do well with underscores in method names)
 
C#
using System.Runtime.InteropServices;
[Guid("68BA0314-67AC-410b-A458-D106C625A1D2")]
public interface IClientInterface {     string[] GetStrings();
    void SetStrings(ref string[] values);
}
 
VB6
Option Explicit
Implements IClientInterface
Dim stringArray() As String
 
Public Sub IClientInterface_SetStrings(ByRef values() As String)
    stringArray = values
End Sub
 
Public Function IClientInterface_GetStrings() As String()
    IClientInterface_GetStrings = stringArray
End Function
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s