CppSharp icon indicating copy to clipboard operation
CppSharp copied to clipboard

Simple C++ struct exposed as CS_VALUE_TYPE looks unexpectedly complex in C#

Open jamesford42 opened this issue 8 years ago • 4 comments

I added the following to "test\CSharp\CSharp.h"

#define MAX_NAME_LEN 8

struct CS_VALUE_TYPE TestStructValueType
{	
	char Name[MAX_NAME_LEN];
};

I was expecting something like this from the generator...

[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct TestStructValueType
{
    [FieldOffset(0)]
    public sbyte Name[8];
}

However this is what I got...

public unsafe partial struct TestStructValueType
    {
        [StructLayout(LayoutKind.Explicit, Size = 8)]
        public partial struct __Internal
        {
            [FieldOffset(0)]
            public fixed sbyte Name[8];

            [SuppressUnmanagedCodeSecurity]
            [DllImport("CSharp.Native.dll", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.ThisCall,
                EntryPoint="??0TestStructValueType@@QAE@ABU0@@Z")]
            internal static extern global::System.IntPtr cctor_1(global::System.IntPtr instance, global::System.IntPtr _0);
        }

        private TestStructValueType.__Internal __instance;
        internal TestStructValueType.__Internal __Instance { get { return __instance; } }

        internal static TestStructValueType __CreateInstance(global::System.IntPtr native, bool skipVTables = false)
        {
            return new TestStructValueType(native.ToPointer(), skipVTables);
        }

        internal static TestStructValueType __CreateInstance(TestStructValueType.__Internal native, bool skipVTables = false)
        {
            return new TestStructValueType(native, skipVTables);
        }

        private TestStructValueType(TestStructValueType.__Internal native, bool skipVTables = false)
            : this()
        {
            __instance = native;
        }

        private TestStructValueType(void* native, bool skipVTables = false) : this()
        {
            __instance = *(__Internal*) native;
        }

        public TestStructValueType(TestStructValueType _0)
            : this()
        {
            var ____arg0 = _0.__Instance;
            var __arg0 = new global::System.IntPtr(&____arg0);
            fixed (__Internal* __instancePtr = &__instance)
            {
                __Internal.cctor_1(new global::System.IntPtr(__instancePtr), __arg0);
            }
        }

        public sbyte[] Name
        {
            get
            {
                fixed (sbyte* __arrPtr = __instance.Name)
                {
                    sbyte[] __value = null;
                    if (__arrPtr != null)
                    {
                        __value = new sbyte[8];
                        for (int i = 0; i < 8; i++)
                            __value[i] = __arrPtr[i];
                    }
                    return __value;
                }
            }

            set
            {
                fixed (sbyte* __arrPtr = __instance.Name)
                {
                    if (value != null)
                    {
                        for (int i = 0; i < 8; i++)
                            __arrPtr[i] = value[i];
                    }
                }
            }
        }
    }

jamesford42 avatar Jan 30 '17 03:01 jamesford42

We make the distinction between the internal struct, which provides the native types closest to native, and the higher-level struct which provides higher-level types, for instance properties for the fields, and strings vs. pointers, just for some examples.

There's also some "book-keeping" code used by CppSharp itself, for creating new instances, and there are constructors, so we can safely copy the structs by invoking the native copy constructor.

What is your use case that you'd prefer the more "raw" version of just the internal struct? Only thing I can think of if you want to do all the marshaling manually and just want the structs generated.

tritao avatar Jan 30 '17 13:01 tritao

Yea i'm not really sure about all the gritty details. But the most concerning part of this is the Name getter/setter. Like it could have totally just been a normal field in this case. Instead it seems to be allocating a new array and therefore causing garbage on each access (and a very slow per element copy).

jamesford42 avatar Jan 30 '17 18:01 jamesford42

Guess we need to special case this primitive array case to get optimized code, either copy the array elements memory ourselves or let P/Invoke deal with it.

tritao avatar Feb 02 '17 17:02 tritao

Have exactly the same problem, we have a manually created C# file that marshals the C++ interface. In this file the C++ structs are mapped directly to C# structs which need to be updated everytime the C++ changes (sometimes doesn't happen and weird bugs happen).

The structs on the C# side are pretty simple with public fields that are accessed directly. Been trying to use CppSharp to generate this C# file (which has around 2700 lines) but haven't managed to get it close to the existing file manually maintained (trying to minimize the impact on the calling code that we have).

Managed to generate the enums but now I'm stuck on the structs -> class/properties (ideally structs -> structs).

aduarte-manusvr avatar Apr 06 '23 15:04 aduarte-manusvr