NET calls to unmanaged code (P/invoke and C++interop) [go]

Source: Internet
Author: User

Convert System::String to wchar_t* or char*

PtrToStringChars converting a string to native wchar_t * or char *
。 Because the CLR string is internal Unicode, this typically returns a Unicode wide string pointer. You can then convert it to a wide string

1. NET Interop
. NET cannot manipulate unmanaged code directly, you need to interoperate.
1.1 P/invoke
Many common Windows operations have managed interfaces, but there are many full Win32 parts that do not have managed interfaces. How does it work? Platform Invoke (P/invoke) is the most common way to accomplish this task. To use P/invoke, you can write a prototype that describes how to invoke a function, and then the runtime uses this information to make the call.

1.1.1 Enumerations and constants
Take MessageBeep () as an example. MSDN gives the following prototypes:
BOOL MessageBeep (
UINT utype//sound type
);
This may seem simple, but there are two interesting facts that can be found in the comments.
? First, the Utype parameter actually accepts a set of pre-defined constants.
? Second, the possible parameter values include-1, which means that although it is defined as a UINT type, int is more appropriate. For the Utype parameter, it is reasonable to use the enum type.
public enum Beeptype
{
Simplebeep =-1,
Iconasterisk = 0x00000040,
Iconexclamation = 0x00000030,
Iconhand = 0x00000010,
Iconquestion = 0x00000020,
Ok = 0x00000000,
}
[DllImport ("User32.dll")]
public static extern bool MessageBeep (Beeptype beeptype);
Now I can invoke it with the following statement: MessageBeep (beeptype.iconquestion);
If the constant is a different type (not int), you need to modify the base type of the enumeration type
Enum Name:type {...}
1.1.2 Treatment of ordinary structures
Sometimes I need to determine the battery condition of my laptop. The WIN32 provides power management functions for this purpose.
BOOL GetSystemPowerStatus (
Lpsystem_power_status Lpsystempowerstatus
);
This function contains a pointer to a struct that we have not processed. To work with structs, we need to define the structure in C #. We start with an unmanaged definition:
typedef struct _SYSTEM_POWER_STATUS {
BYTE ACLineStatus;
BYTE Batteryflag;
BYTE batterylifepercent;
BYTE Reserved1;
DWORD Batterylifetime;
DWORD BatteryFullLifetime;
} system_power_status, *lpsystem_power_status;
Then, you get the C # version by replacing C type with a C # type.
struct SystemPowerStatus
{
BYTE ACLineStatus;
BYTE Batteryflag;
BYTE BatteryLifePercent;
BYTE reserved1;
int batterylifetime;
int batteryfulllifetime;
}
This makes it easy to write a C # prototype:
[DllImport ("kernel32.dll")]
public static extern bool GetSystemPowerStatus (ref SystemPowerStatus SystemPowerStatus);
In this prototype, we use "ref" to indicate that the struct pointer will be passed instead of the structure value. This is the general method of handling the structure passed through the pointer.
This function works fine, but it's a good idea to define the ACLineStatus and Batteryflag fields as enums:
Enum Aclinestatus:byte
{
Offline = 0,
Online = 1,
Unknown = 255,
}
Enum Batteryflag:byte
{ ...}
Note that because the structure field is some bytes, we use byte as the base type of the enum.

