How to pass Nested complex structure and Union from Managed C# dll to C++ dll

Rajanbabu Manoharan 20 Reputation points
2023-03-14T12:31:43.7+00:00

I have a exported methods from C++ dll and need to consume it from C#. The C++ method has complex nested structures and union as parameter. Here I could not send string data from C# to C++.

Below C++, pdll_sensor_test structure has name variable. Could not able to send value from C# to C++. It prints empty. Kindly help the suggestion and provide solution.

Source.cpp (C++)

#include "pch.h"
#include <stdio.h>

extern "C"
{
    enum pdll_test_id
    {
        dut_com_init = 0,
        cont_pkt_tx = 1,
    };

    struct pdll_sensor_test
    {
        pdll_test_id test_id;
        bool en;
        char name[13];
        int reg_addr;
    };

    union pdll_test_data
    {
        pdll_test_id id;
        pdll_sensor_test sensor;
    };

    struct pdll_device
    {
        bool is_active;
        pdll_test_data test;
    };

    __declspec(dllexport) int PdllDeviceMethod(pdll_device* pdlldevice)
    {
        printf("pdlldevice->is_active=%d\n", pdlldevice->is_active);
        printf("pdlldevice->test.sensor.test_id=%d\n", pdlldevice->test.sensor.test_id);
        printf("pdlldevice->test.sensor.en=%d\n", pdlldevice->test.sensor.en);
        printf("pdlldevice->test.sensor.name=%s\n", pdlldevice->test.sensor.name);
        printf("pdlldevice->test.sensor.reg_addr=%d\n", pdlldevice->test.sensor.reg_addr);

        return 0;
    }
}

