SoFunction
Updated on 2025-03-07

Technical points for calling Windows API in C#

In the .Net Framework SDK documentation, instructions for calling the Windows API are relatively fragmented, and the slightly more comprehensive one is for Visual Basic .net. This article collects the key points of calling APIs in C# as follows. I hope to give some help to friends who have not used APIs in C#. In addition, if Visual Studio .net is installed, there are a large number of examples of calling APIs in the C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS directory.

1. Call format

Copy the codeThe code is as follows:

using ; //Cite this namespace to simplify the subsequent code
...
//Use the DllImportAttribute feature to introduce the API function, note that the declared empty method, that is, the method body is empty.
[DllImport("")]
public static extern ReturnType FunctionName(type arg1,type arg2,...);
//There is no difference when calling and calling other methods

Fields can be used to further illustrate the characteristics, separated by commas, such as:
Copy the codeThe code is as follows:

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

The common fields of the DllImportAttribute attribute are as follows:
1. CallingConvention indicates the CallingConvention value used to pass method parameters to the unmanaged implementation.
: The caller cleans the stack. It enables you to call functions with varargs .
: The callee cleans 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 String parameters to the method.

This field is set to one of the CharSet values. 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 the DLL EntryPoint. If this field is set to Ansi, the string is converted to an ANSI string, with the letter "A" appending to the name of the DLL EntryPoint.

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

The name matching rules of , and , are very different. For Ansi, if EntryPoint is set to "MyMethod" and it exists, it returns "MyMethod". If there is no "MyMethod" in the 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 the DLL, but "MyMethod" exists, "MyMethod" is returned. If you are using Auto, the matching rules are platform-related (Unicode on Windows NT and Ansi on Windows 98). If ExactSpelling is set to true, "MyMethod" will only be returned if "MyMethod" exists in the DLL.

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

Copy the codeThe code is as follows:

[DllImport("",CharSet="",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 modified to correspond to the CharSet value specified in the CharSet field. If true, when the field is set to the Ansi value of CharSet, append the letter A to the method name, and when the field is set to the Unicode value of CharSet, append the letter W to the method name. The default value of this field is false.

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

6. SetLastError Indicates that the callee will call Win32 API SetLastError before returning from the attributed method. true Indicates the caller to call SetLastError, defaults to false. The runtime marshaling collector will call GetLastError and cache the returned value in case it is rewritten by other API calls. Users can retrieve the error code by calling GetLastWin32Error.

2. Parameter type:

1. Just use the corresponding numerical type directly. (DWORD -> int , WORD -> Int16)
2. String pointer type in API -> string in .net
3. Handle in API (dWord) -> IntPtr in .net
4. Structure in API  -> Structure or class in .net. Note that in this case, you must first use the StructLayout feature to define the declaration structure or class.

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

Used to control the exact location of each data member. With Explicit, each member must use FieldOffsetAttribute to indicate the position of this field in the type. like:

Copy the codeThe code is as follows:

 [StructLayout(, Size=16, CharSet=)]
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;
}

The following is an example of defining the corresponding class or structure in .net for the OSVERSIONINFO structure in the API:
Copy the codeThe code is as follows:

  /**********************************************
* Define the original structure declaration in the API
* OSVERSIONINFOA STRUCT
*  dwOSVersionInfoSize   DWORD      ?
*  dwMajorVersion        DWORD      ?
*  dwMinorVersion        DWORD      ?
*  dwBuildNumber         DWORD      ?
*  dwPlatformId          DWORD      ?
*  szCSDVersion          BYTE 128 dup (?)
* OSVERSIONINFOA ENDS
*
* OSVERSIONINFO  equ  <OSVERSIONINFOA>
*********************************************/
 
Copy the codeThe code is as follows:

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

Copy the codeThe code is as follows:

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

In this example, the MashalAs feature is used, which describes the marshal processing 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 parameters as data type long pointers to a string (LPStr) of a Windows API function:
Copy the codeThe code is as follows:

 [MarshalAs()]
String existingfile;
[MarshalAs()]
String newfile;

Note that when the structure is used as a parameter, the ref modifier is generally added before it, otherwise an error will occur: the object's reference does not specify the instance of the object.
Copy the codeThe code is as follows:

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

3. How to ensure the successful platform call using managed objects?
If the managed object is not referenced anywhere after the platform invoke is called, the garbage collector may complete the managed object. This will free up the resource and invalidate the handle, resulting in the platform invoke call failing. Wrapping a handle with HandleRef ensures that no managed objects are garbage collected before the platform invoke call is completed.

For example, the following:

Copy the codeThe code is as follows:

 FileStream fs = new FileStream( "", );
StringBuilder buffer = new StringBuilder( 5 );
int read = 0;
ReadFile(, buffer, 5, out read, 0); //Calling the ReadFile function in the Win API

Since fs is a managed object, it is possible to be recycled by the garbage collection bin when the platform call has not yet been completed. After wrapping the handle of the file stream with HandleRef, it can avoid being recycled by the garbage station:
Copy the codeThe code is as follows:

 [ DllImport( "" )]
public static extern bool ReadFile(
HandleRef hndRef,
StringBuilder buffer,
int numberOfBytesToRead,
out int numberOfBytesRead,
ref Overlapped flag );
......
......
FileStream fs = new FileStream( "", );
HandleRef hr = new HandleRef( fs, );
StringBuilder buffer = new StringBuilder( 5 );
int read = 0;
// platform invoke will hold reference to HandleRef until call ends
ReadFile( hr, buffer, 5, out read, 0 );