1.1.3 structure that handles inline pointers
Sometimes the function we want to invoke is a struct that contains pointers, and for such arguments, how do we handle them?
struct Cxtest
{
Lpbyte PData; A pointer to a byte array
int Nlen; The length of the array
}
BOOL WINAPI xfunction (const cxtest &indata_, Cxtest &outdata_);
How do we go about calling in C #
struct Cxtest
{
Public Intprt PData;
public int nlen;
}
static extern bool Xfunction (ref [in] cxtest Indata_, ref cxtest Outdata_);
Here's a look at the specific call, set the array length to Ndatalen
Cxtest stin = new Cxtest (), StOut = new Cxtest ();
byte[] PIn = new Byte[ndatalen];
assigning values to arrays
Stin.pdata = Marshal.allochglobal (Ndatalen);
Marshal.Copy (pIn, 0, Stin.pdata, Ndatalen);
Stin.nlen = Ndatalen;
Stout.pdata = Marshal.allochglobal (Ndatalen);
Stout.nlen = Ndatalen;
Xfunction (ref stin, ref stOut);
byte[] POut = new Byte[ndatalen];
Marshal.Copy (Stout.pdata, POut, 0, Ndatalen);
// ....
Marshal.freehglobal (Stin.pdata);
Marshal.freehglobal (Stout.pdata);
The most important thing here is to be aware that Pdata's memory needs to be applied first, then copied to the data, and finally the memory to release the application.

