20

I can't seem to figure out how to return an array from an exported C++ DLL to my C# program. The only thing I've found from googling was using Marshal.Copy() to copy the array into a buffer but that doesn't give me the values I'm trying to return, I don't know what it's giving me.

Here's what I've been trying:

Exported function:

extern "C" __declspec(dllexport) int* Test() 
{
    int arr[] = {1,2,3,4,5};
    return arr;
}

C# portion:

    [DllImport("Dump.dll")]
    public extern static int[] test();

    static void Main(string[] args)
    {

        Console.WriteLine(test()[0]); 
        Console.ReadKey();


    }

I know the return type int[] is probably wrong because of the managed/unmanaged differences, I just have no idea where to go from here. I can't seem to find an answer for anything but returning character arrays to strings, not integer arrays.

I figured the reason the values I'm getting with Marshal.Copy are not the ones I'm returning is because the 'arr' array in the exported function gets deleted but I'm not 100% sure, if anyone can clear this up that would be great.

David
  • 243
  • 1
  • 3
  • 12
  • http://stackoverflow.com/questions/3776485/marshal-c-int-array-to-c-sharp This may help you – Sriram Sakthivel Jul 13 '13 at 21:50
  • 1
    [Not good.](http://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope) – chris Jul 13 '13 at 21:59
  • Thanks Chris, I was so caught up in this I forgot what I was doing. I'm going to change my code around a bit,this way I have my exported function take in the address to a buffer and fill the buffer from there. – David Jul 13 '13 at 22:16

2 Answers2

20

I have implemented the solution Sriram has proposed. In case someone wants it here it is.

In C++ you create a DLL with this code:

extern "C" __declspec(dllexport) int* test() 
{
    int len = 5;
    int * arr=new int[len+1];
    arr[0]=len;
    arr[1]=1;
    arr[2]=2;
    arr[3]=3;
    arr[4]=4;
    arr[5]=5;
        return arr;
}

extern "C" __declspec(dllexport) int ReleaseMemory(int* pArray)
{
    delete[] pArray;
    return 0;
}

The DLL will be called InteropTestApp.

Then you create a console application in C#.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace DLLCall
{
    class Program
    {
        [DllImport("C:\\Devs\\C++\\Projects\\Interop\\InteropTestApp\\Debug\\InteropTestApp.dll")]
        public static extern IntPtr test();

        [DllImport("C:\\Devs\\C++\\Projects\\Interop\\InteropTestApp\\Debug\\InteropTestApp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int ReleaseMemory(IntPtr ptr);

        static void Main(string[] args)
        {
            IntPtr ptr = test();
            int arrayLength = Marshal.ReadInt32(ptr);
            // points to arr[1], which is first value
            IntPtr start = IntPtr.Add(ptr, 4);
            int[] result = new int[arrayLength];
            Marshal.Copy(start, result, 0, arrayLength);

            ReleaseMemory(ptr);

            Console.ReadKey();
        }
    }
}

result now contains the values 1,2,3,4,5.

Hope that helps.

Gabriel
  • 3,564
  • 1
  • 27
  • 49
  • 3
    It doesn't help. You'll need to show how you know the magic "5" and how you are going to release the array so there is no permanent memory leak. – Hans Passant Aug 04 '13 at 14:33
  • 3
    @Hans, the question was "Return C++ array to C#". So, yes, it does help. – user1764961 Aug 04 '13 at 17:04
  • 5
    Okay, then just consider the comment a cautionary warning to the innumerable googlers that will find your answer some day and have no clue that the code leaks memory like a sieve and arbitrarily bombs with access violations. – Hans Passant Aug 04 '13 at 17:08
  • agreed. Will edit my post later to take your (constructive) remarks into account later. – Gabriel Aug 04 '13 at 22:34
  • @HansPassant I am one of the innumerable googlers find this answer. what's wrong with this answer except for the magic "5"? would it be save if I work in both side and know exactly the length of the array?Thanks – flankechen Aug 18 '15 at 03:30
  • @flankechen The answer was edited after Hans' comment to remedy the memory leak. – karl_ Apr 20 '16 at 16:11
  • @HansPassant can you confirm you are ok with the answer and tell us if you still see something wrong? – Gabriel Jul 16 '16 at 09:54
  • 1
    @Gabriel The code doesn't leak memory. Memory allocated in C++ has to be deleted inside C++. C#'s GC has no clue about memory generated outside itself. – Luke Dupin Oct 25 '16 at 18:48
  • When I use the `ReleaseMemory` function it causes my code to crash, what exactly does it do? (Sorry for the probably stupid question, but I'm not very familiar with C++) – Lupus Ossorum Dec 16 '16 at 06:37
  • @LupusOssorum: is that exact code crashing for you? ReleaseMemory is here to tell the system that the array you allocated is not needed anymore and that this memory is available if another part of the program needs memory. C# does that job for you through memory management, C++ does not. Is that clearer? – Gabriel Dec 17 '16 at 22:03
  • 1
    @Gabriel thanks, I think it was because I called `ReleaseMemory` before I was finished with it in the C# code. – Lupus Ossorum Dec 29 '16 at 00:48
  • I made an edit to remove the need for the magic `"5"`. I allocate the array in c++ with a `+1` length, and in the `[0]` position I put the length of the array (without the `+1`). Now, in C# code, I read the first element (that is the length), increment the pointer, and with this length I can read the rest of the elements into a c# array. – lmcarreiro Feb 01 '18 at 11:46
  • Could someone tell me why the pointer is being incremented in the latest edit? Without incrementing it the code would work fine for me, but by incrementing it it throws an error as the new pointer address has not been allocated (which actually makes sense to me). Ps. You got rid of the five and now there is a 4. Just saying. – FatFingersJackson Dec 23 '18 at 13:02
  • Figured it out, the incrementation (sizeofint) skips the first element in array, which is the length. It still crashes with (Pointer was not allocated). I'll see if I can update the solution, which is to call release with the original address. – FatFingersJackson Dec 23 '18 at 13:37
  • There are lots of comments here! they confused me. – Dang D. Khanh Apr 18 '20 at 00:55
  • 1
    @TheGridLock: short answer is code is working the way it is supposed to be. If you see any problems, please let me know. – Gabriel Apr 18 '20 at 07:31
  • Thanks @Gabriel. this 's what i need! – Dang D. Khanh Apr 20 '20 at 06:25
  • @Gabriel Sorry to hijack this comment section. Do you know if it is possible to do this with a 2D array or a vector>? I am looking to pass a cluster of data from a C++ dll to my C# application. Do you perhaps have a hint for me? – Jax Teller May 10 '20 at 21:58
  • Is it not possible to do this a better way whereby C# allocates the memory for the array and passes it as an out parameter into the C++ function to assign to it? I ask rather than show this because I'm struggling to find a way to assign to an array in C++ which it didn't create... – Matt Arnold Jan 08 '21 at 10:56
0

More or less the same as the answer above, but this worked for me. For an array returned from a c method like this:

EXPORT char** methodname(void);

I did not succeed in getting this to work with the MarshalAs attribute, but this did the trick: (condensed code)

[DllImport(libName, EntryPoint = "methodname", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr methodname();

var listPtr = methodname();
var list = new List<string>();
IntPtr itemPtr = IntPtr.Zero;
var offset = 0;
while ((itemPtr = Marshal.ReadIntPtr(listPtr, offset)) != IntPtr.Zero) 
{
    Console.WriteLine(Marshal.PtrToStringAnsi(itemPtr));
    offset += 4;
}
Davy
  • 6,295
  • 5
  • 27
  • 38