[Go] using the Win32 class library in C # programming

Source: Internet
Author: User

http://blog.163.com/j_yd168/blog/static/496797282008611326218/

C # users frequently ask two questions: "Why should I write additional code to use features built into Windows?" Why is there no corresponding content in the framework to complete this task for me? "When the framework team built their. NET parts, they evaluated the work needed to get. NET programmers to use Win32, and found that the Win32 API set was huge. They do not have enough resources to write managed interfaces for all Win32 APIs, test them, and write documents, so they can only prioritize the most important parts. Many common operations have managed interfaces, but there are many complete Win32 parts that do not have managed interfaces.

Platform Invoke (P/invoke) is the most common way to accomplish this task. To use P/invoke, you can write a prototype that describes how to invoke a function, and then the runtime uses this information to make the call. Another approach is to use Managed Extensions to C + + to wrap functions, which are described in a later column.

The best way to understand how to do this is through the example. In some cases, I've only given part of the code, and the full code is available for download.

Simple example

In the first example, we call the Beep () API to make a sound. First, I need to write an appropriate definition for Beep (). Looking at the definition in MSDN, I found that it had the following prototypes:

BOOL Beep (

DWORD dwfreq,//Sound frequency

DWORD dwduration//Sound duration

);

To write this prototype in C #, you need to convert the Win32 type to the appropriate C # type. Since the DWORD is a 4-byte integer, we can use an int or uint as the C # corresponding type. Because int is a CLS-compliant type (which can be used in all. NET languages), this is more common than uint, and in most cases, the difference between them is not important. The bool type corresponds to BOOL. Now we can write the following prototypes in C #:

public static extern bool Beep (int frequency, int duration);

 

This is a fairly standard definition, except that we use extern to indicate the actual code of the function elsewhere. This prototype tells the runtime how to call the function, and now we need to tell it where to find the function.

We need to review the code in MSDN. In the reference information, we find that Beep () is defined in the Kernel32.lib. This means that the run-time code is included in the Kernel32.dll. We add the DllImport property to the prototype to tell the runtime:

[DllImport ("kernel32.dll")]

This is all the work we have to do. The following is a complete example of the random sounds generated in the the 1960s sci-fi movies that are common.

Using System;

Using System.Runtime.InteropServices;

Namespace Beep

{

Class Class1

{

[DllImport ("kernel32.dll")]

public static extern bool Beep (int frequency, int duration);

static void Main (string[] args)

{

Random random = new random ();

for (int i = 0; i < 10000; i++)

{

Beep (random. Next (10000), 100);

}

}

}

}

It sounds enough to excite any listener! Because DllImport allows you to invoke any code in Win32, it is possible to invoke malicious code. So you must be a fully trusted user for the runtime to make P/invoke calls.

Enumerations and Constants

Beep () can be used to emit arbitrary sounds, but sometimes we want to emit a certain type of sound, so we use MessageBeep () instead. MSDN gives the following prototypes:

BOOL MessageBeep (

UINT utype//sound type

);

This may seem simple, but there are two interesting facts that can be found in the comments.

First, the Utype parameter actually accepts a set of pre-defined constants.

Second, the possible parameter values include-1, which means that although it is defined as a UINT type, int is more appropriate.

For the Utype parameter, it is reasonable to use the enum type. MSDN lists the named constants, but does not give any hints about the specific values. Because of this, we need to look at the actual API.

If you have Visual Studio installed? and C + +, the Platform SDK is located under \program Files\Microsoft Visual Studio. Net\vc7\platformsdk\include.

To find these constants, I performed a findstr in the directory.

Findstr "Mb_iconhand" *.h

It determines that the constants are in Winuser.h, and then I use these constants to create my enum and prototype:

public enum Beeptype

{

Simplebeep =-1,

Iconasterisk = 0x00000040,

Iconexclamation = 0x00000030,

Iconhand = 0x00000010,

Iconquestion = 0x00000020,

Ok = 0x00000000,

}

[DllImport ("User32.dll")]

public static extern bool MessageBeep (Beeptype beeptype);

Now I can invoke it with the following statement: MessageBeep (beeptype.iconquestion);

Processing structure

Sometimes I need to determine the battery condition of my laptop. The WIN32 provides power management functions for this purpose.

Search MSDN to find the GetSystemPowerStatus () function.

BOOL GetSystemPowerStatus (

Lpsystem_power_status Lpsystempowerstatus

);

