C#
C++
interop
PInvoke
programming

Possible to call C code from C?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Yes, it is absolutely possible to call C++ code from C#. This is a common requirement when you need to reuse an existing C++ library, access low-level system APIs, or offload performance-critical computations to native code while keeping the rest of your application in C#. There are two main approaches: Platform Invocation Services (P/Invoke) for calling C-style functions, and C++/CLI for creating a managed wrapper around C++ classes. This article covers both approaches with working examples and discusses when to choose each one.

Approach 1: P/Invoke

P/Invoke (Platform Invocation Services) lets C# call functions exported from unmanaged DLLs. This works well when the C++ code exposes functions with C-compatible signatures, meaning they use extern "C" to prevent name mangling.

Step 1: Write the C++ DLL

First, create a C++ function and export it from a DLL:

cpp
1// MathLib.h
2#pragma once
3
4#ifdef MATHLIB_EXPORTS
5#define MATHLIB_API __declspec(dllexport)
6#else
7#define MATHLIB_API __declspec(dllimport)
8#endif
9
10extern "C" {
11    MATHLIB_API int Add(int a, int b);
12    MATHLIB_API double ComputeSquareRoot(double value);
13}
cpp
1// MathLib.cpp
2#include "MathLib.h"
3#include <cmath>
4
5int Add(int a, int b) {
6    return a + b;
7}
8
9double ComputeSquareRoot(double value) {
10    return std::sqrt(value);
11}

The extern "C" block is critical. Without it, the C++ compiler applies name mangling to function names, and P/Invoke will not be able to find them in the DLL.

Step 2: Call from C#

In your C# project, declare the external functions using the DllImport attribute:

csharp
1using System;
2using System.Runtime.InteropServices;
3
4class Program
5{
6    [DllImport("MathLib.dll", CallingConvention = CallingConvention.Cdecl)]
7    public static extern int Add(int a, int b);
8
9    [DllImport("MathLib.dll", CallingConvention = CallingConvention.Cdecl)]
10    public static extern double ComputeSquareRoot(double value);
11
12    static void Main()
13    {
14        Console.WriteLine(Add(3, 4));              // Output: 7
15        Console.WriteLine(ComputeSquareRoot(16));   // Output: 4
16    }
17}

The CallingConvention must match between the C++ and C# sides. Cdecl is the default calling convention for C/C++ functions. If the C++ side uses __stdcall, you must specify CallingConvention.StdCall in C#.

Passing Strings and Arrays

P/Invoke handles data marshaling between managed and unmanaged memory. For strings and arrays, you need to pay attention to how data is converted:

csharp
1// C++ side: extern "C" MATHLIB_API void FillArray(int* arr, int length);
2
3[DllImport("MathLib.dll", CallingConvention = CallingConvention.Cdecl)]
4public static extern void FillArray(int[] arr, int length);
5
6static void Main()
7{
8    int[] data = new int[10];
9    FillArray(data, data.Length);
10    // data is now filled by the C++ function
11}

For strings, use [MarshalAs(UnmanagedType.LPStr)] for ANSI strings or [MarshalAs(UnmanagedType.LPWStr)] for wide strings.

Approach 2: C++/CLI Wrapper

When you need to call C++ class methods, use templates, or work with complex data structures, P/Invoke becomes cumbersome. In these cases, C++/CLI provides a better solution. C++/CLI is a language extension that can compile to both managed (.NET) and unmanaged (native) code, making it the ideal bridge between the two worlds.

Step 1: Write the Native C++ Class

cpp
1// Calculator.h
2class Calculator {
3public:
4    Calculator();
5    int Multiply(int a, int b);
6    double Divide(double a, double b);
7};
cpp
1// Calculator.cpp
2#include "Calculator.h"
3#include <stdexcept>
4
5Calculator::Calculator() {}
6
7int Calculator::Multiply(int a, int b) {
8    return a * b;
9}
10
11double Calculator::Divide(double a, double b) {
12    if (b == 0.0) throw std::invalid_argument("Division by zero");
13    return a / b;
14}

Step 2: Create the C++/CLI Wrapper

cpp
1// CalculatorWrapper.h (C++/CLI)
2#pragma once
3#include "Calculator.h"
4
5using namespace System;
6
7public ref class CalculatorWrapper
8{
9private:
10    Calculator* m_native;
11public:
12    CalculatorWrapper() {
13        m_native = new Calculator();
14    }
15
16    ~CalculatorWrapper() {
17        delete m_native;
18    }
19
20    int Multiply(int a, int b) {
21        return m_native->Multiply(a, b);
22    }
23
24    double Divide(double a, double b) {
25        try {
26            return m_native->Divide(a, b);
27        }
28        catch (const std::exception& ex) {
29            throw gcnew InvalidOperationException(
30                gcnew String(ex.what()));
31        }
32    }
33};

Step 3: Use the Wrapper in C#

csharp
1using System;
2
3class Program
4{
5    static void Main()
6    {
7        var calc = new CalculatorWrapper();
8        Console.WriteLine(calc.Multiply(6, 7));   // Output: 42
9        Console.WriteLine(calc.Divide(10, 3));     // Output: 3.333...
10    }
11}

The C# code treats CalculatorWrapper as a normal .NET class. All the complexity of interacting with native C++ is hidden inside the wrapper.

When to Use Each Approach

Use P/Invoke when you are calling standalone C-style functions from an existing DLL. It requires no additional projects or compilers beyond the standard C# toolchain, and it works across platforms with .NET Core and .NET 5+.

Use C++/CLI when you need to interact with C++ classes, use inheritance hierarchies, or work with complex data types. C++/CLI is Windows-only and requires the Visual C++ compiler, but it provides a much cleaner integration for object-oriented C++ code.

Common Pitfalls

Forgetting extern "C". Without this, C++ name mangling makes function names unrecognizable to P/Invoke. You will get a DllNotFoundException or EntryPointNotFoundException at runtime.

Mismatched calling conventions. If the C++ side uses __cdecl but the C# side declares CallingConvention.StdCall, the stack will be corrupted, often causing crashes that are difficult to diagnose.

Memory leaks in C++/CLI wrappers. If you allocate native objects with new in the constructor, you must delete them in both the destructor (~ClassName, called by Dispose) and the finalizer (!ClassName, called by the garbage collector). Otherwise, native memory will leak.

32-bit vs 64-bit mismatches. The DLL and the C# application must target the same architecture. A 64-bit C# process cannot load a 32-bit DLL and vice versa.

Summary

Calling C++ code from C# is well-supported through two primary mechanisms. P/Invoke is the simpler option for C-style functions exported from DLLs and works cross-platform. C++/CLI provides full access to C++ classes and complex types but is limited to Windows. In both cases, pay careful attention to calling conventions, data marshaling, and memory management to avoid crashes and leaks. Choose P/Invoke for simple function calls and C++/CLI when you need object-oriented interop with native C++ code.


Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.