C # Win32 API programming (detailed)

Source: Internet
Author: User

C # Win32 API Programming
C # users often ask two questions: "Why should I write additional code to use features built in windows? Why can't I complete this task for me without relevant content in the framework ?" When the Framework Team built their. Net part, they evaluated the work needed to enable. Net programmers to use Win32, and found that the Win32 API set was very large. They do not have enough resources to write managed interfaces for all Win32 APIs, test them, and write documents, so they have to prioritize the most important part. Many common operations have hosted interfaces, but many complete Win32 operations are not hosted.

Platform call (P/invoke) is the most common method to complete this task. To use P/invoke, You can compile a prototype that describes how to call a function, and then use this information to call the function at runtime. Another method is to use managed extensions to C ++ to wrap functions. This part will be introduced in future columns.

The best way to understand how to complete this task is through examples. In some examples, I only provide some code. The complete code can be downloaded.

Simple Example

In the first example, we will call the BEEP () API to make a sound. First, I need to write appropriate definitions for beep. Looking at the definition in msdn, I found it has the following prototype:

Bool BEEP (
DWORD dwfreq, // Audio Frequency
DWORD dwduration // audio duration
);

To use C # to compile this prototype, You need to convert the Win32 type to the corresponding C # type. Because DWORD is a 4-byte integer, we can use Int or uint as the corresponding type of C. Since Int Is a cls compatible type (can be used in all. NET languages), it is more common than uint, and in most cases, the difference between them is not important. The bool type corresponds to the bool type. Now we can use C # To compile the following prototype:

Public static extern bool BEEP (INT frequency, int duration );
 
This is a standard definition, but we use extern to indicate that the actual code of the function is elsewhere. This prototype tells the runtime how to call a function; now we need to tell it where to find the function.

We need to review the code in msdn. In the reference information, we found that beep () is defined in kernel32.lib. This means that the runtime code is included in kernel32.dll. We add the dllimport attribute in the prototype to notify the runtime of this information:

[Dllimport ("kernel32.dll")]

This is all we have to do. The following is a complete example. The random sound generated by it is very common in sci-fi movies in 1960s.

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 );
}
}
}
}

The sound is enough to stimulate any listener! Because dllimport allows you to call any code in Win32, malicious code may be called. Therefore, you must be a fully trusted user before calling p/invoke.

Enumeration and constants

Beep () can be used to make arbitrary sound, but sometimes we want to make a specific type of sound, so we use messagebeep () instead (). Msdn provides the following prototype:

Bool messagebeep (
Uint utype // sound type
);

This looks simple, but two interesting facts can be found in the annotations.

First, the utype parameter actually accepts a set of predefined constants.

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

It is reasonable to use the enum type for the utype parameter. Msdn lists named constants, but does not provide any prompts for specific values. Because of this, we need to check the actual API.

If you have installed Visual Studio? And C ++, the Platform SDK is located in/program files/Microsoft Visual Studio. NET/vc7/platformsdk/include.

To find these constants, I executed a findstr in this directory.

Findstr "mb_iconhand" *. h

It determines that constants are located 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 );

Now I can use the following statement to call it: messagebeep (beeptype. iconquestion );
Processing Structure

Sometimes I need to determine the battery condition of my laptop. Win32 provides power management functions.

Search for msdn and find the getsystempowerstatus () function.

Bool getsystempowerstatus (
Lpsystem_power_status lpsystempowerstatus
);

This function contains a pointer to a structure that we have not processed. To process the structure, we need to use C # to define the structure. We start with the definition of unmanaged systems:

Typedef struct _ system_power_status {
Byte aclinestatus;
Byte batteryflag;
Byte batterylifepercent;
Byte reserved1;
DWORD batterylifetime;
DWORD batteryfulllifetime;
} System_power_status, * lpsystem_power_status;

Then, use the C # type instead of the C type to get the C # version.

Struct systempowerstatus
{
Byte aclinestatus;
Byte batteryflag;
Byte batterylifepercent;
Byte reserved1;
Int batterylifetime;
Int batteryfulllifetime;
}

In this way, you can easily compile the C # prototype:

[Dllimport ("kernel32.dll")]
Public static extern bool getsystempowerstatus (
Ref systempowerstatus );

In this prototype, we use "Ref" to specify that the structure pointer will be passed instead of the structure value. This is a general method for processing the structure passed through pointers.

This function works well, but it is best to define the aclinestatus and batteryflag fields as Enum:

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 fields in the structure are some bytes, we use byte as the basic type of The enum.

String

Although there is only one. Net string type, this string type has several unique features in an unmanaged application. You can use character pointers and structures with nested character arrays. Each array needs to be properly encapsulated.

There are two different string representations in Win32:

ANSI
Unicode

