Platform/Invoke: calling the OS from .NET

Platform/Invoke allows the coder to use standard (unmanaged) C/C++ DLLs. If you need to have access to any function inside the extensive Windows APIs (which hold basically everything the operating system can perform) and there's no available wrapper to call the same functionality from the CLR, then this is the choice.

From the developer's perspective, by Platform/Invoke, we understand a feature of the CLR that allows a program to interact with the functionality that is unique to the system in which the application runs, thus allowing managed code to call native code and vice versa.

The assembly responsible for calling the APIs will define how the native code is called and accessed, via metadata embedded inside, which usually requires attribute decorations. These attributes are defined inside the class containing the caller methods in order to indicate the compiler the correct way to do the marshaling between the two worlds (managed and unmanaged).

The idea is that if I need to call an unmanaged function from the managed code, I should indicate the destination context how big the things that I'm passing are and what direction they are going. That is if I'm asking for data or if I'm passing data (or both).

The caveat is that there are many exceptions, and often, there's always a better way to do it, even if coded correctly. This is where the tools we just reviewed help the programmer to deal with these situations.

But let's first review the foundations of the platform invocation and how to use it from C# with a simple example.

The process of platform invocation

To achieve platform invocation, the CLR has to do several things:

  • Locate the DLL containing the function and load it in memory
  • Locate the function's memory address and push its arguments onto the stack, marshaling data as required

Note, however, that operating in this way also has some pitfalls. For example, you no longer have the benefits of type safety or garbage collection, and you have to be careful when using them. The great advantage, on the other hand, is that the enormous amount of functionality provided by the system is available for us, and we're talking about functionality that has been fully tested and optimized.

It's easy to insert external APIs' definitions into our code. Let's look at this in an example. Imagine that our application uses the system's calculator (or any other system's tool) and we want to make sure that in certain circumstances, the calculator is located in a given position (such as the screen's origin) and also that we want to have the ability to close the calculator from our program.

