DllImport in C#: How to Specify Calling Conventions

This article explains the DllImport attribute in C# and how to correctly specify calling conventions when interoperating with unmanaged code. You will learn what DllImport does, why calling conventions matter to prevent application crashes, and how to apply the CallingConvention enumeration to your native function declarations.


What is the DllImport Attribute?

The DllImport attribute is a core feature of Platform Invocation Services (P/Invoke) in .NET, located in the System.Runtime.InteropServices namespace. It allows managed C# code to call unmanaged functions exported from dynamic-link libraries (DLLs), such as the Windows API, C runtime libraries, or custom C/C++ DLLs.

To use DllImport, you declare a method with the static and extern keywords and decorate it with the attribute, specifying the name of the target DLL.

using System;
using System.Runtime.InteropServices;

class Program
{
    // Imports the MessageBoxW function from user32.dll
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
}

What are Calling Conventions?

A calling convention defines the low-level contract between the caller and the callee of a function. It determines:

  • The order in which function parameters are passed (left-to-right or right-to-left).
  • Where parameters are placed (in CPU registers or on the call stack).
  • Which entity is responsible for cleaning up parameters from the stack (the caller or the callee).

If the calling convention specified in C# does not match the convention used by the native function, the stack pointer can become misaligned. On 32-bit (x86) systems, this typically leads to a PInvokeStackImbalance exception or fatal memory corruption. On 64-bit (x64) systems, the operating system uses a unified calling convention, but specifying the correct convention remains a best practice for cross-architecture portability and clarity.


How to Specify Calling Conventions in C

You specify the calling convention by assigning a value from the CallingConvention enum to the CallingConvention field within the DllImport attribute.

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AddNumbers(int a, int b);

Common CallingConvention Enumeration Values

C# provides several values in the System.Runtime.InteropServices.CallingConvention enum:

1. CallingConvention.Winapi (Default)

This is the default value in .NET. It uses the platform's default API calling convention. On 32-bit Windows, it defaults to StdCall. On Windows CE, it defaults to Cdecl. On 64-bit systems, it maps to the standard 64-bit ABI.

[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
public static extern uint GetCurrentProcessId();

2. CallingConvention.Cdecl

The caller cleans the stack. This is the default calling convention for C and C++ programs (__cdecl). It enables functions to accept variable numbers of arguments (such as printf). Use this when importing functions from custom C or C++ libraries that do not explicitly define a different convention.

[DllImport("crtdll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int printf(string format, int i, double d);

3. CallingConvention.StdCall

The callee cleans the stack. This convention (__stdcall) is the standard for calling Win32 API functions on x86 architectures. It does not support variable-length parameter lists.

[DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern bool SetCursorPos(int X, int Y);

4. CallingConvention.ThisCall

Used primarily for calling unmanaged C++ member functions on an exported class. The first parameter is passed via the CPU register ECX (representing the this pointer), and all other parameters are pushed to the stack.

5. CallingConvention.FastCall

Specifies that the first few arguments are placed into processor registers rather than pushed onto the stack, providing a slight performance advantage. This convention (__fastcall) is rarely used in P/Invoke because register mapping is not standardized across different C++ compilers.