Originally, Windows used single-byte characters, which saved storage space, but complicated multi-byte encoding was required when processing many languages. Windows NT? When it appears, it uses double-byte unicode encoding. To solve this difference, Win32 API adopts a very clever approach. It defines the tchar type, which is a single-byte character on the Win9x Platform and a double-byte UNICODE character on the WINNT platform. For each function that accepts a string or structure (including character data), Win32 API defines two versions of the structure and specifies ANSI encoding with the suffix, use W to specify the wide encoding (UNICODE ). If you compile the C ++ program into a single byte, the variant is obtained. If the C ++ program is compiled into Unicode, the W variant is obtained. The Win9x Platform contains the ANSI version, while the WINNT platform contains the W version.

Since P/invoke designers do not want you to worry about your platform, they provide built-in support to automatically use version A or version W. If the called function does not exist, the interoperability layer searches for and uses version A or version W for you.

The example can be used to illustrate the subtlety of String Support.

Simple string

The following is a simple example of a function that accepts string parameters:

Bool getdiskfreespace (
Lpctstr lprootpathname, // root path
Lpdword l1_ctorspercluster, // number of sectors of each cluster
Lpdword lpbytespersector, // The number of bytes per slice
Lpdword lpnumberoffreeclusters, // number of available sectors
Lpdword lptotalnumberofclusters // total number of sectors
);

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

Because there is no function named getdiskfreespace (), the mail collector will automatically find the "a" or "W" variant and call the corresponding function. We use an attribute to indicate the string type required by the API.

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

[Dllimport ("kernel32.dll")]
Static extern bool getdiskfreespace (
[Financialas (unmanagedtype. lptstr)]
String rootpathname,
Ref int sectorspercluster,
Ref int bytespersector,
Ref int numberoffreeclusters,
Ref int totalnumberofclusters );

Unfortunately, this function cannot be executed when I try to run it. The problem is that, no matter which platform we are on, the sender and receiver try to find the ANSI version of the API by default. Because lptstr means that Unicode strings will be used on Windows NT platform, therefore, an attempt to use a unicode string to call the ANSI function fails.

There are two ways to solve this problem: a simple method is to delete the externalas attribute. If this is done, the function version A will always be called. If this version is available on all the platforms involved, this is a good method. However, this will reduce the code execution speed, because the mail collector will. net string is converted from Unicode to multi-byte, then the version of the function is called (the string is converted back to Unicode), and the W version of the function is called.

To avoid this problem, you need to tell the mail collector to find version A on Win9x and version w on NT. To achieve this, set charset to a part of the dllimport attribute:

[Dllimport ("kernel32.dll", charset = charset. Auto)]
  
In my informal timing test, I found this approach was about 5% faster than the previous method.

For most Win32 APIs, you can set the charset attribute for the string type and use lptstr. However, some functions that do not adopt the/W mechanism must adopt different methods for these functions.

String Buffer

The string type in. NET is unchangeable, which means its value will remain unchanged forever. For a function that copies string values to the string buffer, the string is invalid. In this way, at least the temporary buffer created by the mail collector during String Conversion will be damaged; in severe cases, the managed heap will be damaged, which usually leads to errors. In either case, the correct return value cannot be obtained.

To solve this problem, we need to use another type. The stringbuilder type is designed as a buffer. We will use it to replace the string. The following is an example:

[Dllimport ("kernel32.dll", charset = charset. Auto)]
Public static extern int getaskpathname (
[Financialas (unmanagedtype. lptstr)]
String path,
[Financialas (unmanagedtype. lptstr)]
Stringbuilder export path,
Int bytes pathlength );

Using this function is simple:

Stringbuilder export Path = new stringbuilder (80 );
Int result = getaskpathname (@ "D:/test.jpg", using path, using path. capacity );
String S = export path. tostring ();

Note that the capacity of stringbuilder transmits the buffer size.

Structure with nested character array

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

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;

To use it in C #, two structures are required. One is systemtime, which is easy to set:

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. Its definition must be more complex:

[Structlayout (layoutkind. Sequential, charset = charset. Unicode)]
Struct timezoneinformation
{
Public int bias;
[Financialas (unmanagedtype. byvaltstr, sizeconst = 32)]
Public String standardname;
Systemtime standarddate;
Public int standardbias;
[Financialas (unmanagedtype. byvaltstr, sizeconst = 32)]
Public String daylightname;
Systemtime daylightdate;
Public int daylightbias;
}

This definition has two important details. The first is the financialas attribute:

[Financialas (unmanagedtype. byvaltstr, sizeconst = 32)]
 
View the byvaltstr document. We found that this attribute is used for the embedded character array; the other is sizeconst, which is used to set the size of the array.

When I wrote this code for the first time, I encountered an execution engine error. This usually means that some interoperability overwrites some memory, indicating that the size of the structure is incorrect. I used marshal. sizeof () to obtain the size of the used mail collector. The result is 108 bytes. I further investigated and soon recalled that the default character type for interoperability is ANSI or single-byte. In the function definition, the character type is wchar, which is double byte, thus leading to this problem.

I corrected it by adding the structlayout attribute. Structure is arranged in order by default, which means all fields are arranged in the order they are listed. The charset value is set to Unicode to always use the correct character type.

After such processing, the function is normal. You may wonder why charset. Auto is not used in this function. This is because it does not have a and W variants, but always uses Unicode strings, so I use the above encoding method.

Functions with callback

When Win32 functions need to return multiple pieces of data, they are usually implemented through the callback mechanism. The developer passes the function pointer to the function and calls the function of the developer for each item.

In C #, function pointers are not provided. Instead, "delegates" are used to call Win32 functions instead of function pointers.

The enumdesktops () function is an example of this type of function:

Bool enum1_tops (
Hwinsta, // handle of the window instance
Desktopenumproc lpenumfunc, // callback function
Lparam // the value used for the callback function
);

The hwinsta type is replaced by intptr, while lparam is replaced by Int. Worker topenumproc requires more work. The definitions in msdn are as follows:

Bool callback enum1_topproc (
Lptstr lpszdesktop, // desktop name
Lparam // user-defined value
);

We can convert it to the following delegate:

Delegate bool enum1_topproc ([financialas (unmanagedtype. lptstr)] string shorttopname, int lparam );

After this definition is completed, we can write the following definition for enumdesktops:

[Dllimport ("user32.dll", charset = charset. Auto)]
Static extern bool enum1_tops (
Intptr windowstation,
Enumdesktopproc callback,
Int lparam );

In this way, the function can run normally.

When using delegation in interoperability, there is an important technique: The Mail collector creates a function pointer pointing to the delegate, which is passed to the unmanaged function. However, the mail collector cannot determine what to do with the function pointer for an unmanaged function. Therefore, it assumes that the function pointer is valid only when the function is called.

The result is that if you call a function such as setconsolectrlhandler () and the function pointer is saved for future use, you need to make sure that the delegate is referenced in your code. If this is not done, the function may be executed on the surface, but the delegate will be deleted in future memory reclaim processing and an error will occur.

Other advanced functions

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

DWORD setentriesinacl (
Ulong ccountofexplicitentries, // number of items
Pexplicit_access plistofexplicitentries, // buffer zone
PACl oldacl, // original ACL
PACl * newacl // new ACL
);

The processing of the first two parameters is relatively simple: ulong is very simple, and unmanagedtype. lparray can be used to block the buffer.

However, the third and fourth parameters have some problems. The problem lies in the way the ACL is defined. The ACL structure only defines the ACL header, and the rest of the buffer is composed of ACE. Ace can have multiple different types, and the length of these different types of ACE is also different.

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

Other attributes

The dllimport and structlayout attributes have some very useful options to facilitate the use of P/invoke. All these options are listed below:

Dllimport

Callingconvention

You can use it to tell the mail collector which call conventions are used by the function. You can set it as your function call convention. Generally, if this setting is incorrect, the Code cannot be executed. However, if your function is a cdecl function and stdcall (default) is used to call this function, the function can be executed, but the function parameters are not deleted from the stack, this causes the stack to be filled up.

Charset

Controls whether to call Variant A or variant W.

Entrypoint

This attribute is used to set the name searched by the package collector in the DLL. After setting this attribute, You can rename the C # function as any name.

Exactspelling

Set this attribute to true, and the feifer will disable the search feature of a and W.

Preservesig

Com interoperability makes the function with final output parameters look like it returns this value. This attribute is used to disable this feature.

Setlasterror

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

Structlayout

Layoutkind

The structure is arranged in sequence by default and applies in most cases. To fully control the placement of structure members, you can use layoutkind. Explicit and add the fieldoffset attribute to each structure member. This is usually required when you need to create a Union.

Charset

Controls the default character types of byvaltstr members.

Pack

Set the compression size of the structure. It controls the structure arrangement. If the C structure adopts other compression methods, you may need to set this attribute.

Size

Set the structure size. This attribute is not commonly used. However, you may need to allocate additional space at the end of the structure.

Load from different locations

You cannot specify where you want dllimport to find files at runtime, but you can use one technique to achieve this purpose.
Dllimport calls loadlibrary () to complete its work. If a specific dll has been loaded in the process, loadlibrary () will succeed even if the specified loading 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 this DLL.

Because of this behavior, we can call loadlibrary () in advance to direct your call to other DLL. If you are writing a library, you can call getmodulehandle () to prevent this situation, to ensure that the Library is not loaded before the first call of P/invoke.

P/invoke troubleshooting

If your P/invoke call fails, it is usually because some types are incorrectly defined. The following are some common problems:

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

2. the string type is incorrectly set.

 

Related Article

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.