-1

I have following c++ code:

int nCount[5] = {0, 1, 2, 3, 4};    
return &nCount[0];

I am receiving this code in C# P/Invoke layer with following code:

[DllImport(@"/sharedlibrary.so",CallingConvention = CallingConvention.Cdecl)]                                
 public extern static IntPtr read_array_adapter();

Which works well. But when I run c# code below:

int[] result = new int[5];
Marshal.Copy(ptr, result, 0, 5); 

It populates the array result with some random large numbers which looks like below:

int[]{1663918692,1852139884,1970351988,1936417641,244554078}

which has no relation with my original array in C++ which looks like {0,1,2,3,4} Any idea what Marshal.Copy may be doing to populate these kind of results?

Lost
  • 12,007
  • 32
  • 121
  • 193
  • `int nCount[5] = {0, 1, 2, 3, 4}; return &nCount[0];` -- This will not work if `nCount` is a local variable. You should specify where that array is declared, as that means much more than what you may think. – PaulMcKenzie May 17 '19 at 03:55
  • It is a local function variable. What should it be for me to get the real values? – Lost May 17 '19 at 03:57
  • Well, make the array global. Returning the address of a local variable in C++ is undefined behavior. – PaulMcKenzie May 17 '19 at 03:57
  • See https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope – chris May 17 '19 at 03:58
  • You also need to specify the intent of that array. It is hard to believe it is there just for 5 numbers, 0,1,2,3,4. There has to be a bigger plan in mind. What that plan is would determine how you want to specify where and how that array is declared. – PaulMcKenzie May 17 '19 at 04:03
  • @PaulMcKenzie this was for simplicity. I basically need to return an int[] to a c# application’ s p/invoke layer and in treasury it’s a huge []. Sounds like there is no other Way than returning an int* if I want to share int[] between two layers – Lost May 17 '19 at 04:10
  • @Lost Note that you were returning an `int*` anyway. It was just that the `int *` was local. – PaulMcKenzie May 17 '19 at 04:11
  • I’ll try again by making it global but aren’t global variables bad practice? – Lost May 17 '19 at 04:13
  • If your only use for that array is to have something to send back to C# reliably, then that should be ok. – PaulMcKenzie May 17 '19 at 04:20
  • Euw, don't make it global. – David Heffernan May 17 '19 at 07:59
  • It is a standard dangling pointer problem. You tend to get away with in C++ code by accident, but when you pinvoke the pinvoke marshaller's stack will always corrupt the pointed-to values. Make it correct by allowing the caller to pass the storage for the array. – Hans Passant May 17 '19 at 11:03
  • @HansPassant what does it mean by `allowing the caller to pass the storage of the array`? Can you point me to an example? – Lost May 17 '19 at 16:48
  • Actually I tried passing Intptr by reference, hoping it would survive the call-stack transition but that one also came back with some garbage values. – Lost May 17 '19 at 16:49

1 Answers1

1

If you want memory to survive a function scope, and don't want to use global variables, then you must allocate it from elsewhere than the stack (local variable).

You can use any allocator, you can use one that .NET already knows, or if you use another one specific to C++ or your platform, then you must also provide another P/Invokable function for deallocation, something like this:

C++

int* read_array_adapter()
{
    int nCount[5] = {0, 1, 2, 3, 4};    
    return AllocateAndCopy(...);
}

void free_array_adapter(int *) // or a generic pointer of course...
{
    Free(...);
}

C#

static void Main(string[] args)
{
    var ptr = read_array_adapter();
    var result = new int[5];
    Marshal.Copy(ptr, result, 0, 5);
    free_array_adapter(ptr);
}

[DllImport(@"/sharedlibrary.so",CallingConvention = CallingConvention.Cdecl)]                                
public extern static IntPtr read_array_adapter();

[DllImport(@"/sharedlibrary.so",CallingConvention = CallingConvention.Cdecl)]                                
public extern static void free_array_adapter(IntPtr ptr);

You can also use a known allocator between .NET and C/C++, but this depends on the platform (Windows, linux, etc.): https://www.mono-project.com/docs/advanced/pinvoke/

Here is a sample implementation with C's malloc/free duo:

int* read_array_adapter()
{
    int nCount[5] = { 0, 1, 2, 3, 4 };
    int* p = (int*)malloc(5 * 4);
    memcpy(p, nCount, 5 * 4);
    return p;
}

void free_array_adapter(void * ptr)
{
    free(ptr);
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • what is ‘’allocateAndCopy? – Lost May 17 '19 at 10:34
  • Any allocation (+ copy) function that you can come up with. And Free would be is its deallocation part. – Simon Mourier May 17 '19 at 12:54
  • Can you point me to an example of how this allocate and Copy function would look like? I am kind of lost. Also, I tried passing IntPtr by reference hoping that it should survive the call but it still ended up with wrong values :( – Lost May 17 '19 at 16:47
  • Any allocator/deallocator will do. I have updated my answer with malloc/free. – Simon Mourier May 17 '19 at 17:26
  • Thank you so much. The example that you provided just fixed it. Really hanks a bunch. One questions though, would this be safe enough against variable sized strings? – Lost May 17 '19 at 18:00
  • Same idea for any data. Just make sure you provide the C# side a way to deallocate what you allocated. – Simon Mourier May 17 '19 at 18:21