Program.cs (C#)

using System;
using System.Runtime.InteropServices;

namespace CSharpApp
{
    public class Program
    {
        public enum pdll_test_id
        {
            dut_com_init = 0,
            cont_pkt_tx = 1,
        };

        [StructLayout(LayoutKind.Sequential)]
        public struct pdll_sensor_test
        {
            public pdll_test_id test_id;
            public bool en;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
            public string name;
            public int reg_addr;
        };

        //This is union in C++
        [StructLayout(LayoutKind.Explicit)]
        public struct pdll_test_data
        {
            [FieldOffset(0)]
            public pdll_test_id id;
            [FieldOffset(4)]
            public pdll_sensor_test sensor;
        };

        [StructLayout(LayoutKind.Sequential)]
        public struct pdll_device
        {
            public bool is_active;
            public pdll_test_data test;
        };

        [DllImport("CPPDynamicDLL.dll")]
        public static extern int PdllDeviceMethod(ref pdll_device pdlldevice);
        public static void Main(string[] args)
        {
            Console.WriteLine("This is C# program");

            pdll_device pdllDevice = new pdll_device();
            pdllDevice.is_active = true;

            pdllDevice.test.id = pdll_test_id.dut_com_init;
            pdllDevice.test.sensor.test_id = pdll_test_id.cont_pkt_tx;
            pdllDevice.test.sensor.en = true;
            pdllDevice.test.sensor.name = "wsa";
            pdllDevice.test.sensor.reg_addr = 1024;
            var result = PdllDeviceMethod(ref pdllDevice);
        }
    }
}
Developer technologies C#
0 comments No comments
{count} votes

Accepted answer
  1. Viorel 122.6K Reputation points
    2023-03-15T08:16:33.9666667+00:00

    According to https://learn.microsoft.com/en-us/dotnet/framework/interop/marshalling-classes-structures-and-unions#unions-sample, value types and reference types (such as the strings) are not permitted to overlap. The provided sample uses two variants of the C# structures to simulate the C++ union. I think that you can try a similar approach:

    extern "C"
    {
        enum pdll_test_id : __int32
        {
            dut_com_init = 0,
            cont_pkt_tx = 1,
        };
    
    #pragma pack(push)
    #pragma pack(4)
    
        struct pdll_sensor_test
        {
            pdll_test_id test_id;
            bool en;
            char name[13];
            int reg_addr;
        };
    
    
        union pdll_test_data
        {
            pdll_test_id id;
            pdll_sensor_test sensor;
        };
    
        struct pdll_device
        {
            bool is_active;
            pdll_test_data test;
        };
    #pragma pack(pop)
    
    
        __declspec( dllexport ) int __cdecl PdllDeviceMethod( pdll_device* pdlldevice )
        {
            printf( "pdlldevice->is_active=%d\n", pdlldevice->is_active );
            printf( "pdlldevice->test.sensor.test_id=%d\n", pdlldevice->test.sensor.test_id );
            printf( "pdlldevice->test.sensor.en=%d\n", pdlldevice->test.sensor.en );
            printf( "pdlldevice->test.sensor.name=%s\n", pdlldevice->test.sensor.name );
            printf( "pdlldevice->test.sensor.reg_addr=%d\n", pdlldevice->test.sensor.reg_addr );
    
            return 0;
        }
    }
    
    public enum pdll_test_id : Int32
    {
        dut_com_init = 0,
        cont_pkt_tx = 1,
    };
    
    
    [StructLayout( LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi )]
    public struct pdll_sensor_test
    {
        public pdll_test_id test_id;
        [MarshalAs( UnmanagedType.I1 )]
        public bool en;
        [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 13 )]
        public string name; 
        public int reg_addr;
    };
    
    
    [StructLayout( LayoutKind.Sequential, Pack = 4)]
    public struct pdll_test_data_1
    {
        public pdll_test_id id;
    };
    
    
    [StructLayout( LayoutKind.Sequential, Pack = 4 )]
    public struct pdll_test_data_2
    {
        public pdll_sensor_test sensor;
    };
    
    
    [StructLayout( LayoutKind.Sequential, Pack = 4 )]
    public struct pdll_device_1
    {
        [MarshalAs( UnmanagedType.I1 )]
        public bool is_active;
        public pdll_test_data_1 test;
    };
    
    
    
    [StructLayout( LayoutKind.Sequential, Pack = 4 )]
    public struct pdll_device_2
    {
        [MarshalAs( UnmanagedType.I1 )]
        public bool is_active;
        public pdll_test_data_2 test;
    };
    
    
    [DllImport( @"CPPDynamicDLL.dll", CallingConvention = CallingConvention.Cdecl )]
    public static extern int PdllDeviceMethod( ref pdll_device_1 pdlldevice );
    
    [DllImport( @"CPPDynamicDLL.dll", CallingConvention = CallingConvention.Cdecl )]
    public static extern int PdllDeviceMethod( ref pdll_device_2 pdlldevice );
    
    . . .
    
    pdll_device_2 pdllDevice = new pdll_device_2( );
    pdllDevice.is_active = true;
    pdllDevice.test.sensor.test_id = pdll_test_id.cont_pkt_tx;
    pdllDevice.test.sensor.en = true;
    pdllDevice.test.sensor.name = "wsa";
    pdllDevice.test.sensor.reg_addr = 1024;
    
    var result = PdllDeviceMethod( ref pdllDevice );
    

    The example also uses some additional declarations (maybe redundant) to make sure that C# communicates correctly with C++ in this experiment. You can also use the original unchanged C++ code and only adjust the C# declarations.

    It is also possible to consider fixed arrays like fixed byte name[13] and pointers, however this requires activating the unsafe code option. Or maybe you can construct the data as a byte array and pass it to DLL.

    Or it is possible to create an intermediate Class Library in C++/CLI that can interact with C# and C easier.

    Therefore, if the DLL can be rebuilt, it is better to avoid unions.

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Minxin Yu 13,501 Reputation points Microsoft External Staff
    2023-03-15T07:16:56.4333333+00:00

    Hi, Rajan Babu Manoharan
    The problem is with the alignment, try modifying the code:

    Debug x64
    C++ part:

       struct pdll_sensor_test
        {
            pdll_test_id test_id;
            bool en;
           
            int reg_addr;
            char name[13];
        };
    
       union pdll_test_data
        {
           
            pdll_sensor_test sensor;
            pdll_test_id id;
        };
    
    

    C# part

            public struct pdll_sensor_test
            {
                public pdll_test_id test_id;
                public bool en;
         
                public int reg_addr;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
                public string name;
            };
    
            //This is union in C++
            [StructLayout(LayoutKind.Explicit)]
            public struct pdll_test_data
            {
                [FieldOffset(0)]
                public pdll_sensor_test sensor;
                [FieldOffset(32)]
                public pdll_test_id id;
    
            };
    

    User's image

    Best regards,

    Minxin Yu


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.