Click here to Skip to main content
15,883,705 members
Articles / Programming Languages / C#
Article

Essential P/Invoke

Rate me:
Please Sign up or sign in to vote.
4.70/5 (47 votes)
31 Oct 20054 min read 177.4K   97   16
The article aims to shed some light on an irksome topic, in managed code, named P/Invoke.

Introduction

The article aims to shed some light on an irksome topic, in managed code, named P/Invoke. The article contains a useful table of how to translate managed to unmanaged types, short code examples (in C#), tips and a list of resources.

You will not find here confound theoretical information about what P/Invoke is, but an essential knowledge. The article is by no means a complete guide and I am not pretending to be an expert in this filed, only one with some experience.

Lazy development

Working in a managed environment like the .NET Framework is fun. With cool wrapper classes like Thread and Environment, our life becomes easy. But as soon as we start to feel lazy, the need for P/Invoke pops up (d-a-m-n!).

In a nutshell, P/Invoke (Platform Invoke) is Microsoft's way to get lazy, by not having to wrap all the Win32 APIs. This leaves, us, developers with some work to do. For example if you feel the need to share an Event between two processes (event is a named kernel object) you will be surprised (or not) to know that C# does not include this feature. For that reason, and many more, you need P/Invoke.

CreateEvent, Win API, as implemented in kernel32.dll:

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);

C# P/Invoke code:

C#
[DllImport("kernel32.dll, SetLastError=true ")]
static extern IntPtr CreateEvent(IntPtr lpEventAttributes, 
       bool bManualReset, bool bInitialState, 
       [MarshalAs(UnmanagedType.LPStr) string lpName);

As you can see you need to declare the exact prototype with the static keyword (the only way to simulate a global method in C#), the extern keyword (confess to the CLR that the method is not implemented in the assembly) and the DllImport attribute.

Hmmm… At this point you might think that this is not too bad, but, as you will soon witness, using P/Invoke can be a real pain.

Everything might go just well till you face the need to call a complex API that dazzles and puzzles you. "With what kinds of types should I declare the prototype? How to call the imported method? What to do when allocation is required? What to do with structures?"

For example, the CreateFileMapping method (implemented in the kernel32.dll):

HANDLE CreateFileMapping(
  HANDLE hFile,
  LPSECURITY_ATTRIBUTES lpAttributes,
  DWORD flProtect,
  DWORD dwMaximumSizeHigh,
  DWORD dwMaximumSizeLow,
  LPCTSTR lpName
); 


[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateFileMapping(IntPtr hFile,
   IntPtr lpFileMappingAttributes, PageProtection flProtect, 
   uint dwMaximumSizeHigh,
   uint dwMaximumSizeLow, string lpName);

Tips

  • Use MarshalAs whenever the P/Invoke type is different from the one that the API requires (see the CreateEvent example).
  • The default marshaling type for strings in P/Invoke is LPTSTR. If the actual parameter type differs from the P/Invoke default then you will need to use the MarshalAs attribute in the function prototype declaration. For example, if a function receives the LPCTSTR parameter then we should use [MarshalAs(UnmanagedType.LPTStr)] strings.
  • Use CharSet.Auto in DllImport. This is important for strings. If the API works with Unicode and you don't use the auto attribute then the CLR will marshal the data as ANSI. For some reason Microsoft has decided not to use the auto attribute as default. The auto tells the CLR to figure out automatically what the preferred charset is.
  • Performance considerations: "… P/Invoke has an overhead of between 10 and 30 x86 instructions per call. In addition to this fixed cost, marshaling creates additional overhead. There is no marshaling cost between blittable types that have the same representation in managed and unmanaged code. For example, there is no cost to translate between int and Int32. For higher performance, it may be necessary to have fewer P/Invoke calls that marshal as much data as possible, rather than have more calls that marshal less data per call. Or somewhat more memorably: prefer a chunky over a chatty API."( MSDN).
  • Make sure you use the fixed keyword when passing managed allocation buffers to unmanaged code. When marshaling pointer to data, the garbage collector needs to be alerted not to mess with the allocated data, otherwise the unmanaged code might crash while trying to retrieve a corrupted memory addresses. The fixed keyword tells the GC to leave your allocated data (PIN), and hence not to compact it during generating collections.

Unmanaged to Managed type translation table

C/C++C#
HANDLE, LPDWORD, LPVOID, void*IntPtr
LPCTSTR, LPCTSTR, LPSTR, char*, const char*, Wchar_t*, LPWSTRString [in], StringBuilder [in, out]
DWORD, unsigned long, UlongUInt32, [MarshalAs(UnmanagedType.U4)]
boolbool
LP<struct>[In] ref <struct>
SIZE_Tuint
LPDWORDout uint
LPTSTR[Out] StringBuilder
PULARGE_INTEGERout ulong
WORDuInt16
Byte, unsigned charbyte
ShortInt16
Long, intInt32
floatsingle
doubledouble
NULL pointerIntPtr.Zero
UintUint32

Resources

Epilogue

Given the fact that in the current .NET framework (2.0 Beta) we still don't have wrappers for every Win32 API, using Platform Invoke (P/Invoke) is almost unavoidable.

You might not find all or exactly what you need from the above resources or from the entire article, but I am sure you will be able to learn from it and use what you have learned to find a solution to your needs. I encourage you to add your comments and promise to update the article periodically.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralCalling C++ dll by using C# wrapper method Pin
Ashoka the Great23-Mar-10 5:25
Ashoka the Great23-Mar-10 5:25 
I am facing a problem while using the ITMSDLL a dll of C++.


The following is the C# code defined in my Application where dll is used

public static string Get_Month_Mask(string theByt,int theMon, string theYr)
        {
            string aMByte = theByt;
            int aSt_Dow, aEn_Dow, aSt_Wk, aEn_Wk;
            DateTime aSDate = new DateTime(Convert.ToInt32(theYr),Convert.ToInt32(theMon),01);
            DateTime aEDate = aSDate.AddMonths(1).AddDays(-1.0);
            string aRetMask = "X".PadRight(120);
            int aRc;
            aSt_Dow = Utility.GetDayNumber(aSDate.DayOfWeek.ToString());
            aEn_Dow = Utility.GetDayNumber(aEDate.DayOfWeek.ToString());
            aSt_Wk = Utility.GetWeekOfTheYear(aSDate);
            aEn_Wk = Utility.GetWeekOfTheYear(aEDate);
            //dll object called here
            aRc = Utility.objCITMSDLL.GetMonthMask(out aRetMask,out aMByte, aSt_Dow, aSt_Wk, aEn_Dow, aEn_Wk);
            return aRetMask.ToString();
        }



And the below is the code used in ITMSDOTNETDLL where the wrapper method is written.


[DllImport("ITMSDLL.dll")]
internal static extern int get_month_mask(out string theMask,out string theByt, int theSt_Dow, int theSt_Week, int theEn_Dow, int theEn_Week);
 
public int GetMonthMask(out string theMask,out string theByt, int theSt_Dow, int theSt_Week, int theEn_Dow, int theEn_Week)
{
return CITMSDLLMethods.get_month_mask(out theMask, out theByt, theSt_Dow, theSt_Week, theEn_Dow, theEn_Week);
}

it throws error

The runtime has encountered a fatal error. The address of the error was at 0x79e71bd7, on thread 0xc94. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

if we use "ref" instead of "out" then it also throws same(above) error.


If anybody can help then please help me.

Thanks in Advance
Ashok Kumar Singh

AnswerRe: Calling C++ dll by using C# wrapper method Pin
Shawn-USA5-Jun-13 8:19
Shawn-USA5-Jun-13 8:19 
QuestionP/Invoke & Environment problem? Pin
fergara18-Sep-08 11:32
fergara18-Sep-08 11:32 
General32 64 bit Pin
hahnl8-Sep-07 5:05
hahnl8-Sep-07 5:05 
GeneralRe: 32 64 bit Pin
Shawn-USA5-Jun-13 8:31
Shawn-USA5-Jun-13 8:31 
QuestionHow to translate "const std::string" Pin
Lorenz C5-Jun-07 5:07
Lorenz C5-Jun-07 5:07 
AnswerRe: How to translate "const std::string" Pin
Cohen Shwartz Oren8-Jun-07 12:44
Cohen Shwartz Oren8-Jun-07 12:44 
GeneralRe: How to translate "const std::string" Pin
Shawn-USA5-Jun-13 8:26
Shawn-USA5-Jun-13 8:26 
Generalgood job and thanks Pin
James Ashley8-May-07 7:15
James Ashley8-May-07 7:15 
GeneralRe: good job and thanks Pin
Cohen Shwartz Oren8-Jun-07 12:46
Cohen Shwartz Oren8-Jun-07 12:46 
QuestionLPDWORD Pin
Jonathan L. Nazario13-Mar-07 13:11
Jonathan L. Nazario13-Mar-07 13:11 
AnswerRe: LPDWORD Pin
Cohen Shwartz Oren13-Mar-07 14:07
Cohen Shwartz Oren13-Mar-07 14:07 
QuestionHow get the DNS Domain name using PInvoke? Pin
Cohen Shwartz Oren5-Feb-07 23:24
Cohen Shwartz Oren5-Feb-07 23:24 
AnswerRe: How get the DNS Domain name using PInvoke? Pin
Cohen Shwartz Oren5-Feb-07 23:24
Cohen Shwartz Oren5-Feb-07 23:24 
GeneralNice Pin
S.B.8-Nov-05 10:18
S.B.8-Nov-05 10:18 
GeneralRe: Nice Pin
Cohen Shwartz Oren13-Mar-07 14:07
Cohen Shwartz Oren13-Mar-07 14:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.