We need three APIs here—SetWindowPos (to change the calculator's position), SendMessage (to close the calculator), and FindWindow—in order to get the calculator's handle that we need to use with other two.

So, we search for these functions in the Platform/Invoke Assistant to find their definitions, and we use the Insert button to have the translated definition inserted in our code. For every function search, we should see a window like this:

The process of platform invocation

After finding the three functions, we should have the following code available:

[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, 
    int x, int Y, int cx, int cy, int wFlags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, 
    IntPtr wParam, IntPtr lParam);
IntPtr calcHandler;
private const UInt32 WM_CLOSE = 0x0010;

This is our bridge to the operating system's functionality, and we can call these functions from any accessible place just like if they were .NET functions.

For this demo, I'll create a basic Windows Forms app with a couple of buttons in order to implement the required functionality. The first button finds the calculator's handler and locates the Calculator in the top-left position:

private void btnPosition_Click(object sender, EventArgs e)
{
  calcHandler =  FindWindowByCaption(IntPtr.Zero, "Calculator");
  SetWindowPos(calcHandler, 0, 0, 0, 0, 0, 0x0001 | 0x0040);
}

Now, in another button, we have to send a message to the Calculator to close it. Again, we can check with the assistant, knowing that the message identifier is called WM_CLOSE and that we will find it searching for constants and going down to those starting with WM_. So we insert this definition and are ready to call the second button, which closes the calculator:

private const UInt32 WM_CLOSE = 0x0010;
private void btnClose_Click(object sender, EventArgs e)
{
  SendMessage(calcHandler, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}

Remember that when we are using the system APIs, some parameters have to be specifically marshaled (converted) into the destination types. This is why the last two parameters are expressed as IntPrt.Zero, which is the correct definition for this type in .NET.

Naturally, we can use this technique to close any window, including managed ones, although in this case, we have other (simpler) options, including the possibilities that we saw in relation to Reflection, if the holding assembly is external.

Also, note that some solutions called multiplatform are based on calls to native code from managed code: in Silverlight; the runtime is based on Platform Adaptation Layer (PAL) based on these principles. This allows you to call native functions in different OSes.

This can also be said for Platform/Invoke in Linux and MacOS, the most successful manifestation of this being the Xamarin initiative (more information about Platform/Invoke on these platforms is available at http://www.mono-project.com/docs/advanced/pinvoke/).

However, as we'll see at the end, the new .NET Core is a great promise in this respect since it is thought of to work on any platform and any operating system.

Nevertheless, if we're programming for Windows, there are situations where we need to know specific data about the configuration of our platform. That's where Windows Management Instrumentation (WMI), or its recent alternative Windows Management Infrastructure, can be very useful, not just for programmers, but for IT people as well.

Windows Management Instrumentation

The official documentation defines WMI technology in this manner:

"Windows Management Instrumentation (WMI) is the infrastructure for management data and operations on Windows-based operating systems. You can write WMI scripts or applications to automate administrative tasks on remote computers but WMI also supplies management data to other parts of the operating system and products, for example, System Center Operations Manager, formerly Microsoft Operations Manager (MOM), or Windows Remote Management (WinRM)."

However, the same documentation adds:

"WMI is fully supported by Microsoft; however, the latest version of administrative scripting and control is available through the Windows Management Infrastructure (MI). MI is fully compatible with previous versions of WMI and provides a host of features and benefits that make designing and developing providers and clients easier than ever. For more information, see Windows Management Infrastructure (MI)."

You can find more information on configuring MI at https://msdn.microsoft.com/en-us/library/dn313131(v=vs.85).aspx if you want to dig into these features in depth.

So, with WMI, we query the system to get details on its implementation and the software and hardware installed. The reason for the query is that WMI stores the system's information in Common Information Model (CIM) databases, stored and updated by the system continuously.

And by the way, the CIM is not something exclusive to Windows operating systems. As Wikipedia states:

"The Common Information Model (CIM) is an open standard that defines how managed elements in an IT environment are represented as a common set of objects and relationships between them.

The Distributed Management Task Force maintains the CIM to allow consistent management of these managed elements, independent of their manufacturer or provider."

The DMTF updates these documents frequently (sometimes, twice a year).

CIM searchable tables

There are many tables permanently updated by the system, which we can search. The complete list is published on MSDN, and you can find this information in the Computer System Hardware Classes section, available at https://msdn.microsoft.com/en-us/library/aa389273(v=vs.85).aspx.

I'll resume some of the most useful terms to search for programmers here:

  • Input device classes
  • Mass storage classes
  • Motherboard, controller, and port classes
  • Networking device classes
  • Power classes
  • Printing classes
  • Telephony classes
  • Video and monitor classes

Each one contains a set of distinct classes, holding a variety of information about the hardware and the software.

For this brief review, we're just going to use the classical WMI and look at the type of data that can be revealed to us in a demo and how to query for it.

Although there are several ways to access this information for the .NET programmer, .NET provides part of the WMI functionality through the System.Management namespace, which is filled with classes to search for system-related information, such as ManagementObjectSearcher, SelectQuery, ManagementObject, and so on.

For a simple query about the system information, we first create a ManagementObjectSearcher object that defines the focus of our search (an information provider). This object should receive a SQL string, indicating the table we want to search for.

So, in our demo, we're going to start by creating a Windows Forms app, including a few buttons and a couple of Listbox controls to present the results.

We'll start by coding a general query to obtain the list of tables available. The code for the button in charge of that is as follows:

ManagementObjectSearchermos = newManagementObjectSearcher
("SELECT * FROM meta_class WHERE __CLASS LIKE 'Win32_%'");
foreach (ManagementObject obj in mos.Get())
listBox1.Items.Add(obj["__CLASS"]);

As you can see, meta_class is a CIM object containing the complete list of classes available for searching. Note that the query might take a while since ManagementObjectSearcher has to go through all the information available in the system and registered in the CIM tables.

You should see output similar to the what is shown in the next screenshot:

CIM searchable tables

Later on, we can query these tables to retrieve the required data. In this demo, we'll use several tables—Win32_OperatingSystem, Win32_processor, Win32_bios, Win32_Environment, and Win32_Share—to find some information about the running machine and related characteristics.

The way it works is always the same: you create ManagementObjectSearcher and iterate over it, invoking the Get()method on every instance of the collection returned by the searcher. So, we have the following code:

private void btnQueryOS_Click(object sender, EventArgs e)
{
    listBox1.Items.Clear();
    listBox2.Items.Clear();

    // First, we get some information about the Operating System:
    // Name, Version, Manufacturer, Computer Name, and Windows Directory
    // We call Get() to retrieve the collection of objects and loop through it
    var osSearch = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem");
    listBox1.Items.Add("Operating System Info");
    listBox1.Items.Add("-----------------------------");
    foreach (ManagementObject osInfo in osSearch.Get())
    {
        listBox1.Items.Add("Name: " + osInfo["name"].ToString());
        listBox1.Items.Add("Version: " + osInfo["version"].ToString());
        listBox1.Items.Add("Manufacturer: " + osInfo["manufacturer"].ToString());
        listBox1.Items.Add("Computer name: " + osInfo["csname"].ToString());
        listBox1.Items.Add("Windows Directory: " + osInfo["windowsdirectory"].ToString());
    }

    // Now, some data about the processor and BIOS
    listBox2.Items.Add("Processor Info");
    listBox2.Items.Add("------------------");
    var ProcQuery = new SelectQuery("Win32_processor");
    ManagementObjectSearcher ProcSearch = new ManagementObjectSearcher(ProcQuery);
    foreach (ManagementObject ProcInfo in ProcSearch.Get())
    {
        listBox2.Items.Add("Processor: " + ProcInfo["caption"].ToString());
    }

    listBox2.Items.Add("BIOS Info");
    listBox2.Items.Add("-------------");
    var BiosQuery = new SelectQuery("Win32_bios");
    ManagementObjectSearcher BiosSearch = new ManagementObjectSearcher(BiosQuery);
    foreach (ManagementObject BiosInfo in BiosSearch.Get())
    {
        listBox2.Items.Add("Bios: " + BiosInfo["version"].ToString());
    }

    // An enumeration of Win32_Environment instances
    listBox2.Items.Add("Environment Instances");
    listBox2.Items.Add("-----------------------------");
    var envQuery = new SelectQuery("Win32_Environment");
    ManagementObjectSearcher envInstances = new ManagementObjectSearcher(envQuery);
    foreach (ManagementBaseObject envVar in envInstances.Get())
        listBox2.Items.Add(envVar["Name"] + " -- " + envVar["VariableValue"]);

    // Finally, a list of shared units
    listBox2.Items.Add("Shared Units");
    listBox2.Items.Add("------------------");
    var sharedQuery = new ManagementObjectSearcher("select * from win32_share");
    foreach (ManagementObject share in sharedQuery.Get())
    {
        listBox2.Items.Add("Share = " + share["Name"]);
    }
}

If you run the demo, depending on your machine, you'll get some distinct values, but the structure of the information should be similar to what is shown in the next screenshot:

CIM searchable tables

To summarize, WMI offers a simple, managed way to access practically any relevant data related to the hardware and software on our machine and also in the network to which we are connected (as far as the query has the required permissions).

As for the security concerns, Microsoft has published an exhaustive article on the subject on MSDN: Maintaining WMI Security (https://msdn.microsoft.com/en-us/library/aa392291(v=vs.85).aspx), with all the critical information and guidelines about maintaining security while allowing access to these resources.

There is much more functionality related to the ManagementObject class. For instance, you can get information related to processes or services by creating a new instance of the desired element and use the methods the object inherits.

For example, if you want to know which services are dependent on other services programmatically, you can use the GetRelated method of the object's instance. Let's imagine we want to know which services are related to the LSM (Local Session Manager) service. We could code the following:

var mo = newManagementObject(@"Win32_service='LSM'");
foreach (var o in mo.GetRelated("Win32_Service", "Win32_DependentService",
null,null,"Antecedent","Dependent", false, null))
{
  listBox1.Items.Add(o["__PATH"]);
}

In this way, we can get this (hard-to-find) information in a totally programmatic manner. This will help us configure some scenarios in which one of our application's procedures require the active presence of a certain service (remember that we can launch a service from code as well).

Besides this, other actions are available, such as stopping, pausing, or resuming a given service. In the case of the LSM service, we should see information similar to what is shown in this screenshot:

CIM searchable tables

And there is much more information that you will discover going through the class hierarchy related to System.Management. Practically every byte of system-related data that we should otherwise read via Registry or Windows APIs is available here in a totally programmatic fashion with no need for complex approaches.

The only caveat is that the documentation is very long. Consequently, Microsoft created a tool called WMI Code Creator, which analyzes the information available and generates code for all possible scenarios (often, this code is expressed in Windows Scripting Host), but a big part is perfectly translatable to C#.

Besides, we have the advantage of a tool that joins much of the functionality available in a single user interface.

You can download it from https://www.microsoft.com/en-gb/download/details.aspx%3Fid%3D8572, getting a ZIP file that contains the executable and the source code, which is a valuable tip for coding in WMI.

The tool includes several options:

  • Query data from a WMI class
  • Executing a method
  • Receiving an event
  • Browsing the namespaces on this computer

As you can see in the next screenshot, this tool is quite complete in possibilities and in the information it provides:

CIM searchable tables

Another typical usage of WMI is to check the state of a piece of hardware before performing an action that could provoke a system's failure, such as testing a hard drive before copying big chunks of data that could exceed the disk quota.

The code, in this case, is simple, and it gives us an idea about how to code other system-related queries. All we need is something like this:

ManagementObject disk = new
ManagementObject("win32_logicaldisk.deviceid='c:'");
disk.Get();
var totalMb = long.Parse(disk["Size"].ToString()) / (1024 * 1024);
var freeMb = long.Parse(disk["FreeSpace"].ToString()) / (1024 * 1024);
listBox1.Items.Add("Logical Disk Size = " + totalMb + " Mb.");
listBox1.Items.Add("Logical Disk FreeSpace = " + freeMb + " Mb.");

Thus, after adding a new button and including the previous code to check the state of the C: drive, we should see output similar to this:

CIM searchable tables

To summarize, we've seen several ways of interacting with the operating system. We can analyze which messages are linked to a certain functionality and capture the related events to either change, cancel, or modify the default behaviors. In this case, the communication is in both directions.

This also happens (bi-directional communications) when we use the system APIs to call functionalities through Platform/Invoke, which offers unlimited possibilities.

Note

However, the official Microsoft recommendation is that if they're available for the .NET programmer, it's always preferable to use the resources linked to .NET classes.

Finally, Windows Management Instrumentation and its variant MI provide access to otherwise difficult-to-reach information, allowing our applications to configure and behave more suitably depending on the operating system's state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.7.131