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.

How To Fix an Unhandled System.DllNotFoundException in Mixed C++/C# Projects

Let’s see how to fix a common problem when building mixed C++/C# projects in Visual Studio.

Someone had a Visual Studio 2019 solution containing a C# application project and a native C++ DLL project. The C# application was supposed to call some C-interface functions exported by the native C++ DLL.

Both projects built successfully in Visual Studio. But, after the C# application was launched, a System.DllNotFoundException was thrown when the C# code tried to invoke the DLL-exported functions:

Visual Studio shows an error message complaining about an unhandled exception of type System.DllNotFoundException when trying to invoke native C++ DLL functions from C# code.
Visual Studio complains about an unhandled System.DllNotFoundException when debugging the C# application.

So, it looks like the C# application is unable to find the native C++ DLL.

First Attempt: A Manual Fix

In a first attempt to solve this problem, I tried manually copying the native C++ DLL from the folder where it was built into the same folder where the C# application was built (for example: from MySolution\Debug to MySolution\CSharpApp\bin\Debug). Then, I relaunched the C# application, and everything worked fine as expected this time! Wow 🙂 The problem was kind of easy to fix.

However, I was not 100% happy, as this was kind of a manual fix, that required manually copying-and-pasting the DLL from its own folder to the C# application folder. I would love to have Visual Studio doing that automatically!

A Better Solution: Making the Copy Automatic in Post-build

Well, it turns out that we can do better than that! In fact, it’s possible to automate that process and basically instruct Visual Studio’s build system to perform the aforementioned copy for us. To do so, we basically need to specify a custom command line that VS automatically executes as a post-build event.

In Solution Explorer, right-click on the C# application project, and select Properties from the menu.

Select the Build Events tab, and enter the desired copy instruction in the Post-build event command line box. For example, the following command can be used:

xcopy "$(SolutionDir)$(Configuration)\MyCppDll.dll" "$(TargetDir)" /Y

Then type Ctrl+S or click the Save button (= diskette icon) in the toolbar to save these changes.

Setting a post-build event command line inside Visual Studio IDE to copy the DLL into the same folder of the C# application.
Setting the post-build event command line to copy the DLL into the C# application folder

Basically, with the above settings we are telling Visual Studio: “Dear VS, after successfully building the C# application, please copy the C++ DLL from its original folder into the same folder where you have just built the C# application. Thank you very much!”

After relaunching the C# application, this time everything went well, and the C# EXE was able to find the C++ DLL and call its exported C-interface functions.

Addendum: Demystifying the $(Thingy)

If you take a look at the custom copy command added in post-build event, you’ll notice some apparently weird syntax like $(SolutionDir) or $(TargetDir). These $(something) are basically MSBuild macros, that expand to meaningful stuff like the path of the Visual Studio solution, or the directory of the primary output file for the build (e.g. the directory where the C# application .exe file is created).

You can read more about those MSBuild macros in this MSDN page: Common macros for MSBuild commands and properties.

Note that the macros representing paths can include the trailing backslash \; for example, this is the case of $(SolutionDir). So, take that into account when combining these macros to refer to actual sub-directories and paths in your solution.

Considering the command used above:

xcopy "$(SolutionDir)$(Configuration)\MyCppDll.dll" "$(TargetDir)" /Y

$(SolutionDir) represents the full path of the Visual Studio solution, e.g. C:\Users\Gio\source\repos\MySolution\.

$(TargetDir) is the directory of the primary output file for the build, for example the directory where the C# console app .exe is created. This could be something like C:\Users\Gio\source\repos\MySolution\CSharpApp\bin\Debug\.

$(Configuration) is the name of the current project configuration, for example: Debug when doing a debug build.

So, for example: $(SolutionDir)$(Configuration) would expand to something like C:\Users\Gio\source\repos\MySolution\Debug in debug builds.

In addition, you can also see how these MSBuild macros are actually expanded in a given context. To do so in Visual Studio, once you are in the Build Events tab, click the Edit Post-build button. Then click the Macros > > button to view the actual expansions of those macros.

A sample expansion of some MSBuild macros.
Sample expansion of some MSBuild macros

How to Enumerate the Modules Loaded in a Process

Let’s see how to use a convenient Windows API C-interface library from C++ code to enumerate the modules loaded in a given process.

Suppose that you want to enumerate all the modules in a given process in Windows, for example because you want to discover the DLLs loaded into a given process.

You can use the so called Tool Help Library for that (the associated Windows SDK header file is <tlhelp32.h>)

In particular, you can start taking the snapshot of the process’s module list, invoking the CreateToolhelp32Snapshot Windows API function. On success, this function will return a HANDLE that will be used with the following module enumeration functions.

// Create module snapshot for the enumeration.
//
// The first parameter passed to CreateToolhelp32Snapshot
// tells the API what kind of elements are included in the snapshot:
// In this case we are interested in the list of *modules*
// loaded in the process of given ID.
HANDLE hEnum = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
                                          processID);
if (hEnum == INVALID_HANDLE_VALUE)
{
    // Signal error...
}

// On success, store the raw handle in some safe RAII wrapper,
// so it will be *automatically* closed via CloseHandle, 
// even in case of exceptions.
//
// E.g.: ScopedHandle enumHandle{ hEnum };

Then you can initialize the enumeration loop calling the Module32First API function, which will return information about the first module in the list. The module information is stored as fields of the MODULEENTRTY32 structure. You can retrieve the particular pieces of information that you want (e.g. the module name, or its size, etc.) from this structure’s fields.

Next, you can repeatedly invoke the Module32Next API to get information for the subsequent modules in the list.

In C++ code, the enumeration logic can look like this:

