1

Goal: Retrieve the array of structs from c++, from the c++ side it's finalized to return pointer to struct pointer (Double pointer)(Not in my control).

Sample c++ code :

struct Output
{
    char* Name; 
};

extern "C"
{
__declspec(dllexport) Output** getoutput()
    {

        Output* items = (Output*)malloc(sizeof(Output) * 4);

        items->Name = "Hello World";

        return &items;
    }
}

c# Side code :

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct Output
    {
        [MarshalAsAttribute(UnmanagedType.LPStr)]
        public string Name;
    };


 [DllImport(@"CPPInvokeExposed.dll",
       CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr getoutput();
static void Main(string[] args)
    {
       var output = Program.getoutput();
        Output[] outputs = new Output[1];
        MarshalUnmananagedArray2Struct<Output>(output, 1, out outputs);
        **outputs[0]// this has junk chars** 

    }

 public static void MarshalUnmananagedArray2Struct<T>(IntPtr unmanagedArray, int length, out T[] mangagedArray)
    {
        var size = Marshal.SizeOf(typeof(T));
        mangagedArray = new T[length];

        for (int i = 0; i < length; i++)
        {
            IntPtr ins = new IntPtr(unmanagedArray.ToInt64() + i * size);

            mangagedArray[i] = Marshal.PtrToStructure<T>(ins);
        }
    }

Is not clear whether the problem is in c++ or c# code. What should be the correct way get the Char* from c++ which exists in struct.

One strange thing is, for the same code if c++ code return single pointer(Output*) instead double pointer(Output**), there is no junk character, getting the correct assigned value. looks like something wrong while returning double pointer from c++.

enter image description here

Shashi
  • 2,860
  • 2
  • 34
  • 45
  • 1
    `return &items;` This returns the address of an automatic variable. That variable will not exist anymore when the function returns. Accessing that memory location is illegal and causes undefined behaviour. – Gerhardh May 31 '20 at 10:27
  • On contrary, returning `items` would be fine. Just make sure you allocate the memory in a way that has a corresponding deallocator on the C# side ([Marshal.FreeCoTaskMem](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.freecotaskmem?view=netcore-3.1), [Marshal.FreeHGlobal](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.freehglobal?view=netcore-3.1)). – GSerg May 31 '20 at 10:30
  • @GSerg pszReturn = (char*)::CoTaskMemAlloc(ulSize); even then same (junk char)problem persists. – Shashi May 31 '20 at 10:40
  • @Gerhardh Getting the compiler error for returning &items as you mentioned, but i did not find any way to solve that. – Shashi May 31 '20 at 10:42
  • The way to solve that is to return `items`, not `&items`. An even better way is to pass the `items` as an out argument, alongside with an `int*` argument that tells the size, then you can have automatic marshalling on top of not [returning an invalid address](https://stackoverflow.com/q/6441218/11683). – GSerg May 31 '20 at 10:44
  • @GSerg That is right , but on c# side size of the array is not known. – Shashi May 31 '20 at 10:49
  • That is [why](https://stackoverflow.com/questions/62017624/how-to-properly-pass-a-c-array-to-c#comment109688093_62017624) I mentioned the `int*` argument. – GSerg May 31 '20 at 10:53
  • @GSerg Feeling like you are near to solution, it will be helpful if you elaborate bit more about out argument and size. – Shashi May 31 '20 at 11:41
  • `Output*` on the C++ side and `Output[]` on the C# side? See https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-behavior#memory-management-with-the-interop-marshaler first. – GSerg May 31 '20 at 11:45

1 Answers1

-1

You C++ code causes undefined behaviour:

return &items;

This returns the address of an automatic variable. That variable will not exist anymore when the function returns. Accessing that memory location is illegal and causes undefined behaviour.

A clean solution would be to return the pointer itself, not the address. But in your question you state that the return type is not under your control.

In that case you must create the second level of indirection by yourself:

__declspec(dllexport) Output** getoutput()
{
    Output* items = (Output*)malloc(sizeof(Output) * 4);
    items->Name = "Hello World";

    Output **retval = (Output**)malloc(sizeof(Output*))
    *retval = items;
    return retval;
}

Of course you also need to take care about freeing both levels of memory allocation afterwards.

BTW: You allocate memory for 4 structs but only assign a value to member of the first element.

Gerhardh
  • 11,688
  • 4
  • 17
  • 39
  • In production i am allocating n number and filling all data, just for example i set to first element. I tried one level of indirection and without changing c# code. Still the junk issue persist. – Shashi May 31 '20 at 11:29