1.1.4 working with nested arrays and string structures
The definition and implementation under C + +:
struct Cxtest
{
WCHAR wzname[64];
int Nlen;
BYTE bydata[100];
}
BOOL Settest (const cxtest &sttest_);
In C #, to facilitate the initialization of a byte array, we use classes instead of structs
[StructLayout (LayoutKind.Sequential, pack=2, CharSet=CharSet.Unicode)]
Class Cxtest
{
public void Init ()
{
StrName = "";
Nlen = 0;
Bydata = new BYTE[100];
}
[MarshalAs (UnmanagedType.ByValTStr, SizeConst = 64)]
public string StrName;
public int nlen;
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 100)]
Public byte[] Bydata;
}
stataic extern bool Settest (cxtest sttest_);
After the definition, although the space reserved for bydata, it points to null and cannot be copied for it. Because structs cannot customize default parameters, an init function is added or replaced by a class to initialize Bydata.
Getting data from the underlying interface must use a struct, and when the data is fetched from the underlying interface (out), the bydata automatically points to the actual content. When setting data to the underlying interface, you must first call Init when using a struct, and by ref, if it is a class, you cannot use ref adornments (in C #: Classes are placed by default in the heap, and structs are placed by default on the stack).

1.1.5 string and string buffers
There are two different string representations in Win32: ANSI, Unicode. Because P/invoke designers don't want you to worry about your platform, they provide built-in support to automatically use A or W version. If the function you call does not exist, the interop layer will find and use a or W version for you. However, the default character type for Interop is Ansi or single-byte, and if the unmanaged code is a wide character, you need to explicitly set CharSet to CharSet.Unicode.
The string type in. NET is an immutable type, which means that its value will always remain the same. For a function to copy a string value to a string buffer, the string will be invalid. Doing so at least destroys the temporary buffer that is created by the marshaler when the string is converted, and when it is severely destroys the managed heap, which usually results in an error. It is not possible to get the correct return value in either case.
To resolve this issue, we need to use a different type. The StringBuilder type is designed to be used as a buffer, and we will use it instead of a string. Here is an example:
C-Format Function declaration:
DWORD GetShortPathName (
LPCTSTR Lpszlongpath,
LPTSTR Lpszshortpath,
DWORD Cchbuffer
);
Encapsulated in C #
[DllImport ("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetShortPathName (
[MarshalAs (UNMANAGEDTYPE.LPTSTR)]
String path,
[MarshalAs (UNMANAGEDTYPE.LPTSTR)]
StringBuilder ShortPath,
int shortpathlength);

Using this function is simple:
StringBuilder ShortPath = new StringBuilder (80);
int result = GetShortPathName (
@ "D:\dest.jpg", ShortPath, shortpath.capacity);
string s = shortpath.tostring ();
Note that the capacity of StringBuilder passes the buffer size.

1.1.6 pointer parameters
Many Windows API functions use pointers as one or more of their parameters. Pointers increase the complexity of marshaling data because they add an indirection layer. If there is no pointer, you can pass the data through the value of the thread stack. With pointers, you can pass data by reference by pushing the memory address of that data into the thread stack. The function then accesses the data indirectly through the memory address. There are several ways to represent this additional layer of indirection using managed code.

Marshaling opaque (Opaque) pointers: A special case
Sometimes in the Windows API, a method passes or returns a pointer that is opaque, which means that the pointer value is technically a pointer, but the code does not use it directly. Instead, the code returns the pointer to Windows for later reuse. A very common example is the concept of a handle.
When an opaque pointer is returned to your application (or if your application expects an opaque pointer), you should marshal the parameter or return value to a special type of-system.intptr in the CLR. When you use the INTPTR type, the out or ref parameters are usually not used, because IntPtr means to hold the pointer directly. However, if you marshal a pointer to a pointer, it is appropriate to use the By-ref parameter for INTPTR.
In the CLR type system, the System.IntPtr type has a special property. Unlike other base types in the system, INTPTR does not have a fixed size. Instead, its size at run time depends on the normal pointer size of the underlying operating system. This means that in 32-bit Windows, the width of the INTPTR variable is 32 bits, whereas in 64-bit Windows, the code compiled by the real-time compiler will treat the INTPTR value as a 64-bit value. This auto-sizing feature is useful when you are marshaling opaque pointers between managed and unmanaged code.
You can cast the INTPTR value to either a 32-bit or 64-bit integer value in managed code, or cast the latter to the former. However, when using Windows API functions, because pointers should be opaque, they cannot be used in addition to storing and passing to external methods. The two exceptions to this "store-and-pass-only" rule are when you need to pass a null pointer value to an external method and you need to compare the INTPTR value with a null value. To do this, you cannot cast 0 to System.IntPtr, and you should use the Int32.zero static public field on the IntPtr type.

1.1.7 callback function
When the Win32 function needs to return multiple data, it is usually done through a callback mechanism. The developer passes the function pointer to the function and then invokes the developer's function for each item.
Instead of a function pointer in C #, a delegate is used instead of a function pointer when the WIN32 function is called. The EnumDesktops () function is an example of such a function:
BOOL EnumDesktops (
Hwinsta Hwinsta,//Handle of Window instance
Desktopenumproc Lpenumfunc,//callback function
LPARAM lparam//The value used for the callback function
);
The Hwinsta type is replaced by INTPTR, while LPARAM is replaced by Int. Desktopenumproc needs a little more work. The following is the definition in MSDN:
BOOL CALLBACK EnumDesktopProc (
LPTSTR lpszdesktop,//Desktop name
LPARAM lparam//user-defined values
);
We can convert it to the following delegate:
delegate BOOL EnumDesktopProc (
[MarshalAs (UNMANAGEDTYPE.LPTSTR)]
String Desktopname,
int lParam);

With that definition in order, we can write the following definition for EnumDesktops ():
[DllImport ("user32.dll", CharSet = CharSet.Auto)]
static extern bool EnumDesktops (
IntPtr Windowstation,
EnumDesktopProc Callback,
int lParam);
This will allow the function to run normally.

There is an important trick to using delegates in Interop: The marshaler creates a function pointer to the delegate that is passed to the unmanaged function. However, the marshaler cannot determine what the unmanaged function will do with the function pointer, so it assumes that the function pointer is only valid when the function is called.
Therefore, if the delegate is saved for later use after a function call such as Setcallback (), the managed code needs to ensure that the delegate reference is valid (not reclaimed) when the delegate is used, in which case it is generally set to global.

Additional options for the 1.1.8 property
The DLLImport and StructLayout properties have some very useful options to help with the use of P/invoke. In addition, the return value can be decorated with the return property.
DLL Import Property
In addition to pointing out the host DLL, DllImportAttribute contains optional properties, four of which are particularly interesting: entrypoint, CharSet, SetLastError, and CallingConvention.
? EntryPoint: In cases where you do not want the external managed method to have the same name as the DLL export, you can set the property to indicate the entry point name of the exported DLL function. This is especially useful when you define two external methods that call the same unmanaged function.
? CharSet: If the DLL function does not handle the text in any way, you can omit the DllImportAttribute CharSet property. However, when Char or String data is part of an equation, you should set the CharSet property to CharSet.Auto. This allows the CLR to use the appropriate character set based on the host OS. If the CharSet property is not explicitly set, its default value is CharSet.Ansi.
? SetLastError: Set to True causes the CLR to cache errors set by the API function after each call to an external method. Then, in the wrapper method, you can get the cached error value by calling the System.Runtime.InteropServices.Marshal.GetLastWin32Error method. It then examines these expected error values from the API functions and throws a perceptible exception for those values. For all other failure conditions (including failure scenarios that are not expected at all), the System.ComponentModel.Win32Exception exception is thrown and the value returned by Marshal.GetLastWin32Error is passed to it.
? CallingConvention: With this property, you can give the CLR an indication of which function calling convention should be used for parameters in the stack. The default value of Callingconvention.winapi is the best choice, and it works in most cases. However, if the call does not work, you can examine the declaration header file in the Platform SDK to see if the API function you are calling is an exception API that does not conform to the calling convention standard.

StructLayout Property
? LayoutKind: Structs are laid out sequentially by default, and in most cases apply. If you need full control over where the struct members are placed, you can use layoutkind.explicit and then add the FieldOffset property for each struct member. This is usually required when you need to create a union.
? CharSet: Controls the default character type for BYVALTSTR members.
? Pack: Sets the compression size of the structure. It controls how the structure is arranged. You may need to set this property if the C structure uses other compression methods.
? Size: Sets the size of the structure. Not commonly used, but this property may be used if you need to allocate additional space at the end of the structure.

return value
The return value modifies the type returned, which is generally the type of bool that needs to be processed.
[DllImport ("user32.dll", SetLastError = True)]
[Return:marshalas (Unmanagedtype.bool)]
private static extern bool Getlastinputinfo (ref Xlastinputinfo Stinfo_)

1.1.9 Other issues
Loading from different locations
You cannot specify where you want DLLImport to look for files at run time, but you can use a technique to achieve this.
DllImport calls LoadLibrary () to complete its work. If a specific DLL is already loaded in the process, LoadLibrary () will succeed even if the specified load path is different.
This means that if you call LoadLibrary () directly, you can load the DLL from any location, and then DllImport LoadLibrary () will use the DLL.
Because of this behavior, we can call LoadLibrary () ahead of time to point your calls to other DLLs. If you are writing a library, you can prevent this by calling GetModuleHandle () to ensure that the library is not loaded before the first call to P/invoke.

P/invoke Troubleshooting
If your P/invoke call fails, it is usually because some types are not defined correctly. Here are a few FAQs:
? Long! = long. In C + +, Long is a 4-byte integer, but in C #, it is a 8-byte integer.
? The string type is not set correctly.

For very complex structures, it is still difficult to handle through P/invoke, which is considered to be handled using C + + Inerop.

1.2 C + + Interop
Using P/invoke can marshal most of the operations, but it is cumbersome to handle complex operations and cannot handle exceptions (the actual information of the original exception cannot be obtained). At the same time, interop performance is generally better.
1.2.1 Managed Types
Classes, structs, enumerations, etc. under C + + cannot be used directly under managed C + + and require the use of managed classes, structs, and Enum types: ref class, ref struct, and enum class.
Pointers and references under C + + do not have to be replaced with a tracking handle (^) and a tracking reference (%) under managed C + +. Similarly, arrays and strings need to be replaced by: string^ and array<type>^.
Constants under managed C + + need to be decorated with literal.
string^ strverb=nullptr; Cannot use NULL directly
array<string^>^ strnames={"Jill", "Tes"};
array<int>^ Nweight = {130, 168};
int nvalue = 10;
int% Ntrackvalue=nvalue;
literal int namemaxlen = 64;

When defining a struct, you need to modify it with the StructLayout and Marshal Properties, as an example of the following C + + struct body:
#pragma pack (push, Mypack_h, 4)
struct CPPSTRUCT
{
Public
BOOL Bvalid;
DWORD ncount;
Large_integer Linumber;
WCHAR WZNAME[10];
BYTE bybuff[100];
Cppsubstruct stsub;
}
#pragma pack (pop, mypack_h)
The corresponding. NET definition
[StructLayout (layoutkind::sequential, Pack = 4, CharSet = Charset::unicode)]
ref struct MYSTRUCT
{
Public
MyStruct ()
{
You must first use gcnew to allocate space to the struct, and the string does not need
Bybuff = gcnew array<unsigned char> (100)
Stsub = gcnew mysubstruct ();
}
[MarshalAs (Unmanagedtype::bool)]
BOOL Bvalid;
int ncount;
Long Long llnumber;
[MarshalAs (unmanagedtype::byvaltstr, SizeConst = 10)]
string^ StrName;
[MarshalAs (Unmanagedtype::byvalarray, SizeConst = 100)]
array<unsigned char>^ Bybuff;
[MarshalAs (Unmanagedtype::struct)]
mysubstruct ^ stsub;
};


1.2.2 String and array conversions
Managed strings and arrays can be converted to unmanaged strings and arrays through the pin_ptr in <vcclr.h>:
Pin_ptr<const wchar_t> pkeysn = PtrToStringChars (strkeysn_)
wchar_t wzuser[clen::ckeysnlen+1];
GETNAMEBYSN (PKEYSN, Wzuser);
Return gcnew String (wzuser);
When you convert a string, you need to use PtrToStringChars to get the pointer, and if it is an array, use the address of the first element directly (&elments[0]), but if the array pointer is empty, set _xptr to be a managed array pointer (such as array <unsigned char>^ Bybuffer):
((nullptr = = _xptr) | | (0 = = _xptr->length)) ? NULLPTR: &_xptr[0])
Array operations:
int GetInfo (IntPtr hhandle, [out] array<unsigned char>^%byinfo)
{
int nlen = 100;
array<unsigned char>^ bykey = gcnew array<unsigned char> (100);
pin_ptr<unsigned char> pbuff = &byKey[0];
int ncount = Cppgetinfo (Hhandle.topointer (), Pbuff, Nlen);

Byinfo = gcnew array<unsigned char> (Nlen);
Array::copy (Bykey, Byinfo, Nlen);
return ncount;
}
To be able to return bymysubstruct, you must use a trace reference (%).
Managed memory uses gcnew to request (no manual release) and then uses PIN_PTR to convert to an unmanaged pointer (which, of course, can also be used in place of pbuffer[100] to copy unmanaged content to the managed digital clock via copy); ToPointer () To get an unmanaged pointer.

1.2.3 Callback function
Statement
[Unmanagedfunctionpointer (Callingconvention::stdcall)]
delegate int Callbackfun (...);
Set (c + + declaration Cppcallbackfun for Callbackfun)
void Setcallback (callbackfun^ delfun_)
{
IntPtr ptrcallback = marshal::getfunctionpointerfordelegate (Delfun_);
Cppsetcallback (Static_cast<cppcallbackfun> (Ptrcallback.topointer ()));
}

1.2.4 Exception Handling
An unmanaged exception cannot be used in a managed program, and an unmanaged exception must be captured before being converted to a managed exception.
Set Cppexception to exception under C + +, dotnetexception (requires inheritance of standard exceptions, such as ApplicationException, exception, etc.) as managed exceptions
Try
{
......
}
catch (Cppexception &ex)
{
Throw gcnew dotnetexception (gcnew String (ex. Getmsg ()), ex. GetCode ());
}
When capturing C + + exceptions, you need to use references to prevent truncation; The newly thrown managed exception needs to be gcnew out.

NET calls to unmanaged code (P/invoke and C++interop) [go]

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.