0

I've got a dll that is used by vba code in an Excel worksheet. Things work fine - except there's one function that needs a SAFEARRAY.

Abbreviated Excel VBA Code looks like this:

Private sub ExcelFoo()
    Dim v(115,17)
    ...stuff ...
    MyFunctionCollectionLib.SetMatrix v

This calls a Dll, written in c++. The c++ function looks like this:

void _stdcall SetMatrix(SAFEARRAY **psaMatrix)
   ... store matrix somewhere ...

It all works fine in Excel.

Now I want to use the same function in C#. The object browser shows the following information about the function (in the dll):

Sub SetMatrix(psaMatrix() As Double)

So I wrote a Dll wrapper as follows:

// void _stdcall SetMatrix(SAFEARRAY **psaMatrix)
[DllImport( "MyFunctionCollectionLib.dll", CallingConvention = CallingConvention.StdCall )]
public extern static void SetMatrix(ref double[,] psaMatrix);

An then in C# I attempt to use it as follows:

public void ReadDefaultValues()
{
    double[,] tMatrix = _TestData.ReadDefaultValues();
    DllWrapper.DllWrapper.SetMatrix(ref tMatrix);

The debugger shows me that tMatrix is originally a double[115, 17] - as defined, but gets it gets changed to a double[1] after the SetMatrix() call.

There are a few other oddities which make me doubt I'm doing this correctly. I really don't know anything about Excel/vba and all that. So I'm getting caught by lots of "gotchas" since I'm really out of my field.

Is there documentation about SAFEARRAY that is easilly available? (It doesn't have to be easilly readable) My google searches bring up lots of somewhat obscure pages about COM and marshalling. These pages don't appear to directly address what I'm looking for. The Microsoft documentation describes the SAFEARRAY struct, but doesn't appear to offer any elaboration on usage.

=======================================================================

If I understand the suggested documentation, what I need to do is change my wrapper:

// void _stdcall SetMatrix(SAFEARRAY **psaMatrix)
[DllImport( "MyFunctionCollectionLib.dll", CallingConvention = CallingConvention.StdCall )]
[In][Out][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] double[,] pArrayOfDouble

For the wrapper there is a VT_DECIMAL, but no VT_DOUBLE type - so I assume I should use VT_VARIANT as the type.

The code above fails (copied here):

    double[,] tMatrix = _TestData.ReadDefaultValues();
    DllWrapper.DllWrapper.SetMatrix(ref tMatrix);

when I try with var (is this the c# variant?) it also fails

    var tMatrix = _TestData.ReadDefaultValues();
    DllWrapper.DllWrapper.SetMatrix(ref tMatrix);

The error message in both cases is: specified array was not the expected type.

Where do I find the expected type? The stack trace Shows:

   at MyFunctionCOllection.MyFunctionCOllectionWrapper.SetMatrix(Double[,] pArrayOfDouble)
infowanna
  • 169
  • 11
  • [Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 1.](https://limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/) – Phil1970 Nov 19 '16 at 20:50
  • [Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 2.](https://limbioliong.wordpress.com/2012/02/29/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-2/) – Phil1970 Nov 19 '16 at 20:51
  • [Marshalling arrays of VARIANT using P/Invoke](http://stackoverflow.com/questions/18716314/marshalling-arrays-of-variant-using-p-invoke) – Phil1970 Nov 19 '16 at 20:53
  • I added a few links above... Another possibility is to use C++/CLI between VBA and C# if you are able to make it work in C++ but not in C#. This allows you to do anything you want to forward the call to C#. – Phil1970 Nov 19 '16 at 20:56
  • I'm not entirely certain I was clear. My end goal is to replace the Excel worksheet with a c# application. The c# needs to use the existing dll that Excel is currently using. Will review your links. – infowanna Nov 19 '16 at 21:03
  • Well, you have essentially 3 ways to use legacy code in C#. P/Invoke, COM and C++/CLI mixed mode code... – Phil1970 Nov 19 '16 at 21:14
  • @Phil1970 - I must not have understood something in the dox you pointed out - it Looks like it requires a change in the Interface Definition. But then it appears sending doubles is not so straightforward. Do I need an extra step to "pack" them in a Variant? – infowanna Nov 21 '16 at 11:15

1 Answers1

0

ok, apparently it wanted redefining the c# "wrapper" or whatever it's called:

[DllImport( "MyFunctionCollectionLib.dll", CallingConvention = CallingConvention.StdCall )]
[In][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_R8)] double[,] pArrayOfDouble  

Then I can code it without a ref:

var tMatrix = _TestData.ReadDefaultValues();
DllWrapper.DllWrapper.SetMatrix(tMatrix);

I didn't see VT_R8 as defining doubles. Now at least the thing stopped complaining about datatypes and such.

infowanna
  • 169
  • 11