How To Pass a Custom Struct from C# to a C++ Native DLL?

Let’s discuss a possible way to build a “bridge” between the managed C# world and the native C++ world, using P/Invoke.

Someone had a native C++ DLL, that exported a C-interface function. This exported function expected a const pointer to a custom structure, defined like this:

// Structure expected by the C++ native DLL
struct DllData
{
    GUID Id;
    int  Value;
    const wchar_t* Name;
};

The declaration of the function exported from the C++ DLL looks like this:

extern "C" HRESULT 
__stdcall MyCppDll_ProcessData(const DllData* pData);

The request was to create a custom structure in C# corresponding to the DLL structure shown above, and pass an instance of that struct to the C-interface function exported by the C++ DLL.

A Few Options to Connect the C++ Native World with the C# Managed World

In general, to pass data between managed C# code and native C++ code, there are several options available. For example:

  • Create a native C++ DLL that exports C-interface functions, and call them from C# via P/Invoke (Platform Invoke).
  • Create a (thin) C++/CLI bridging layer to connect the native C++ code with the managed C# code.
  • Wrap the native C++ code using COM, via COM objects and COM interfaces, and let C# interact with those COM wrappers.

The COM option is the most complex one, but probably also the most versatile. It would also allow reusing the wrapped C++ components from other programming languages that know how to talk with COM.

C++/CLI is an interesting option, easier than COM. However, like COM, it’s a Windows-only option. For example, considering this GitHub issue on .NET Core: C++/CLI migration to .Net core on Linux, it seems that C++/CLI is not supported on other platforms like Linux.

On the other hand, the P/Invoke option is available cross-platform on both Windows and Linux.

In the remaining part of this article, I’ll focus on the P/Invoke option to solve the problem at hand.

Using P/Invoke to Pass the Custom Structure from C# to the C++ DLL

To be able to pass the custom structure from C# to the C++ DLL exported-function, we need two steps:

  1. Define the C++ struct in C# terms; in other words, we need to map the existing C++ struct definition into some corresponding C# structure definition.
  2. Use DllImport to write a C# function declaration corresponding to the original native C-interface exported function, that C# will be able to understand.

Let’s start with the step #1. This is the C++ structure:

// Structure expected by the C++ native DLL
struct DllData
{
    GUID Id;
    int  Value;
    const wchar_t* Name;
};

It contains three fields, of types: GUID, int, and const wchar_t*. We can map those in C# using the managed types Guid, Int32, and String. So, the corresponding C# structure definition looks like this:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct DllData
{
    public Guid Id;
    public Int32 Value;
    public String Name;
}

Note that the CharSet field is set to CharSet.Unicode, to specify that the String fields should be copied from their managed C# format (which is Unicode UTF-16) to the native Unicode format (again, UTF-16 const wchar_t* in the C++ structure definition).

Now let’s focus on the step #2, which is the use of the DllImport attribute to import in C# the C-interface function exported by the native DLL. The native C-interface function has the following declaration:

extern "C" HRESULT 
__stdcall MyCppDll_ProcessData(const DllData* pData);

I crafted the following P/Invoke C# declaration for it:

[DllImport("MyCppDll.dll", 
           EntryPoint = "MyCppDll_ProcessData",
           CallingConvention = CallingConvention.StdCall,
           ExactSpelling = true,
           PreserveSig = false)]
static extern void ProcessData([In] ref DllData data);

The first parameter is the name of the native DLL: MyCppDll.dll in our case.

Then, I used the EntryPoint field to specify the name of the C-interface function exported from the DLL.

Next, I used the CallingConvention field to specify the StdCall calling convention, which corresponds to the C/C++ __stdcall.

With ExactSpelling=true we tell P/Invoke to search only for the function having the exact name we specified (MyCppDll_ProcessData in this case). Platform Invoke will fail if it cannot locate the function with that exact spelling.

Moreover, with the PreserveSig field set to false, we tell P/Invoke that the native function returns an HRESULT, and in case of error return codes, these will be automatically converted to exceptions in C#.

Finally, since the DllData structure is passed by pointer, I used a ref parameter in the C# P/Invoke declaration. In addition, since the pointer is marked const in C/C++, to explicitly convey the input-only nature of the parameter, I used the [In] attribute in the C# P/Invoke code.

Note that to use the above P/Invoke services, you need the System and System.Runtime.InteropServices namespaces.

Diagram showing a C# application passing a custom struct to a C-interface native C++ DLL.
Passing a custom struct from C# to a C-interface native C++ DLL via P/Invoke

Once you have set the above P/Invoke infrastructure, you can simply pass instances of the C# structure to the native C-interface function exported by the native C++ DLL, like this:

// Create an instance of the custom struct in C#
DllData data = new DllData
{ 
    Id = Guid.NewGuid(), 
    Value = 10, 
    Name = "Connie" 
};

// Pass it to the C++ DLL
ProcessData(ref data);

Piece of cake 😉

P.S. I uploaded some related compilable demo code here on GitHub.