0

I am very new to C# and C/C++. I wrote a code in C that stored 2 variables in an array and return the array. Using the P/invoke method I created a DLL library of this C function and imported the library to C#. While calling the array, I am unable to get the array from the C#. Need your kind help. here is C code

#include <stdio.h>

extern __declspec(dllexport) int* sum();
int* sum()
{
    int a = 50, b = 80, sum, neg;
    sum = a + b;
    neg = b - a;
    int arr[2] = { sum, neg };
    return arr;
}

Now the C# code is...

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication
{
    class Program
    {
        [DllImport("Project2.dll")]
        unsafe public static extern int* sum();
        static void Main(string[] args)
        {
            unsafe
            {
                int* array2 = sum();

                Console.WriteLine("Value: " + array2[0]);
            }
            
        }
    }
}

Please let me know how can I get the array.

  • Returning an array in C or C++ is more complicated than that. You declare that array as a local variable within `sum`. It doesn't exist when `sum` exits – Flydog57 Dec 24 '21 at 19:39
  • 1
    Looks like a duplicate of [Marshal C++ int array to C#](https://stackoverflow.com/q/3776485/3744182) and/or [Return array of integers from cross-platform DLL](https://stackoverflow.com/q/7679522), agree? Though if you really only have two integers to return, it might be easy to marshal a struct with those two values. See e.g. [Marshaling byval C-structure as return value in C#](https://stackoverflow.com/q/4845128/3744182). – dbc Dec 24 '21 at 20:25

1 Answers1

2

There are multiple ways to return arrays, but in all cases you must solve two problems:

  • be able to tell .NET the count of items in the array;
  • use a common memory allocator for the native and .NET Size. The allocator of choice is the COM allocator as it's known to .NET;

Here are two examples using .NET safe code:

Native code 1:

extern __declspec(dllexport) SAFEARRAY * sumSafeArray()
{
    int a = 50, b = 80, sum, neg;
    sum = a + b;
    neg = b - a;
    int arr[2] = { sum, neg };

    // SAFEARRAY is a OLE/COM automation type well known to Windows and .NET
    // (note it implicitly uses the COM allocator too)
    SAFEARRAY* psa = SafeArrayCreateVector(VT_I4, 0, 2);
    void* data;
    SafeArrayAccessData(psa, &data);
    CopyMemory(data, arr, sizeof(arr));
    SafeArrayUnaccessData(psa);
    return psa;
}

Managed code 1:

[DllImport("mydll")]
[return: MarshalAs(UnmanagedType.SafeArray)]
private static extern int[] sumSafeArray();

foreach (var i in sumSafeArray())
{
    Console.WriteLine(i);
}

Native code 2:

extern __declspec(dllexport) void sumArray(int** out, int* count)
{
    int a = 50, b = 80, sum, neg;
    sum = a + b;
    neg = b - a;
    int arr[2] = { sum, neg };

    // always use COM allocator
    *out = (int*)CoTaskMemAlloc(sizeof(arr));
    CopyMemory(*out, arr, sizeof(arr));
    *count = sizeof(arr) / sizeof(int);
}

Managed code 2:

[DllImport("mydll")]
private static extern void sumArray(out IntPtr array, out int size);

sumArray(out var ptr, out var size);
var ints = new int[size];
Marshal.Copy(ptr, ints, 0, size);
Marshal.FreeCoTaskMem(ptr);
foreach (var i in ints)
{
    Console.WriteLine(i);
}

And one example using .NET unsafe code:

Native code:

extern __declspec(dllexport) int* sum(int* count)
{
    int a = 50, b = 80, sum, neg;
    sum = a + b;
    neg = b - a;
    int arr[2] = { sum, neg };

    // always use COM allocator
    int* out = (int*)CoTaskMemAlloc(sizeof(arr));
    CopyMemory(out, arr, sizeof(arr));
    *count = sizeof(arr) / sizeof(int);
    return out;
}

Managed code:

[DllImport("mydll")]
private unsafe static extern int* sum(out int count);

var ints = sum(out var count);
for (var i = 0; i < count; i++)
{
    Console.WriteLine(*ints);
    ints++;
}

You could also use another allocator (new/malloc/custom, etc.) but in this case you would need to expose/export to .NET the corresponding "free" method and do the free yourself when needed.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Great answer, but you're missing `finally`s for the `Marshal.FreeCoTaskMem` – Charlieface Dec 25 '21 at 19:29
  • @Charlieface - I'm not missing anything, the code doesn't handle any possible error. – Simon Mourier Dec 25 '21 at 21:52
  • There is not necessarily any need to *handle* any errors with `catch` blocks, the calling code can do that. But you must be able to safely back out code without memory leaks, and for that you need a `finally` block. This is the equivalent of a `using` block: no error *handling*, just ensuring that things are disposed properly. Or putting it another way: if you don't care enough to ensure safe memory release, why care which allocater you use? Just allocate from wherever and let it leak – Charlieface Dec 26 '21 at 00:33
  • @Simon Mourier Great Answer, But I am using C language for native code. Whenever I put your code in C language it shows me this error. "ALT Requires C++ Compilation (Use .CPP suffix) atlbase.h" But I cannot use C++ suffix as my functions are written in C language and also I am very new to C++/C programming. – suleman Ali kazmi Dec 27 '21 at 08:58
  • @sulemanAlikazmi - My code (note I had left an extern "C" in my answer that I've just removed) doesn't use any C++ and it doesn't use ATL either. It just needs "windows.h" to compile. It looks like your code is using ATL, but I can't say. – Simon Mourier Dec 27 '21 at 09:58
  • @SimonMourier Thank you so much. I got my answer and my code is working after your help. Your this kind act is unforgettable. Thanks Again. – suleman Ali kazmi Dec 27 '21 at 15:58
  • @SimonMourier one more favor, please. Can you please recommend me online resources (Online Courses, Tutorials, etc.) which help me to resolve these types of technical problems? Thanks – suleman Ali kazmi Dec 27 '21 at 16:11
  • @sulemanAlikazmi - this site + official documentation from Microsoft is the best you can find. – Simon Mourier Dec 27 '21 at 16:14