Advertisment

Platform Invocation Services: .Net calling Win32

author-image
PCQ Bureau
New Update

One of the core strategies of the .Net framework is to support communication with legacy code, namely, COM servers and components and Win32 DLLs so as to provide smooth shift to new technology. While .Net divides this switch in two parts, one dealing with interaction with COM, and the other dealing with interaction with Win32 DLLs, this article shows how to do the latter. In .Net, Platform Invocation Services (PInvoke) are responsible for supporting the interaction with the unmanaged Win32 DLLs. We shall exemplify the usage of PInvoke by writing a C# application that enumerates the running processes, using the 32bit ToolHelp API. We are duplicating the functionality of System.Diagnostics. Process class because:

Advertisment

Unlike System.Diagnostics.Process class, the ToolHelp API lets us also get the ID the parent to a given process, amongt other details. 

We shall see a practical way to represent standard Win32 entities like structures, pointer variables and how to use them in

Pinvoke. 

Introducing PInvoke



To use PInvoke, an application must use the System.Runtime.InteropServices namespace. The various classes present in this namespace help with all kinds of inter-operability needs. That said, here’s how we declare the usage of a typical Win32 DLL function, exemplified using the MessageBox API, implemented within USER32.DLL:

Advertisment





public static extern int MessageBox(int Handle, string Message, string Caption, int
ButtonType); 

The DllImport attribute is used to tell which DLL is containing the function whose declaration is immediately followed. The first, and the mandatory, parameter of this attribute is the name of the DLL containing the function, which in this case is USER32.DLL. This is followed by many of the optional attributes, like CharSet, which has been used to specify the character set that will be used. Immediately after the DllImport attribute definition is the declaration of the API function, with appropriate signature. Note that there can be no other line of code between the DllImport attribute and the API function declaration.

A call to this function would be like any other call, as shown below (ofcourse, you must be conversant with the meaning behind the parameters passed to it):

Advertisment

MessageBox(0,”Hello Pinvoke”,”PInvoke 2 Win32”, 0);

This call displays a standard message box, with the message Hello PInvoke, and caption PInvoke 2 Win32. Now that we have gone through enough basics to get us started, let’s call the ToolHelp API and enumerate processes from the managed world.

Introducing ToolHelp



The ToolHelp API is a very simple-to-use API, present in Win 9X/Me/2000/XP. However, it’s not available for Win NT, since process enumeration here is done using values in the registry. The ToolHelp API lets us enumerate the system’s Process and Thread lists, apart from the per-process Module and Heap lists as well. A typical usage of ToolHelp API would involve the following steps:

Advertisment

Create a snapshot of the system resources (like processes) that are to be enumerated

Use the relevant pair of enumeration functions (like Process32First & Process32Next) to enumerate the resources 

Close the handle to the snapshot 



Now, let’s start the enumeration. We shall take each of the API functions involved, one at a time, have a look at their Win32 implementation, and then convert them to their respective .Net versions. We start by converting the

CreateToolhelp32Snapshot API, which is used to create a snapshot of the resources to be enumerated. Here’s how it looks

in the Win32 world:

HANDLE WINAPI CreateToolhelp32Snapshot(



DWORD dwFlags, 


DWORD th32ProcessID 


); 

Advertisment

The return value is a Win32 HANDLE, which is a 32bit pointer, and in turn, is a 32bit integer value. Hence, its equivalent in C# will be the int datatype. Like wise, a Win32 DWORD is an unsigned 32bit integer value, which equates to a C# uint. Hence, the converted C# declaration, including the PInvoke DllImport attribute, will be:





public static extern int CreateToolhelp32Snapshot(uint flags, uint processid);

Next, once the snapshot handle is created, we use it to enumerate the relevant resources, which in this case will be the system process list. The ToolHelp API enumeration function pair for process enumeration is declared in Win32, as shown below:

Advertisment

BOOL WINAPI Process32First(



HANDLE hSnapshot, 


LPPROCESSENTRY32 lppe 


); 


BOOL WINAPI Process32Next(


HANDLE hSnapshot, 


LPPROCESSENTRY32 lppe 


); 





Both the function sport similar declaration, and the return value of both is a Win32 BOOL, which is an integer value and hence, equates to a C# int. However, the interesting part is the second parameter of both functions, which is a pointer to the following Win32 structure that details out enumeration information:

typedef struct tagPROCESSENTRY32 { 



DWORD dwSize; 


DWORD cntUsage; 


DWORD th32ProcessID; 


DWORD th32DefaultHeapID; 


DWORD th32ModuleID; 


DWORD cntThreads; 


DWORD th32ParentProcessID; 


LONG pcPriClassBase; 


DWORD dwFlags; 


char szExeFile


} PROCESSENTRY32; 


typedef PROCESSENTRY32 * PPROCESSENTRY32; 


typedef PROCESSENTRY32 * LPPROCESSENTRY32; Using the same rules that we used above, this structure gets converted to the following C# equivalent:





public struct ProcessEntry32 { 


public uint dwSize; 


public uint cntUsage; 


public uint th32ProcessID; 


public IntPtr th32DefaultHeapID; 


public uint th32ModuleID; 


public uint cntThreads; 


public uint th32ParentProcessID; 


public int pcPriClassBase; 


public uint dwFlags; 


public string szExeFile;


}; 
























Advertisment

The first difference that you will notice is the presence of the StructLayout attribute. This attribute specifies how an unmanaged structure will be layed out in the memory by the .Net runtime. By specifying layout to be sequential, we instruct the runtime to have each of the structure elements allocated memory next to each other. The other two attributes, Auto & Explicit, let the runtime allocate memory to the structure members as it deems fit (may not be next to each other, but scattered across), or let us specify the explicit offset position where they will be located, relative to a starting location, respectively.

The next addition that will be noticed is the use of MarshalAs attribute before the string variable. This is so because strings are immutable in nature, when marshalled by default. This means that they cannot be changed. In order to make modifications, a copy of the string is made, modified, and then returned. By specifying the MarshalAs attribute, we instruct the runtime to marshal the string as an equivalent to char szExe<256>, so that it can be modified by the called function. This is necessary since this member will contain the name of the executable associated with a given process.

Now that we have converted the Win32 structure, a pointer to which is passed as an argument to the Process32First and Process32Next APIs, to its C# format, lets have a look at the converted version of the Process32First and Process32Next API, as shown below:





public static extern int Process32First(int handle, ref ProcessEntry32 pe); 





public static extern int Process32Next(int handle, ref ProcessEntry32 pe);

The second parameter is indicated to be a pointer to the structure by passing it as a C# ref parameter. This ensures that the called APIs can modify the structure passed to it. Finally, to close the snapshot handle we created in the beginning, we



use the CloseHandle API, which is declared in Win32 as:

BOOL CloseHandle(



HANDLE hObject // handle to object to close


); Using the same rules that we have used above, this gets converted to the following C# equivalent:





public static extern int CloseHandle(int handle);


 

Now that we have seen how the APIs get converted to their .NET equivalents, in C#, for use in Pinvoke. We’ll go through the entire application source and see how it will look like in the next issue.

Kumar Gaurav Khanna

Advertisment