Technical notes on calling Windows API in C

  • 2020-06-12 10:17:55
  • OfStack

In the.Net Framework SDK documentation, the instructions for calling Windows API are scattered, and the slightly more comprehensive one is for Visual Basic.net. This article brings together the main points of calling API in C# to give some help to friends who have not used API #. Also, if you have Visual Studio. net installed, there are plenty of examples of calling API in the directory C:\Program Files\ Visual Studio. NET\FrameworkSDK\ Technologies\ PlatformInvoke\WinAPIs\CS.

1. Invocation format


 using System.Runtime.InteropServices; // Reference this namespace to simplify the code that follows 
...
// use DllImportAttribute Feature to introduce api Function, notice that the declared method is empty, that is, the body of the method is empty. 
[DllImport("user32.dll")]
public static extern ReturnType FunctionName(type arg1,type arg2,...);
// The invocation is no different from calling any other method  

You can use fields to step 1 to illustrate features, separated by commas, such as:

[ DllImport( "kernel32", EntryPoint="GetVersionEx" )]  

The common fields for the DllImportAttribute feature are as follows:
1. CallingConvention indicates the CallingConvention value used to pass method parameters to an unmanaged implementation.
CallingConvention.Cdecl: The caller cleans up the stack. It enables you to call functions with varargs.
CallingConvention.StdCall: The called party clears the stack. It is the default convention for calling unmanaged functions from managed code.

2. CharSet controls the name version of the calling function and indicates how to marshal the String parameter to the method.

This field is set to the value 1 of CharSet. If the CharSet field is set to Unicode, all string parameters are converted to Unicode characters before being passed to the unmanaged implementation. This also causes the letter "W" to be appended to the name of DLL EntryPoint. If this field is set to Ansi, the string is converted to ANSI and the letter "A" is appended to the name of DLL EntryPoint.

Most Win32 API use this convention to append "W" or "A". If CharSet is set to Auto, the conversion is platform-specific (Unicode on Windows NT, Ansi on Windows 98). The default value for CharSet is Ansi. The CharSet field is also used to determine which version of the function will be imported from the specified DLL.

CharSet. Ansi and CharSet. Unicode have very different name matching rules. For Ansi, return "MyMethod" if EntryPoint is set to "MyMethod" and it exists. If there is no "MyMethod" in DLL but "MyMethodA" exists, "MyMethodA" is returned.

The opposite is true for Unicode. If EntryPoint is set to "MyMethod" and it exists, "MyMethodW" is returned. If "MyMethodW" does not exist in DLL but "MyMethod" does, "MyMethod" is returned. If Auto is used, the matching rule is platform-dependent (Unicode on Windows NT and Ansi on Windows 98). If ExactSpelling is set to true, "MyMethod" is returned only if "MyMethod" exists in DLL.

3. EntryPoint indicates the name or serial number of the DLL entry point to be invoked.
If your method name does not want to have the same name as the api function, 1 must specify this parameter, for example:


[DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]
public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type); 

4. ExactSpelling indicates whether the name of the entry point in the unmanaged DLL should be changed to correspond to the CharSet value specified in the CharSet field. If true, appends the letter A to the method name when the DllImportAttribute.CharSet field is set to the Ansi value of CharSet, and when the DllImportAttribute.CharSet field is set to the Unicode value of CharSet. The default value for this field is false.

5. PreserveSig indicates that the managed method signature should not be converted into an unmanaged signature that returns HRESULT and may have an additional [out, retval] parameter corresponding to the return value.

6. SetLastError indicates that the caller will call Win32 API SetLastError before returning from the attributed method. true indicates that the caller will call SetLastError, which defaults to false. The runtime marshaler calls GetLastError and caches the returned value in case it is overridden by another API call. Users can retrieve the error code by calling GetLastWin32Error.

2. Parameter type:

