P/Invoke is a way of calling C/C++ functions from a .NET program. It’s very easy to use. This article will cover the basics of using P/Invoke.
Note: This tutorial will focus on Windows and thus use Visual Studio. If you’re developing on another platform or with another IDE, adopting the things in this article should be easy enough.
Project Structure ∞
For this tutorial, we need a small project structure containing two projects:
- NativeLib : a C++ library project
-
PInvokeTest : a C# console project
To get you started real quick, you can download the project structure here:
If you’re not using Visual Studio 2010 (or don’t want to use the provided zip file), adopt the following settings.
For project NativeLib, go to the project settings and (for all configurations):
- under
C/C++
–>Preprocessor
–>Preprocessor Definitions
addMYAPI=__declspec(dllexport)
-
under
C/C++
–>Advanced
: changeCalling Convention
to__stdcall (/Gz)
For project PInvokeTest:
- Specify NativeLib as dependency for PInvokeTest. Right click on PInvokeTest and choose
Project Dependencies...
. Then select NativeLib and hitOK
. -
Change the
Output path
(under project settings:Build
) to../Debug
and../Release
for the differentConfiguration
s respectively.
Simple P/Invoke ∞
First, let’s create a native function called print_line()
.
Add a file called NativeLib.h
to NativeLib (or replace it contents):
#ifndef _NATIVELIB_H_ #define _NATIVELIB_H_ #ifndef MYAPI #define MYAPI #endif #ifdef __cplusplus extern "C" { #endif MYAPI void print_line(const char* str); #ifdef __cplusplus } #endif #endif // _NATIVELIB_H_
Then, add NativeLib.cpp
:
#include "NativeLib.h" #include <stdio.h> MYAPI void print_line(const char* str) { printf("%s\n", str); }
Now, let’s call this function from the PInvokeTest project. To do this, add the highlighted lines to Program.cs
:
1 2 3 4 56 7 8 9 1011 12 131415 16 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace PInvokeTest { class Program { static void Main(string[] args) { print_line("Hello, PInvoke!"); } [DllImport("NativeLib.dll")] private static extern void print_line(string str); } } |
The most important lines in this sections are lines 13 and 14. Here we’re specifying the C/C++ function to import into our .NET class. There are a couple of things to note about this:
- The modifier is
static extern
.extern
means that the function is imported from C/C++.static
is necessary because the function has no knowledge about the classProgram
. - The name of the function matches the name of C/C++ function.
- The type of parameter
str
is a .NET type (here:string
). P/Invoke automatically converts (also called: marshals) data types from .NET to C/C++ and the other way around. -
The attribute
[DllImport]
specifies the name of DLL file from which we import the function. Note: DllImport allows you to control almost every aspect of the import, like providing a different .NET method name or specifying the calling convention.
Now compile the project and it should print Hello, PInvoke!
to the console.
You can download the complete project here:
Troubleshooting ∞
There are a couple of things that can go wrong with P/Invoke.
Unable to load DLL ∞
You may get a DllNotFoundException
with an error message like “The specified module could not be found.”
As the error message suggests the DLL “NativeLib.dll” could not be found.
The problem here is that Visual Studio doesn’t copy native DLLs to the output directory of .NET projects.
Solution: Change the output directory of the .NET project (PInvokeTest) to match the output directory of the native project (NativeLib). In PInvokeTest‘s project settings under Build
choose ../Debug
and ../Release
for Output path
in the respective configuration.
Stack Imbalance ∞
You may get an error saying that a PInvokeStackImbalance was detected
.
The reason is most likely that the native library uses another calling convention then the .NET project. By default, C/C++ projects use the __cdecl
calling convention, whereas [DllImport]
uses __stdcall
by default.
Solution: Make sure the calling conventions match. Either:
- Specify the correct calling convention in
[DllImport]
, for example[DllImport("NativeLib.dll", CallingConvention=CallingConvention.Cdecl)]
- Change the default calling convention for the native project. This is done in the project settings under
C/C++
–>Advanced
–>Calling Convention
. -
Add the desired calling convention to the desired C/C++ functions, for example:
void __stdcall print_line(const char* str)
. This will only change the calling convention for these functions.
In most cases, it doesn’t matter what calling convention you use. There are some differences, though. You can read more about these differences in the Code Project article Calling Conventions Demystified (Section: Conclusion).
Portability ∞
On non-Windows systems you can use Mono to execute .NET applications. If you’re planning on supporting multiple platforms with your .NET code, I suggest you either:
- Don’t specify a file extension (
.dll
) in[DllImport]
, like[DllImport("NativeLib")]
. This way the appropriate file name will be chosen automatically. Note, however, that this only works as long as there is no dot in the file name (like inSystem.Network.dll
). -
Or: Always specify the full Windows file name (i.e. including file extension) and use Mono’s library mapping mechanism to map platform-dependent file names to Windows file names.
C++/CLI ∞
Besides P/Invoke, the other way of integrating C/C++ functions is using C++/CLI. Although C++/CLI performs better than P/Invoke it also has several drawbacks:
- You need to learn a new language (if you only know C#; even if you know C++ as well). See my C++/CLI Cheat Sheet for an overview.
-
C++/CLI is not supported by Mono; so you can use C++/CLI assemblies only on Windows.
Read On ∞
You can find more information about P/Invoke here:
Pingback: Help with using c++ class called with P/Invoke
Thanks a lot for this clear and coincise post…. The only small add I would suggest is to enable debug of unmanaged code from C#. To enable it the user should go to the “Debug” tab Check “Enable Unmanaged code debugging”. Best Regards
Good intro. However, some (like me) might wonder why and how the Unicode C# string winds up coming into the DLL as ASCII. Changing the C++ code to use WCHAR and wprintf results in an unusable string. In order to make this work, you have to specify the character set when constructing the DllImportAttribute class, as in
[DllImportAttribute("NativeLib.dll", CharSet = CharSet.Unicode)]
“The problem here is that Visual Studio doesnΓ’β¬β’t copy native DLLs to the output directory of .NET projects.”
This explains why I get cant find dll error for pinvoking used at large codebases.
For that, you can add a link to the unmanaged DLL as a file in the C# project, and set Build Action to None and Copy to Output Directory to Copy If Newer.
Hi, I still have the DllNotFoundException problem. Could you explain me exactly what I need to do to solve that problem?
Thanks
This is good. Please share us if you examples for Reverse PInvoke.
Pingback: A WPF GUI for a C++ DLL: A Complex PInvoke Construct | Clatter from the Byte Kitchen
Hope you guyes can help me.
I simply cant get this stuff working.
I have some vb code, which is used somewhere else in our organisation.
Now I need to use that particular dll from c#.
*********************
*** vb code snippet.
Const lenname = 32
Const lenstatus = 14
Type personType
FirstName As String * lenname
LastName As String * lenname
PersonStatus As String * lenstatus
End Type
Declare Function GetPersonInfo Lib “ksxn.DLL” Alias “GetPersonInfo” (ByVal a1 As String, a2 As kpersoninfotype,ByVal a3 As String) As Integer
*** vb code snippet.
*********************
*********************
*** My C# code snippet.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace UnManagedCS
{
class Program
{
/* GetPersonInfo */
DllImport(“ksxn.DLL”, SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetPersonInfo(string a1, out string a2, out string a3);
static void Main()
{
string a1 = “”;
string a2 = “”;
string a3 = “”;
Console.WriteLine(“GetPersonInfo {0} “, Convert.ToString(GetPersonInfo(a1, out a2, out a3)));
Console.ReadLine();
}
}
}
*** My C# code snippet.
*********************
Thanks in advance.
Good Article for the Beginners…
Helpful and handy, thanks for your nice blog,
Section “Stack Imbalance ” helped me especially. Had no Info what correct calling convention to use . Changed to Cdecl and it worked.
One small tip. In Visual Studio, enable native code debugging on the C# project and you can step in to C code from C#.
https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-debug-in-mixed-mode?view=vs-2019
Pingback: P/Invoke and .NET Target Framework – Windows Questions