Transmission Door
C # Interop Series articles:
- C # Interoperability Primer Series (i): Introduction to Interoperability in C #
- C # Interoperability Primer series (ii): Invoke the Win32 function using platform invoke
- C # Interoperability Primer Series (iii): data marshaling in platform invoke
- C # Interoperability Primer Series (iv): Calling COM components in C #
Summary of the topic
- Introduction to data marshaling
- Marshaling Win32 data types
- Handling of marshaling strings
- Handling of marshaling structures
- Handling of marshaling classes
- Summary
First, data marshaling introduction
When you see this topic, your first question must be-what is data marshaling? ( this series of topics using hypothetical friends to explain the concept of the question, is to hope that everyone with questions to learn the topic content, as well as everyone in the normal learning process can also be used in this way, the personal feel that this way can make their learning efficiency has improved, even so in the learning process may appear slow, But this approach will have a deeper impression on the knowledge you have seen. Far faster than watching, and finally found that there is not much to remember, here to share the learning method, that the acceptable friends can be in peacetime learning can be tried, if you feel bad, I believe you will certainly have their own better way of learning. The explanation for this is that data marshaling is the process by which data is passed between managed and unmanaged memory through the method's parameters and return values when interoperating with unmanaged functions in managed code, which is the CLR (Common language Runtime) Marshaling service (that is, the marshaler) is completed .
The marshaler mainly carries out 3 tasks:
- Convert data from a managed type to an unmanaged type, or from an unmanaged type to a managed type
- Copy type-converted data from managed-code memory to unmanaged memory or from unmanaged memory to managed memory
- When the call is complete, releases the memory allocated during the marshaling process
Ii. Marshaling Win32 data types
When interoperating with unmanaged code, there is bound to be marshaling of data. There are, however, two types of data that need to be processed during marshaling-a type that can be copied directly into a native structure (blittable) and a type that is not directly copied into the native structure (non-bittable). Here is an introduction to each of these two data types.
2.1 Types that can be copied directly into a native structure
Because in managed and unmanaged code, data types are not represented in the same way as managed memory and unmanaged memory, because of this, we need to marshal the data so that when we call an unmanaged function in managed code, Pass the correct incoming parameters to the unmanaged function and return the correct return value to the managed code. However, not all data types are represented differently in memory, when we refer to data types that have the same representation in managed memory and unmanaged memory-types that can be copied directly into a native structure. These data types do not require any special processing by the marshaler to be passed between managed and unmanaged code , and the following lists some simple data types that are copied directly into the native structure:
Windows Data types
unmanaged data types
Managed data types
Managed Data Type Interpretation
Byte/uchar/uint8
unsigned char
System.Byte
Unsigned 8-bit integer
Sbyte/char/int8
Char
System.SByte
Signed 8-bit integer
Short/int16
Short
System.Int16
Signed 16-bit integer
Ushort/word/uint16/wchar
unsigned short
System.UInt16
Unsigned 16-bit integer
Bool/hresult/int/long
Long/int
System.Int32
Signed 32-bit integer
Dword/ulong/uint
unsigned long/unsigned int
System.UInt32
Unsigned 32-bit integer
Int64/longlong
_int64
System.Int64
Signed 64-bit integer
Uint64/dwordlong/ulonglong
_uint64
System.UInt64
Unsigned 64-bit integer
Int_ptr/handle/wparam
Void*/int or _int64
System.IntPtr
Signed pointer type
HANDLE
void*
System.UIntPtr
Unsigned pointer type
FLOAT
Float
System.Single
Single-precision floating-point number
DOUBLE
Double
System.Double
Double-precision floating-point number
In addition to the simple types listed in the table above, there are also replication types that are data types that can be copied directly into the native structure:
(1) The data element is a unary array that can be copied directly into the native structure, such as an integer array, a floating-point group, etc.
(2) contains only formatted value types that can be copied directly into the native structure
(3) member variables are all types that can be copied into a native structure and marshaled as a formatted type
The formatting mentioned above refers to--when a type is defined, the memory layout of a member is explicitly specified at the time of declaration. Decorate the specified type with the StructLayout property in the code, and set the StructLayout LayoutKind property to sequential or explicit, for example:
The struct below the using system.runtime.interopservices;//also belongs to a type that can be copied directly into the native structure [StructLayout (layoutkind.sequential)]public struct Point {public int x; public int y;}
2.2 Types that are not directly copied to the native structure
If a type is not a type that can be copied directly into the native structure, it is a type that is not directly copied to the native structure. Because some types behave differently in managed memory and unmanaged memory, for this type, the marshaler needs to convert them to the called function after the appropriate type conversion , listing some data types that are not directly copied to the native structure:
Windows Data types
unmanaged data types
Managed data types
Managed Data Type Interpretation
Bool
bool
System.Boolean
Boolean type
Wchar/tchar
char/wchar_t
System.Char
ANSI character/unicode character
Lpcstr/lpcwstr/lpctstr/lpstr/lpwstr/lptstr
Const CHAR*/CONST wchar_t*/char*/wchar_t*
System.String
ANSI string/unicode string that, if unmanaged code does not need to update this string, declares the string type in managed code with string type
Lpstr/lpwstr/lptstr
char*/wchar_t*
System.stringbuilder
ANSI string/unicode string, if unmanaged code needs to update this string and then pass the updated string back to managed code, then declare the string in managed code with the StringBuilder type
In addition to the types listed in the previous table, there are many other types that belong to types that are not directly copied into the native structure, such as other pointer types and handle types. After understanding the differences between the blittable and non-blittable types, you can better handle the marshaling of the data in the interop process, with a brief introduction to some of the specific data type marshaling issues
Third, the processing of the marshaling string
In the previous topic, we have already dealt with the marshaling of strings (the previous topic used the string as an in parameter to the Win32 MessageBox function to see the previous topic). So in this section will introduce--marshal as the return value of the string, here is a demo code, the code is mainly called the Win32 GetTempPath function to get the return to return the temporary path, at this time the device will need to send back the returned string to the managed code.
The example of the return value in a managed function that is marshaled back to a managed function is defined by the class program {//Win32 GetTempPath function as follows://dword WINAPI GetTempPath (//_in_ D WORD nbufferlength,//_out_ LPTSTR lpbuffer//);//focus on how to define the function prototype in managed code
[DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, setlasterror=true)] public static extern uint GetTempPath (int Bufferlength, StringBuilder buffer); static void Main (string[] args) {StringBuilder buffer = new StringBuilder (300); UINT Temppath=gettemppath (+, buffer); String path = buffer. ToString (); if (TempPath = = 0) {int errorcode =marshal.getlastwin32error (); Win32Exception win32expection = new Win32Exception (errorcode); Console.WriteLine ("An exception occurred calling an unmanaged function, the exception information is:" +win32expection.) Message); } Console.WriteLine ("Call the unmanaged function successfully. "); Console.WriteLine ("Temp path is:" + buffer); Console.read (); } }
The result of the operation is:
Iv. handling of the marshaling structure
When we actually call the Win32 API function, we often need to marshal the struct and class, such as the type of replication, the following is the Win32 function GetVersionEx as an example to demonstrate how to marshal the structure as a parameter. In order to invoke unmanaged code in managed code, first we need to know the definition of the unmanaged function, the following is the GetVersionEx unmanaged definition (more information about the function can be found on the MSDN link: http://msdn.microsoft.com/en-us/ library/ms885648.aspx):
BOOL GetVersionEx (
The parameter lpversioninformation is a pointer type that points to the osversioninfo struct, so we must know before we getversionex the function in managed code for the function osVersionInfo the unmanaged definition of the struct, and then defines an equivalent struct type as a parameter in managed code. The following is an unmanaged definition of the OSVERSIONINFO struct:
typedef struct _osversioninfo{ DWORD dwosversioninfosize; This is initialized to the size of the structure DWORD dwmajorversion before using GetVersionEx ; System major Version number DWORD dwminorversion; System Minor version number DWORD dwbuildnumber; System build number DWORD dwPlatformId; System-supported Platforms TCHAR szcsdversion[128]; The name of the system patch pack WORD wservicepackmajor; The major version of the System fix pack WORD Wservicepackminor; The minor version of the system patch pack WORD wsuitemask; Identify the program groups on the system BYTE Wproducttype; Identity system type BYTE wreserved; reserved, not used} osVersionInfo;
Now that we know the definition of the osversioninfo struct in unmanaged code, we need to define an equivalent structure in managed code and make sure that two structs are laid out in memory. The struct body in managed code is defined as follows:
Because the Win32 GetVersionEx function parameter lpversioninformation is a data structure that points to the OSVERSIONINFO //So the struct is defined in managed code and the struct object as an unmanaged function parameter [StructLayout (Layoutkind.sequential,charset=charset.unicode)] public struct OSVERSIONINFO {public UInt32 osversioninfosize;//structure size, before calling the method to initialize the field public UInt32 MajorVersion; System major Version number public UInt32 minorversion;//System This version number public UInt32 BuildNumber; System build number public UInt32 PlatformID; Platform supported by System //This property is used to indicate that it is marshaled to an inline array [MarshalAs (unmanagedtype.byvaltstr,sizeconst=128)] public string CSDVersion; The name of the system patch package is public UInt16 servicepackmajor;//The main version of the system patch package public UInt16 Servicepackminor; Minor version of the system patch package public UInt16 Suitemask; Identify the program group on the system public Byte ProductType; Identity system type Public Byte Reserved; Reserved, not used }
As can be seen from the above definition, the structure defined in managed code has the following three aspects that are identical to those in unmanaged code:
- Order of field declarations
- Type of field
- The size of the field in memory
And in the definition of the structure above, we used the StructLayout property, which belongs to the System.Runtime.InteropServices namespace (so you must add this extra namespace when using platform invoke technology). The function of this class is to allow the developer to explicitly specify the memory layout of the data fields in the struct or class, to ensure that the data fields in the struct are in the same order as they were defined in memory, so it is specified as layoutkind.sequential(which is also the default value). The following is a concrete look at the code that is called in managed code:
Using system;using system.componentmodel;using system.runtime.interopservices;namespace marshaling the struct body {class Program { To GetVersionEx a managed definition//In order to pass a pointer to a struct and pass the initialized information to the unmanaged code, you need to decorate the parameter with the REF keyword
The Out keyword cannot be used here, and if the Out keyword is used, the CLR does not initialize the parameter, which causes the call to fail
[DllImport ("Kernel32", charset=charset.unicode,entrypoint= "GetVersionEx")] private static extern Boolean Get Versionex_struct (ref osversioninfo OSVERSIONINFO); Because the Win32 GetVersionEx function parameter lpversioninformation is a data structure that points to the OSVERSIONINFO//So the struct is defined in managed code and the struct object as an unmanaged function parameter [St Ructlayout (Layoutkind.sequential,charset=charset.unicode)] public struct OSVERSIONINFO {public U Int32 osversioninfosize; The size of the structure, before calling the method to initialize the field public UInt32 majorversion; System major Version number public UInt32 minorversion; System This version number public UInt32 BuildNumber; System build number public UInt32 PlatformID; Platform supported by System//This property is used to indicate that it is marshaled to an inline array [MarshalAs (unmanagedtype.byvaltstr,sizeconst=128)] public String csdversion; Name of the system Patch pack public UInt16 servicepackmajor; The main version of the system patch package public UInt16 Servicepackminor; Minor version of the system patch package public UInt16 suitemask; Identify the program group on the system public Byte ProdUcttype; Identity system type public Byte Reserved; reserved, not used}//Get operating System Information private static string Getosversion () {//define a string to store version information String versionname = String. Empty; Initializes a struct object osversioninfo osversioninformation = new osVersionInfo (); Before calling the GetVersionEx method, you must use the sizeof method to set the Osversioninfosize member Osversioninformation.osversioninfosize = (UInt32) Ma in the struct body Rshal. SizeOf (typeof (osVersionInfo)); Call the Win32 function Boolean result = getversionex_struct (ref osversioninformation); if (!result) {//If the call fails, get the last error code int errorcode = Marshal.GetLastWin32Error (); Win32Exception win32exc = new Win32Exception (errorcode); Console.WriteLine ("The error message that the call failed is:" + win32exc.message); The call fails when returned as an empty string return string. Empty; } else {Console.WriteLine ("Call into"); Switch (osversioninformation.majorversion) {//Here is only a discussion of the major version number 6, as discussed in the other case Case 6:switch (osversioninformation.minorversion) { Case 0:if (Osversioninformation.producttype = = (Byte) 0) {versionname = "Microsoft Windows Vista"; } else {versionname = "M Icrosoft Windows Server 2008 "; Server version} break; Case 1:if (Osversioninformation.producttype = = (Byte) 0) { Versionname = "Microsoft Windows 7"; } else {versionname = "Microsoft Wi Ndows Server R2 "; } break; Case 2:versionname = "Microsoft Windows 8"; Break } break; Default:versionname = "Unknown operating system"; Break } return versionname; }} static void Main (string[] args) {string os=getosversion (); Console.WriteLine ("The current computer is installed with the operating system: {0}", OS); Console.read (); } }}
The result of the operation is:
Attach the Microsoft operating system name and version number of the corresponding relationship, you can refer to the following table for the above code for other discussion:
Operating system
Version number
Windows 8
6.2
Windows 7
6.1
Windows Server R2
6.1
Windows Server 2008
6.0
Windows Vista
6.0
Windows Server 2003 R2
5.2
Windows Server 2003
5.2
Windows XP
5.1
Windows 2000
5.0
V. Handling of the class of marshaling
The following is an example of marshaling a class directly through the GetVersionEx function, with the following code:
Using system;using system.componentmodel;using system.runtime.interopservices;namespace Marshaling class Processing {class Program { Managed definitions for GetVersionEx//Because string is csdversion in the definition of a class, string is copied directly to the native struct type,//So the marshaler requires a copy operation. For unmanaged code to be able to get the initial value set in managed code (referring to the Osversioninfosize field, first initializing the value before calling the function),//So the [in] property must be added; When the function returns, in order to copy the result to the managed object, you must also add the [Ou T] attribute//cannot be used with the REF keyword, because OSVERSIONINFO is a class type, which is a reference type, if the REF keyword is a pointer passed in as a pointer, it will cause the call to fail
[DllImport ("Kernel32", CharSet = CharSet.Unicode, EntryPoint = "GetVersionEx")] private static extern Boole An getversionex_struct ([in, out] osversioninfo osversioninfo); Get operating System Information private static string Getosversion () {//define a string to store operating system information string Versionna Me = string. Empty; Initializes an object of class osversioninfo osversioninformation = new osVersionInfo (); Call the Win32 function Boolean result = Getversionex_struct (osversioninformation); if (!result) {//If the call fails, get the last error code int errorcode = Marshal.GetLastWin32Error (); Win32Exception win32exc = new Win32Exception (errorcode); Console.WriteLine ("The error message that the call failed is:" + win32exc.message); The call fails when returned as an empty string return string. Empty; } else {Console.WriteLine ("Call succeeded"); Switch (osversioninformation.majorversion{//Here is only the case where the major version number is 6, and the other case is the same as that discussed in 6:SWI TCH (osversioninformation.minorversion) {case 0: if (Osversioninformation.producttype = = (Byte) 0) { Versionname = "Microsoft Windows Vista"; } else {versionname = "M Icrosoft Windows Server 2008 "; Server version} break; Case 1:if (Osversioninformation.producttype = = (Byte) 0) { Versionname = "Microsoft Windows 7"; } else { Versionname = "Microsoft Windows Server R2"; } break; Case 2:versionname = "Microsoft Windows 8"; Break } break; Default:versionname = "Unknown operating system"; Break } return versionname; }} static void Main (string[] args) {string OS = Getosversion (); Console.WriteLine ("The current computer is installed with the operating system: {0}", OS); Console.read (); }} [StructLayout (layoutkind.sequential, CharSet = CharSet.Unicode)] public class osVersionInfo {Publi C UInt32 osversioninfosize = (UInt32) marshal.sizeof (typeof (osVersionInfo)); Public UInt32 MajorVersion = 0; Public UInt32 minorversion = 0; Public UIntBuildNumber = 0; Public UInt32 platformid = 0; This property is used to denote that it is marshaled to an inline array [MarshalAs (UnmanagedType.ByValTStr, SizeConst = $)] public string csdversion = null; Public UInt16 servicepackmajor = 0; Public UInt16 Servicepackminor = 0; Public UInt16 suitemask = 0; Public Byte producttype = 0; Public Byte Reserved; }}
The result of the operation is the same as the structure defined above, or it is attached:
Vi. Summary
This topic focuses on several types of data marshaling, one sentence for marshaling is to ensure that the data defined in managed code is laid out in memory in the same way as the memory layout in unmanaged code. Some simple types are also listed in the corresponding relationships defined in unmanaged and managed code, and can be defined in managed code using a universal IntPtr type for some pointer types or callback functions that are not listed. However, the topic is just a primer on data marshaling, To really master the data marshaling to consider a lot of other factors, this need everyone in the usual work accumulated.
Go C # Interoperability Primer Series (iii): data marshaling in platform invoke