1. The corresponding numerical type can be used directly. (DWORD - > int , WORD - > Int16)
2, API string pointer type - > The net string
3. Handle (dWord) - in API > The net IntPtr
4, API structure - > .Structure or class in net. Note that in this case, you first qualify the declaration structure or class with the StructLayout property

The common language runtime USES StructLayoutAttribute to control the physical layout of the data fields of a class or structure in managed memory, meaning that the class or structure needs to be arranged in a certain way. Explicit control of the class layout is important if you want to pass the class to unmanaged code that needs to specify the layout. Its constructor initializes a new instance of the StructLayoutAttribute class with the LayoutKind value. LayoutKind.Sequential is used to force the members to be laid out in the order in which they appear.

LayoutKind.Explicit is used to control the exact location of each data member. With Explicit, each member must indicate the location of this field in the type using FieldOffsetAttribute. Such as:


 [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
public class MySystemTime 
{
[FieldOffset(0)]public ushort wYear; 
[FieldOffset(2)]public ushort wMonth;
[FieldOffset(4)]public ushort wDayOfWeek; 
[FieldOffset(6)]public ushort wDay; 
[FieldOffset(8)]public ushort wHour; 
[FieldOffset(10)]public ushort wMinute; 
[FieldOffset(12)]public ushort wSecond; 
[FieldOffset(14)]public ushort wMilliseconds; 
} 

Here is an example of defining the corresponding class or structure in.net for the OSVERSIONINFO structure in API:

  /**********************************************
* API Defines the original structure declaration 
* OSVERSIONINFOA STRUCT
*  dwOSVersionInfoSize   DWORD      ?
*  dwMajorVersion        DWORD      ?
*  dwMinorVersion        DWORD      ?
*  dwBuildNumber         DWORD      ?
*  dwPlatformId          DWORD      ?
*  szCSDVersion          BYTE 128 dup (?)
* OSVERSIONINFOA ENDS
*
* OSVERSIONINFO  equ  <OSVERSIONINFOA>
*********************************************/ 


 //.net Is declared as a class 
[ StructLayout( LayoutKind.Sequential )]   
public class OSVersionInfo 
{   
public int OSVersionInfoSize;
public int majorVersion; 
public int minorVersion;
public int buildNumber;
public int platformId;
[ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]    
public String versionString;
}
// or 
//.net Is declared as a structure 
[ StructLayout( LayoutKind.Sequential )]  
public struct OSVersionInfo2 
{
public int OSVersionInfoSize;
public int majorVersion; 
public int minorVersion;
public int buildNumber;
public int platformId;


[ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]    
public String versionString;
} 

In this example, the MashalAs feature is used to describe the marshaling format of fields, methods, or parameters. Use it as a parameter prefix and specify the data type required by the target. For example, the following code marshals two arguments to the Windows API function string (LPStr) as long Pointers to the data type:

 [MarshalAs(UnmanagedType.LPStr)]
String existingfile;
[MarshalAs(UnmanagedType.LPStr)]
String newfile; 

Note that the ref modifier is usually used in front of the structure as a parameter, otherwise an error occurs: the reference to the object does not specify an instance of the object.

 [ DllImport( "kernel32", EntryPoint="GetVersionEx" )] 
public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi );  

3. How to ensure successful platform invocation using managed objects?
If the managed object is not referenced anywhere after calling platform invoke, the garbage collector may complete the managed object. This frees the resource and invalidates the handle, causing the platform invoke call to fail. Wrapping the HandleRef handle ensures that managed objects are not garbage collected until the platform invoke call is complete.

For example:


 FileStream fs = new FileStream( "a.txt", FileMode.Open );
StringBuilder buffer = new StringBuilder( 5 );
int read = 0;
ReadFile(fs.Handle, buffer, 5, out read, 0 ); // call Win API In the ReadFile function  

Since fs is a managed object, it is possible to be garbage collected before the platform call is complete. By wrapping the file stream handle in HandleRef, you can avoid being recycled by the garbage station:

[ DllImport( "kernel32", EntryPoint="GetVersionEx" )]  
0


Related articles: