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.