0

I have this function declared in legacy VB6 code and I'm trying to move the calling code into .NET. So I need to translate the following into C#.

Private Declare Sub FooFunc "foo.dll" _
                      (ByRef A As Long, _
                      ByVal B As String, _
                      ByRef C As Long, _
                      ByRef D As Integer,)

I don't have documentation on foo.dll. All I have is the current calling code in VB6 that was written by someone long gone:

Dim a as Long
Dim b as String
Dim c(2001) as Integer
Dim d(2001) as Long

c(0) = itemCount
d(0) = itemCount
For i = 1 To itemCount
  c(i) = ...
Next

Call FooFunc(a, b, c(0), d(0))

DoSomethingWithD(d)
...
  • parameter a may be modified by the dll so I think I need the out keyword
  • parameter b is not modified
  • parameter c is an array of input values and not modified
  • parameter d is a preallocated array that will get output values. The confusing part is that the first item is passed in, but I know the array is filled up with values when called from VB6 like this

I'm thinking I need something like the following in C# but I get a AccessViolationException when I call the function.

[DllImport("foo.dll")]
public static extern void FooFunc(out int a, string b, int[] c, short[] d);

I've tried a few other variations and looking at more explanations on SO but nothing quite matches the odd way the VB6 code passes the arrays and I don't know if that is tripping things up or not. Maybe that is a way to get it to work like a C-style array? And filling the first would just be an odd convention used by whoever made foo.dll I imagine.

Edit 1:

It seems like FooFunc was meant to take in C arrays originally, but C# and even VB6 would pass in SafeArrays which have bytes for rank and bounds before the actual array data. If that's true, then passing by reference the first element makes sense because then FooFunc can read the rest of the data.

However, because unmanaged code can't modify managed arrays directly, I need some way of allocating an unmanaged array that can be modified by unmanaged code and passing a pointer to the first element as a parameter. Is there a way to do that?

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Ben Zuill-Smith
  • 3,504
  • 3
  • 25
  • 44
  • I'm not sure that you want to use `out`, but rather `ref`. – Jonathon Chase Sep 17 '18 at 23:28
  • I would expect a native method that takes an array to also take some sort of parameter for the size (since C arrays are just pointers). Is that what A is? Are the actual parameter names known? It might be helpful to tell us what they actually are. It might also be helpful to have the exact VB calling code (no omissions or name changes), and the way you are calling it in C#. – Dave M Sep 18 '18 at 00:17
  • The `a` parameter should be `ref int` and initialized. The `b` looks right. Marshaling the arrays is going to require serious P/Invoke kicking around to find the right combination of attributes, etc. to get it right. Sorry, my Adam Nathan ".NET & COM" book is buried in a box somewhere. Search for something like "p/invoke marshal array integer" on the internet and look at http://pinvoke.net for possible examples – Flydog57 Sep 18 '18 at 01:08
  • 2
    The easiest way might be to convert the vb6 to vb.net, and then wrap the vb.net code in it's own project you can use in the same visual studio solution as the rest of your C# – Joel Coehoorn Sep 18 '18 at 02:22
  • Take a look at https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-different-types-of-arrays – Flydog57 Sep 18 '18 at 04:25
  • @DaveM the first item of the array is set to the length of the actual data. @Flydog57 thanks for the docs. Seems I would need an [in,out] array in some places if the native code was written properly. The real code has a ton of parameters and is very convoluted. There are actually multiple arrays of data that are filled by the function and several `long` type parameters that are set as well. The post above gets to the heart of the problem. VB6 doesn't seem to care that the arrays are directly modified even though only a single index was referenced. I imagine .NET will not be so lenient. – Ben Zuill-Smith Sep 18 '18 at 05:43
  • 1
    I think I'm onto something and I've updated the question with an "Edit 1" section – Ben Zuill-Smith Sep 18 '18 at 06:26
  • Your "edit 1" is correct – MarkJ Sep 18 '18 at 16:28
  • @MarkJ I'm glad I'm on the right track but still can't figure out quite how to create and marshal the parameters over so I don't get this access violation – Ben Zuill-Smith Sep 18 '18 at 21:57
  • I don't have much experience of .Net interop. I would think it is a common requirement to call C DLLs that expect arrays e.g. Win32 API. My C is rusty but I think the C prototype for your FooFunc is `void FooFunc(int *a, char b[], int c[], long d[]);`. I believe I read once about an automatic tool that will write .Net PInvoke code from a C prototype? [Try here](https://stackoverflow.com/questions/6319650/is-there-a-tool-that-generates-p-invoke-signatures-for-arbitrary-unmanaged-dll) ? Anyone else reading this question and know more about .Net Pinvoke? (looks around hopefully) – MarkJ Sep 20 '18 at 16:12

1 Answers1

1

I think you are misreading the code. The call to FooFunc doesn’t pass an array, it indexes into a couple of arrays and passes a reference to the indexed values so they can be changed.

[DllImport("foo.dll")]
public static extern void FooFunc(ref int a, string b, ref int c, ref short d);
jmoreno
  • 12,752
  • 4
  • 60
  • 91
  • 1
    I know it's strange, but the function definitely modifies the array, not just a single index of the array. I imagine a pointer to the first item works because arrays are stored in contiguous memory and so if you know the address of the first index, you know the address the array (I think). It's very strange code, I know. – Ben Zuill-Smith Sep 18 '18 at 05:11