// Initialize this structure with its size field (dwSize).
// Other fields of the structure containing module info 
// will be written by the Module32First and Module32Next APIs.
MODULEENTRY32 moduleEntry = { sizeof(moduleEntry) };

// Start the enumeration loop invoking Module32First
BOOL continueEnumeration = ::Module32First(enumHandle.Get(),
                                           &moduleEntry);
while (continueEnumeration)
{
    // Extract the pieces of information we need 
    // from the MODULEENTRY32 structure
    moduleList.push_back(
        // Here we retrieve the module name (szModule) 
        // and the module size (modBaseSize),
        // pack them in a C++ struct, and push it into
        // a vector<ModuleInfo> that will contain the list
        // of all modules in the given process
        ModuleInfo{ moduleEntry.szModule, 
                    moduleEntry.modBaseSize }
    );

    // Move to the next module (if any)
    continueEnumeration = ::Module32Next(enumHandle.Get(),
                                         &moduleEntry);
}

When there are no more modules to enumerate in the snapshot, Module32Next will return FALSE, and a subsequent call to GetLastError will return ERROR_NO_MORE_FILES.

You can see that in action in some compilable C++ code in this repo of mine on GitHub.

For example, suppose that you want to see a list of DLLs loaded in the Notepad process. You can use your favorite tool to get the process ID (PID) associated to your running instance of Notepad, then pass this PID as the only parameter to the command line tool cited above, and it will print out a list of the loaded modules (DLLs) inside the Notepad process, as shown below.

List of modules in the Notepad process.

Embedding (and Extracting) Binary Files like DLLs into an EXE as Resources

A Windows .EXE executable file can contain binary resources, which are basically arbitrary binary data embedded in the file.

In particular, it’s possible to embed one or more DLLs as binary resources into an EXE. In this article, I’ll first show you how to embed a DLL as a binary resource into an EXE using the Visual Studio IDE; then, you’ll learn how to access that binary resource data using proper Windows API calls.

A Windows EXE file can contain one or more DLLs embedded as binary resources.

Embedding a Binary Resource Using Visual Studio IDE

If you are using Visual Studio to develop your Windows C++ applications, from Solution Explorer you can right-click your EXE project node, then choose Add > Resource from the menu.

Menu command to add a resource using Visual Studio.
Adding a resource from the Visual Studio IDE

Then click the Import button, and select the binary resource to embed into the EXE, for example: TestDll.dll.

The Add Resource dialog box in Visual Studio
Click the Import button to add the binary resource (e.g. DLL)

In the Custom Resource Type dialog box that appears next, enter RCDATA as Resource type.

Then click the OK button.

A hex representation of the resource bytes is shown in the IDE. Type Ctrl+S or click the diskette icon in the toolbar to save the new resource data.

You can close the binary hex representation of the resource.

The resource was automatically labeled by the Visual Studio IDE as IDR_RCDATA1. To change that resource ID, you can open Resource View. Then, expand the project node, until you see the RCDATA virtual folder, and then IDR_RCDATA1 inside it. Click the IDR_RCDATA1 item to select it.

In the Properties grid below, you can change the ID field, for example: you can rename the resource ID as IDR_TEST_DLL.

Type Ctrl+S to save the modifications.

The binary resource ID under the RCDATA virtual folder in Resource View
The binary resource ID (IDR_TEST_DLL) under the RCDATA virtual folder in Resource View
The resource properties grid
The Properties grid to edit the resource properties

Don’t forget to #include the resource header (for example: “resource.h”) in your C++ code when you need to refer to the embedded resource by its ID.

In particular, if you open the resource.h file that was created and modified by Visual Studio, you’ll see a #define line that associates the “symbolic” name of the resource (e.g. IDR_TEST_DLL) with an integer number that represents the integer ID of the resource, for example:

#define IDR_TEST_DLL            101

Accessing an Embedded Binary Resource from C/C++ Code

Once you have embedded a binary resource, like a DLL, into your EXE, you can access the resource’s binary data using some specific Windows APIs. In particular:

  1. Invoke FindResource to get the specified resource’s information block (represented by an HRSRC handle).
  2. Invoke LoadResource, passing the above handle to the resource information block. On success, LoadResource will return another handle (declared as HGLOBAL for backward compatibility), that can be used to access the first byte of the resource.
  3. Invoke LockResource passing the resource handle returned by LoadResource, to get access to the first byte of the resource.

To get the size of the resource, you can call the SizeofResource API.

The above “API dance” can be translated into the following C++ code:

#include "resource.h"  // for the resource ID (e.g. IDR_TEST_DLL)


// Locate the embedded resource having ID = IDR_TEST_DLL
HRSRC hResourceInfo = ::FindResource(hModule,
                                     MAKEINTRESOURCE(IDR_TEST_DLL),
                                     RT_RCDATA);
if (hResourceInfo == nullptr)
{
    // Handle error...
}

// Get the handle that will be used to access 
// the first byte of the resource
HGLOBAL hResourceData = ::LoadResource(hModule, hResourceInfo);
if (hResourceData == nullptr)
{
    // Handle error...
}

// Get the address of the first byte of the resource
const void * pvResourceData = ::LockResource(hResourceData);
if (pvResourceData == nullptr)
{
    // Handle error...
}

// Get the size, in bytes, of the resource
DWORD dwResourceSize = ::SizeofResource(hModule, hResourceInfo);
if (dwResourceSize == 0)
{
    // Handle error...
}

I uploaded on GitHub a C++ demo code that extracts a DLL embedded as a resource in the EXE, and, for testing purposes, invokes a function exported from the extracted DLL. In particular, you can take a look at the ResourceBinaryView.h file for a reusable C++ class to get a read-only binary view of a resource.

P.S. An EXE is not the only type of Windows Portable Executable (PE) file that can have embedded resources. For example: DLLs can contain resources, as well.