Chapter 26. Interacting with the OS and Hardware

Applications don’t exist in a vacuum. They rely on the operating system to provide many vital services. Some of these, such as the location of important folders or the installed network adapters, are covered in earlier chapters (Chapters 11, “Files and Serialization,” and 12, “Networking and the Web,” respectively).

The OS can also present more information about itself and the hardware environment it’s running on. This chapter talks mostly about the information you can obtain, as well as some Windows-specific technologies, such as the event log, Windows services, and memory-mapped files.

Get OS, Service Pack, and CLR Version

Solution: Use the System.Environment.OSVersion class, as in this sample:

image

This produces the following output (on my Windows 7 system):

image

The version information is in a struct, so you can make decisions based on version or subversion values.

Note

Notice that nowhere does it say “Windows 7” in the version information. “Windows 7” is really the brand, which has relatively little to do with version information. Your programs should make decisions based on version numbers, never names.

Get CPU and Other Hardware Information

Solution: Although .NET does expose some basic hardware information, more can be retrieved using Windows Management Instrumentation (WMI) functionality.

image

image

image

Note

When you build a .NET application, you can specify the target platform: Any, x86, x64, or Itanium. When Any is selected, the same executable file will work on any OS and will be 64 bit on a 64-bit OS, and 32 bit on a 32-bit OS.

If you need your process to stay 32 bit even on a 64-bit OS, you need to specify x86 as the target. This is vital for interoperating with unmanaged code, or other managed libraries that were compiled with specific platform targets. See the section “Ensure Your Application Works in Both 32-bit and 64-bit Environments” later in this chapter to learn how to ensure your app works on all environments, as well as an example of calling a 32-bit unmanaged DLL from C#.

Invoke UAC to Request Admin Privileges

Solution: Starting with Windows Vista, applications are prohibited from running with administrative privileges by default. Attempting to do something that requires administrative access results in a system dialog box requesting confirmation and possibly a password for less-privileged users. This feature is implemented in two parts:

• A special icon indicating User Access Control (UAC) will be invoked.

• Performing the action in an elevated process.

The icon doesn’t do anything in and of itself, and there is no direct support for it in .NET. You can turn it on by sending a message to the Button control. Here’s a custom button class that implements this functionality:

image

image

The Win32 class is a simple wrapper for P/Invoked functionality (see the section “Call Native Windows Functions Using P/Invoke” later in this chapter):

image

It is impossible to elevate a process’s privileges once the process has started, so the technique is to start your same process as an admin and pass it some command-line arguments (or otherwise communicate with it) to tell it what to do. That second process will do the required behavior, then exit. Leaving the second, elevated, process running and having the first exit is not recommended—your programs should always run with the least privilege possible.

Here is the button event handler (which has to do something requiring elevation):

image

Then, when the program starts, you can look for the arguments:

image

Write to the Event Log

Solution: Windows provides an API for any application, service, and driver to write to a common logging interface, managed by the OS. .NET wraps this API into the EventLog and related classes.

Before you can write to the event log, you need to do two things:

1. Create a log source. This is typically your application. This step requires administrative privileges and only needs to be done once, so it’s typically done during application installation.

2. Decide which log your events should go to. By default, events are written to the Application event log, but you can create your own log as well.

The code to create a log source is as follows:

image

Note

In the EventLogDemo sample app, CreateEventSource is called after a UAC request because it requires admin privileges. See the previous section for how to accomplish this.

To do the actual logging, use the following:

image

You could, of course, save the EventLog object in your application to reuse it, rather than disposing of it right away.

Read from the Event Log

You can use the same set of objects to read existing log entries:

image

Access the Registry

Solution: Use the Registry and RegistryKey classes located in the Microsoft.Win32 namespace:

image

Note

Registry keys are represented by handles, which are system resources that must be disposed of, hence the using statements.

image

Here’s the output from the preceding code:

image

Note

In general, most .NET programs avoid the registry in favor of XML configuration files, but the access is there if you need it. Note that non-administrative programs don’t have write permissions on HKLM in Windows Vista and later. If you try to write to it as a non-administrator, Windows will actually redirect you to a virtual HKLM for just that application. Better just to avoid it.

Manage Windows Services

Solution: You can control services (assuming you have the right privileges) with the System.ServiceProcess.ServiceController class:

image

Create a Windows Service

There is nothing inherently different about a Windows service compared to an application except the manageability interfaces it implements (to allow it to be remotely controlled, automatically started and stopped, failed over to different machines, and so on). Also, a Windows service has no user interface (and security settings generally prohibit services from attempting to invoke a UI). You can generally use all the same .NET code as in a regular application.

Solution: The heart of a service is a class that implements System.ServiceProcess.ServiceBase:

image

image

This service does nothing interesting—it just logs events to a file.

The Main function looks a little different:

image

This is the basic functionality for a service, but to make it easier to install the service, you should implement a few more classes:

image

Note

If you’re using Visual Studio, much of this work is automated for you. For example, you can right-click the design view of the service object and then select Add Installer to create this for you.

With this installation class implemented, you can easily install the service into a system by using the .NET Framework tool InstallUtil.exe. To use this tool, start a Visual Studio command prompt with admin privileges and navigate to the directory containing the service executable and type:

InstallUtil /i GenericService.exe

To remove the service, type the following:

InstallUtil /u GenericService.exe

Note

By default, services are not allowed to interact with the desktop. If you want a service to be able to do this, you need to modify the “Allow service to interact with desktop” setting in the service’s properties in the service management snap-in.

Call Native Windows Functions Using P/Invoke

Solution: Use P/Invoke, which is shorthand for Platform Invocation Services. It allows you to call native code directly from .NET. You’ve already seen some examples throughout the book where we needed to supplement .NET’s functionality with that from the OS.

Using P/Invoke involves defining any structures you will need in .NET and then adding an import reference to the function you want to call, potentially mapping each argument to equivalent types.

Here’s an example of the declaration for the SendMessage function:

image

To call this, you would write the following:

image

A more sophisticated example is when you need the API function to fill in a structure:

image

A question that often arises is how to translate between various string representations. Windows natively uses C-style, NULL-terminated strings, whereas .NET uses the String object. The next section demonstrates how to do this.

Call C Functions in a DLL from C#

Solution: P/Invoke is most commonly used for calling the native API, but you can also use it to call native code functions in any unmanaged DLL.

Listings 26.126.3 show the source code for a native code library that defines a C function that takes a char* argument.

Listing 26.1 MyCDll.h

image

Listing 26.2 MyCDll.cpp

image

Listing 26.3 MyCDll.def

image

See the MyCDll project in the examples for this chapter for the full source code.

On the C# side, using this function is quite easy. This code also demonstrates that to call a method that takes char*, you must first convert the string to bytes.

image

Use Memory-Mapped Files

Solution: Memory-mapped files are not the most often used technology, but when you need them, they are very good at what they do. Memory-mapped files are exactly what they sound like: A portion of a file is put into memory. What’s the difference between that and just reading the file in with a FileStream, you ask? Well, what if the file was 100GB in size?

As of this writing, loading a file that size into memory is out of the question for most computers. Memory-mapped files allow you to specify a portion of that file to load into memory, where you can then manipulate it much as any other in-memory structure, such as an array, and the changes are written back to the file automatically.

image

Running this on a text file will result in a sequence of E and O characters being written in the middle.

Ensure Your Application Works in Both 32-bit and 64-bit Environments

Solution: This is a simple configuration setting, with some caveats. In your project’s build options, you can select the Platform target, which can be x86, x64, Itanium, or Any CPU. (Your list may vary, depending on what you’ve installed with Visual Studio; see Figure 26.1.)

Figure 26.1 You can choose the target architecture of your assemblies.

image

Your default choice should be Any CPU. Because .NET assemblies consist of byte code that is just-in-time compiled (JIT compiled) to the current platform at runtime, your assemblies will work on any architecture without rebuilding from the source code.

However, there are times when it’s not that simple. If your .NET assembly needs to reference or interop with another managed assembly or unmanaged DLL that was compiled with a specific architecture, then your assembly needs to match.

Let’s look at an example. Suppose you have the following:

• .NET assembly: Any CPU

• 32-bit COM component

On a 32-bit operating system, this scenario will work because the .NET assembly will be just-in-time compiled as 32 bits.

However, on a 64-bit operating system, the .NET assembly will be 64 bits, and when it tries to call into the COM component, things will break.

In this case, it would be better to specify that your assembly should always be 32 bit. Keep in mind that x64 operating systems run 32-bit processes just fine.

Another consideration to keep in mind is that while .NET itself largely insulates you from platform architecture considerations, when you drop into unsafe code, use pointers, or rely on the size of IntPtr, you are asking for trouble when running on multiple architectures.

Respond to System Configuration Changes

Solution: The Microsoft.Win32 namespace contains the SystemEvents class, which contains a number of static events your application can listen to, such as the following:

DisplaySettingsChanged (and -Changing)

InstalledFontsChanged

PaletteChanged

PowerModeChanged

SessionEnded (the user is logging off; also -Ending)

SessionSwitch (the current use has changed)

TimeChanged

UserPreferenceChanged (and -Changing)

For example, if your application listens for the PowerModeChanged event, you can detect if the computer is entering standby and you can shut down threads, stop computations, and so on.

Note

You should always make sure your application detaches the event handlers when it ends; otherwise, you will leak memory in some cases.

Take Advantage of Windows 7 Features

Solution: Use the free Windows API Code Pack for Microsoft® .NET Framework, available at the MSDN code library. (The URL at the time of writing was http://code.msdn.microsoft.com/WindowsAPICodePack.)

Common File Dialogs

The Vista and Windows 7 file dialogs are shown when using the normal Windows Forms OpenFileDialog class, but using the CommonOpenFileDialog class from the API Pack provides a few more options, such the ability to add the file to the Most Recently Used list:

image

Access Libraries

You can use the API Pack’s CommonOpenFileDialog class to access non-filesystem objects, such as the Libraries introduced in Windows 7:

image

Retrieve Power State Information

Solution: Use the API Code pack mentioned in the previous section. There is a PowerManager class with static properties to read power information.

bool isBatteryPresent = PowerManager.IsBatteryPresent;
string powerSource = PowerManager.PowerSource.ToString();

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

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