This function contains a pointer to a struct that we have not processed. To work with structs, we need to define the structure in C #. We start with an unmanaged definition:

typedef struct _SYSTEM_POWER_STATUS {

BYTE ACLineStatus;

BYTE Batteryflag;

BYTE batterylifepercent;

BYTE Reserved1;

DWORD Batterylifetime;

DWORD BatteryFullLifetime;

} system_power_status, *lpsystem_power_status;

Then, you get the C # version by replacing C type with a C # type.

struct SystemPowerStatus

{

BYTE ACLineStatus;

BYTE Batteryflag;

BYTE BatteryLifePercent;

BYTE reserved1;

int batterylifetime;

int batteryfulllifetime;

}

This makes it easy to write a C # prototype:

[DllImport ("kernel32.dll")]

public static extern bool GetSystemPowerStatus (

Ref SystemPowerStatus SystemPowerStatus);

In this prototype, we use "ref" to indicate that the struct pointer will be passed instead of the structure value. This is the general method of handling the structure passed through the pointer.

This function works fine, but it's a good idea to define the ACLineStatus and Batteryflag fields as enums:

Enum Aclinestatus:byte

{

Offline = 0,

Online = 1,

Unknown = 255,

}

Enum Batteryflag:byte

{

High = 1,

Low = 2,

Critical = 4,

Charging = 8,

Nosystembattery = 128,

Unknown = 255,

}

Note that because the structure field is some bytes, we use byte as the base type of the enum.

String

Although there is only one. NET string type, this type of string has several unique features in an unmanaged application. You can use character pointers and structures with inline character arrays, where each array requires proper marshaling.

There are two different string representations in Win32:

Ansi

Unicode

The original Windows uses single-byte characters, which saves storage space, but requires complex multibyte encodings when working with many languages. Windows NT? After it appears, it uses a double-byte Unicode encoding. To address this difference, the Win32 API uses a very clever approach. It defines the TCHAR type, which is a single-byte character on the Win9x platform and is a double-byte Unicode character on the WinNT platform. For each function that accepts a string or structure that contains character data, the Win32 API defines two versions of the structure, specifying ANSI encoding with A suffix and W indicating wide encoding (that is, Unicode). If you compile a C + + program into a single byte, you get A variant, and if you compile to Unicode, you get a W variant. The Win9x platform contains the Ansi version, while the WinNT platform contains the W version.

Because P/invoke designers don't want you to worry about your platform, they provide built-in support to automatically use A or W version. If the function you call does not exist, the interop layer will find and use a or W version for you.

Some of the subtleties of string support are well illustrated by examples.

Simple string

Here is a simple example of a function that takes a string argument:

BOOL GetDiskFreeSpace (

LPCTSTR lpRootPathName,//root path

Lpdword Lpsectorspercluster,//number of sectors per cluster

Lpdword Lpbytespersector,//number of bytes per sector

Lpdword lpnumberoffreeclusters,//number of available sectors

Lpdword lptotalnumberofclusters//Sectors Total

);

The root path is defined as LPCTSTR. This is a platform-independent string pointer.

Because there is no function named GetDiskFreeSpace (), the marshaler will automatically look for a variant of "A" or "W" and invoke the corresponding function. We use a property to tell the marshaler what type of string the API requires.

The following is a complete definition of the function, as I began to define:

[DllImport ("kernel32.dll")]

static extern bool GetDiskFreeSpace (

[MarshalAs (UNMANAGEDTYPE.LPTSTR)]

String Rootpathname,

ref int SectorsPerCluster,

ref int BytesPerSector,

ref int Numberoffreeclusters,

ref int totalnumberofclusters);

Unfortunately, the function cannot be executed when I try to run it. The problem is that no matter which platform we are on, the marshaler tries to find the ANSI version of the API by default, because LPTSTR means that Unicode strings are used on the Windows NT platform, so attempting to invoke the ANSI function with a Unicode string will Failed.

There are two ways to solve this problem: an easy way is to delete the MarshalAs property. If you do this, you will always call a version of the function, which is a good way to do this if you have this version on all of the platforms that you are involved in. However, this slows down the execution of the code because the marshaler converts the. NET string from Unicode to multibyte, then calls a version of the function (converts the string back to Unicode), and finally calls the W version of the function.

