P/Invoke and Marshaling

Background--Platform Invoke Tutorial

NET Framework supports the Platform Invoke (P/Invoke) service. This service allows managed code to invoke unmanaged functions residing in DLLs. 
Using the P/Invoke service in the .NET Compact Framework includes three primary steps: declaration, invocation, and error handling.

There are two ways that C# code can directly call unmanaged code:

For both techniques, you must provide the C# compiler with a declaration of the unmanaged function, and you may also need to provide the C# compiler with a description of how to marshal the parameters and return value to and from the unmanaged code.

Calling a DLL Export Directly from C#

To declare a method as having an implementation from a DLL export, do the following:

  • Declare the method with the static and extern C# keywords.
  • Attach the DllImport attribute to the method. The DllImport attribute allows you to specify the name of the DLL that contains the method. The common practice is to name the C# method the same as the exported method, but you can also use a different name for the C# method. To use a different name for the C# method, you must use the EntryPoint option in the DllImport attribute.
  • Optionally, specify custom marshaling information for the method's parameters and return value, which will override the .NET Framework default marshaling.

The MarshalAs attribute can be placed on method parameters, method return values.

https://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx

Introduction

The article aims to shed some light on an topic, in managed code, named P/Invoke, which allow managed code to invoke functions residing in unmanaged DLLs. 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, 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:
[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 if needed.  MarshalAsAttribute indicates how to marshal the data between managed and unmanaged code. This attribute is optional, as each data type has a default marshaling behavior. This attribute is only necessary when a given type can be marshaled to multiple types. For example, you can marshal a string to unmanaged code as either a LPStr, a LPWStr, a LPTStr, or a BStr. By default, the common language runtime marshals a string parameter as a BStr to COM method. You can apply the MarshalAsAttribute attribute to an individual field or parameter to cause that particular string to be marshaled as a LPStr instead of a BStr. 
  • 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 is used in conjunction with the unsafe keyword, and is used to ensure that the common language runtime’s garbage collector (GC) leave your allocated data (PIN), while it is being accessed by the unmanaged function, hence not to compact it during generating collections.

 Note (about marshaling):

Blittable and Non-Blittable Types

Default Marshaling Behavior

链接两篇中文blog:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值