To avoid this situation, you need to tell the marshaler to look for A version on the Win9x platform while looking for the W version on the NT platform. To achieve this, you can set CharSet as part of the DllImport property:

[DllImport ("kernel32.dll", CharSet = CharSet.Auto)]

  

In my informal timing test, I found that this approach was about 5% faster than the previous one.

For most Win32 APIs, you can set the CharSet property on a string type and use LPTStr. However, there are some functions that do not use the A/W mechanism, and you must take different methods for these functions.

string buffers

The string type in. NET is an immutable type, which means that its value will always remain the same. For a function to copy a string value to a string buffer, the string will be invalid. Doing so at least destroys the temporary buffer that is created by the marshaler when the string is converted, and when it is severely destroys the managed heap, which usually results in an error. It is not possible to get the correct return value in either case.

To resolve this issue, we need to use a different type. The StringBuilder type is designed to be used as a buffer, and we will use it instead of a string. Here is an example:

[DllImport ("kernel32.dll", CharSet = CharSet.Auto)]

public static extern int GetShortPathName (

[MarshalAs (UNMANAGEDTYPE.LPTSTR)]

String path,

[MarshalAs (UNMANAGEDTYPE.LPTSTR)]

StringBuilder ShortPath,

int shortpathlength);

Using this function is simple:

StringBuilder ShortPath = new StringBuilder (80);

int result = GetShortPathName (@ "D:\test.jpg", ShortPath, shortpath.capacity);

string s = shortpath.tostring ();

Note that the capacity of StringBuilder passes the buffer size.

structure with inline character array

Some functions accept structures with inline character arrays. For example, the GetTimeZoneInformation () function accepts pointers to the following structure:

typedef struct _TIME_ZONE_INFORMATION {

LONG Bias;

WCHAR standardname[32];

SYSTEMTIME standarddate;

LONG StandardBias;

WCHAR daylightname[32];

SYSTEMTIME daylightdate;

LONG DaylightBias;

} time_zone_information, *ptime_zone_information;

Using it in C # requires two kinds of structure. One is SYSTEMTIME, and its settings are simple:

struct SystemTime

{

public short wyear;

public short wmonth;

public short Wdayofweek;

public short wday;

public short whour;

public short Wminute;

public short Wsecond;

public short wmilliseconds;

}

There is nothing special here; the other is timezoneinformation, which is more complex in its definition:

[StructLayout (layoutkind.sequential, CharSet = CharSet.Unicode)]

struct TimeZoneInformation

{

public int bias;

[MarshalAs (UnmanagedType.ByValTStr, SizeConst = 32)]

public string StandardName;

SystemTime standarddate;

public int standardbias;

[MarshalAs (UnmanagedType.ByValTStr, SizeConst = 32)]

public string DaylightName;

SystemTime daylightdate;

public int DaylightBias;

}

There are two important details of this definition. The first one is the MarshalAs property:

[MarshalAs (UnmanagedType.ByValTStr, SizeConst = 32)]

 

Looking at ByValTStr's documentation, we find that this property is used for inline character arrays, and the other is SizeConst, which is used to set the size of the array.

The first time I wrote this code, I encountered an execution engine error. Typically this means that some of the interoperability overrides some memory, indicating that there is an error in the size of the structure. I use Marshal.SizeOf () to get the size of the marshaler used, and the result is 108 bytes. I made a further investigation and soon recalled that the default character type for Interop is Ansi or single-byte. The character type in the function definition is WCHAR, which is double-byte, which causes the problem.

I made corrections by adding the StructLayout property. Structs are laid out sequentially by default, which means that all fields are arranged in the order in which they are listed. The value of CharSet is set to Unicode so that the correct character type is always used.

After this processing, the function is normal. You may wonder why I don't use CharSet.Auto in this function. This is because it also does not have A and W variants, and always uses Unicode strings, so I used the above method encoding.

Functions that have callbacks

When the Win32 function needs to return multiple data, it is usually done through a callback mechanism. The developer passes the function pointer to the function and then invokes the developer's function for each item.

Instead of a function pointer in C #, a delegate is used instead of a function pointer when the WIN32 function is called.

The EnumDesktops () function is an example of such a function:

BOOL EnumDesktops (

Hwinsta Hwinsta,//Handle of Window instance

Desktopenumproc Lpenumfunc,//callback function

LPARAM LPARAM//value for callback function

);

The Hwinsta type is replaced by INTPTR, while LPARAM is replaced by Int. Desktopenumproc needs a little more work. The following is the definition in MSDN:

BOOL CALLBACK EnumDesktopProc (

LPTSTR lpszdesktop,//Desktop name

LPARAM LPARAM//user-defined values

);

We can convert it to the following delegate:

delegate bool EnumDesktopProc ([MarshalAs (UNMANAGEDTYPE.LPTSTR)] string desktopname,int lParam);

With that definition in order, we can write the following definition for EnumDesktops ():

[DllImport ("user32.dll", CharSet = CharSet.Auto)]

static extern bool EnumDesktops (

IntPtr Windowstation,

EnumDesktopProc Callback,

int lParam);

This will allow the function to run normally.

There is an important trick to using delegates in Interop: The marshaler creates a function pointer to the delegate that is passed to the unmanaged function. However, the marshaler cannot determine what the unmanaged function will do with the function pointer, so it assumes that the function pointer is only valid when the function is called.

The result is that if you call a function such as SetConsoleCtrlHandler (), where the function pointer is saved for future use, you need to ensure that the delegate is referenced in your code. If you do not, the function may be executed on the surface, but the delegate will be removed in the future memory recycling process and an error will occur.

Other advanced functions

The examples I have listed so far are relatively simple, but there are many more complex Win32 functions. Here is an example:

DWORD SetEntriesInAcl (

ULONG ccountofexplicitentries,//number of items

Pexplicit_access plistofexplicitentries,//Buffer

PACL Oldacl,//Raw ACL

PACL *newacl//New ACL

);

The first two parameters are simple to handle: ULONG is simple, and you can use UnmanagedType.LPArray to marshal buffers.

But there are some problems with the third and fourth parameters. The problem is how to define ACLs. The ACL structure defines only the ACL header, and the rest of the buffer is made up of aces. Aces can have many different types, and the lengths of these different types of Aces are also different.

If you are willing to allocate space for all buffers and are willing to use less secure code, you can use C # for processing. But the workload is huge and the program is very difficult to debug. Using C + + to process this API is much easier.

Additional Options for properties

The DLLImport and StructLayout properties have some very useful options to help with the use of P/invoke. All of these options are listed below:

DLLImport

CallingConvention

You can use it to tell the marshaler what calling conventions the function uses. You can set it as the calling convention for your function. Typically, if this setting is incorrect, the code will not execute. However, if your function is a Cdecl function and the function is called using stdcall (the default), the function can execute, but the function arguments are not removed from the stack, which causes the stack to fill up.

CharSet

Controls whether a variant is called or a W variant is called.

EntryPoint

This property is used to set the name that the marshaler looks for in the DLL. After you set this property, you can rename the C # function to any name.

ExactSpelling

Set this property to true, and the marshaler turns off the lookup attribute for a and W.

PreserveSig

COM Interop makes a function that has a final output parameter appear to be the value returned by it. This property is used to turn off this feature.

SetLastError

Make sure to call Win32 API SetLastError () so you can identify the error that occurred.

StructLayout

LayoutKind

Structs are laid out sequentially by default, and are applicable in most cases. If you need full control over where the struct members are placed, you can use layoutkind.explicit and then add the FieldOffset property for each struct member. This is usually required when you need to create a union.

CharSet

Controls the default character type for BYVALTSTR members.

Pack

Sets the compression size of the structure. It controls how the structure is arranged. You may need to set this property if the C structure uses other compression methods.

Size

Sets the size of the structure. Not commonly used, but this property may be used if you need to allocate additional space at the end of the structure.

Loading from different locations

You cannot specify where you want DLLImport to look for files at run time, but you can use a technique to achieve this.

DllImport calls LoadLibrary () to complete its work. If a specific DLL is already loaded in the process, LoadLibrary () will succeed even if the specified load path is different.

This means that if you call LoadLibrary () directly, you can load the DLL from any location, and then DllImport LoadLibrary () will use the DLL.

Because of this behavior, we can call LoadLibrary () ahead of time to point your calls to other DLLs. If you are writing a library, you can prevent this by calling GetModuleHandle () to ensure that the library is not loaded before the first call to P/invoke.

P/invoke Troubleshooting

If your P/invoke call fails, it is usually because some types are not defined correctly. Here are a few FAQs:

1.long! = long. In C + +, Long is a 4-byte integer, but in C #, it is a 8-byte integer.

2. String type is not set correctly.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.