C # users often ask two questions: "Why should I write additional code to use features that are built into Windows?" Why is there no corresponding content in the framework for me to do this task? "When the framework team built their. NET section, they evaluated the work that needed to be done to make the. NET programmer able to use Win32, and found the Win32 API set very large." 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 ones. 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 call a function, and then the runtime uses this information to invoke it. Another approach is to wrap the function using Managed Extensions to C + +, which will be covered in a later column.
The best way to understand how to do this is through the example. In some cases, I only give some of the code, and the complete code is available for download.
Simple example
In the first example, we will invoke the Beep () API to emit sound. First, I need to write the appropriate definition for Beep (). Looking at the definition in MSDN, I found that it has the following prototype:
BOOL Beep (
DWORD Dwfreq,//Voice frequency
DWORD dwduration//Sound duration
);
To write this prototype in C #, you need to convert the Win32 type to the corresponding C # type. Since a DWORD is a 4-byte integer, we can use int or uint as the C # corresponding type. Because int is a CLS-compliant type (which can be used with all. NET languages), it is more commonly used than uint, and in most cases the difference between them is not important. The type bool corresponds to bool. Now we can write the following prototype in C #:
public static extern bool Beep (int frequency, int duration);
This is a fairly standard definition, except that we use extern to indicate that the actual code for the function is elsewhere. This prototype will tell the runtime how to call the 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 find that Beep () is defined in Kernel32.lib. This means that the Run-time code is included in the Kernel32.dll. We add the DllImport attribute 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 a random sound that is common in sci-fi movies in the the 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);
}
}
}
}
It's loud 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, and the runtime can make P/invoke calls.
Enumerations and Constants
Beep () can be used to emit arbitrary sounds, but sometimes we want to emit certain types of sounds, so we use MessageBeep () instead. MSDN gives the following prototypes:
BOOL MessageBeep (
UINT Utype//voice type
);
This looks simple, but there are two interesting facts that can be found in the comments.
First, the Utype parameter actually accepts a set of predefined constants.
Second, the possible parameter values include-1, which means that although it is defined as a UINT type, int is more appropriate.
For Utype parameters, it is reasonable to use the enum type. MSDN lists the named constants, but does not give any hint as to 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 in program FilesMicrosoft Visual Studio. Netvc7platformsdkinclude under.
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:
This function contains pointers to a structure that we have not yet processed. To handle the structure, we need to define the structure in C #. We start from an unmanaged definition:
In this prototype, we use "ref" to indicate that the struct pointer will be passed instead of the struct value. This is a general method of dealing with structures passed by pointers.
This function works well, but it is best to define the ACLineStatus and Batteryflag fields as enums:
Note that because the structure's fields are some bytes, we use byte as the basic type of the enum.
String
Although there is only one. NET string type, this type of string is unique in unmanaged applications. You can use character pointers and structs with inline character arrays, where each array requires the correct marshaling.
There are also two different string representations in Win32:
Ansi
Unicode
Initial Windows uses single-byte characters, which saves storage space, but requires complex multibyte encodings when dealing with many languages. Windows NT? When it appears, it uses Double-byte Unicode encoding. To address this difference, the Win32 API uses a very smart 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 struct (which contains character data), the Win32 API defines two versions of the structure, the Ansi encoding with A suffix, and the wide encoding (that is, Unicode) in W. If you compile a C + + program to 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 the platform, they provide built-in support to automate the use of A or W versions. If the function you call does not exist, the interop layer will find and use a or W version for you.
Examples can be a good illustration of some of the subtleties of string support.
Simple string
The following is a simple example of a function that takes a string parameter:
BOOL GetDiskFreeSpace (
LPCTSTR lpRootPathName,//root path
Lpdword Lpsectorspercluster,//number of sectors per cluster
Lpdword Lpbytespersector,//number of bytes per sector
Lpdword lpnumberoffreeclusters,//number of sectors available
Lpdword lptotalnumberofclusters//Sector total
);
The root path is defined as LPCTSTR. This is a platform-independent string pointer.
Because there is no function named GetDiskFreeSpace (), the marshaler automatically finds the "A" or "W" variant and calls the corresponding function. We use a property to tell the marshaler what type of string the API requires.
Here's a complete definition of the function, as I started to define it:
[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, when I try to run, the function is not executed. 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 trying to invoke the ANSI function with a Unicode string will Failed.
There are two ways to solve this problem: one simple way is to remove the MarshalAs attribute. If you do this, you will always call a version of the function, which is a good method if you have this version on all the platforms you are involved in. However, this slows down the execution of the code because the marshaler converts the. NET string from Unicode to multibyte, and 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, you need to tell the marshaler to look for A version on the Win9x platform and find the W version on the NT platform. To achieve this, you can set CharSet as part of the DllImport property:
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 the string type and use LPTStr. However, there are also functions that do not use the A/W mechanism and must take a different approach to these functions.
String buffer
The string type in. NET is an immutable type, which means that its value will always remain unchanged. For functions that copy string values to a string buffer, the string will not be valid. Doing so will at least break the temporary buffer created by the marshaler as it transforms the string, which in turn destroys the managed heap, which usually leads to errors. 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:est.jpg", ShortPath, shortpath.capacity);
string s = shortpath.tostring ();
Note that the StringBuilder Capacity passes the buffer size.
Structure with an array of inline characters
Some functions accept structures that have an array of inline characters. 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 types 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 about it; the other is timezoneinformation, whose definition is more complicated:
[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 to this definition. The first one is the MarshalAs property:
Looking at the BYVALTSTR document, we found that the property is used for an inline character array, 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. This usually means that some of the interop covers some memory, indicating that there is an error in the size of the structure. I used marshal.sizeof () to get the size of the marshaler used, resulting in 108 bytes. I further investigated and soon recalled that the default character type used for Interop is Ansi or Single-byte. The character type in the function definition is WCHAR and is double-byte, which causes the problem.
I made corrections by adding the StructLayout property. Structs are laid out in order by default, which means that all fields are sorted in the order they are listed. The value of CharSet is set to Unicode so that the correct character type is always used.
After this process, the function is normal. You might want to know 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 more than one item of data, it is usually implemented by a callback mechanism. The developer passes the function pointer to the function, and then calls the developer's function for each item.
Instead of a function pointer in C #, you use a delegate to use a delegate instead of a function pointer when calling the WIN32 function.
The EnumDesktops () function is an example of this type of function:
BOOL EnumDesktops (
Hwinsta hwinsta,//window instance handle
Desktopenumproc Lpenumfunc,//callback function
LPARAM LPARAM//value for the callback function
);
The Hwinsta type is replaced by INTPTR, and LPARAM by int instead. The Desktopenumproc need more work. The following is the definition in MSDN:
There is an important trick in using delegates in Interop: The marshaler creates a function pointer to the delegate, which is passed to the unmanaged function. However, the marshaler cannot determine what the unmanaged function is doing with the function pointer, so it assumes that the function pointer is only valid when the function is invoked.
The result is that if you call a function such as SetConsoleCtrlHandler () where the function pointers will be saved for future use, you need to make sure that you reference the delegate in your code. If this is not done, the function may be executed superficially, but the delegate will be deleted in future memory reclamation and an error will occur.
Other advanced functions
The examples I've listed so far are simpler, but there are many more complex Win32 functions. Here is an example:
The first two parameters are simple to handle: ULONG is simple, and you can use UnmanagedType.LPArray to marshal buffers.
But the third and fourth parameters have some problems. The problem is how to define ACLs. The ACL structure defines only the ACL headers, and the remainder of the buffer consists of aces. Aces can have many different types, and the lengths of these different types of aces are 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.
Other options for properties
The DllImport and StructLayout properties have some very useful options to help p/invoke use. 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 to the calling convention of 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 invoked using stdcall (default), then the function can execute, but the function arguments will not be removed from the stack, which will cause the stack to fill up.
CharSet
Controls whether a variant is invoked 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 closes the lookup attribute for a and W.
PreserveSig
COM Interop makes the function with the final output parameter appear to be the value returned by it. This property is used to turn off this attribute.
SetLastError
Be sure to invoke the Win32 API SetLastError () so that you can identify the error that occurred.
StructLayout
LayoutKind
Structs are laid out in order by default, and are applicable in most cases. If you need to fully control where the structure members are placed, you can use layoutkind.explicit and then add the FieldOffset attribute for each struct member. This is usually necessary 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 structure size. Not common, but you might use this property if you need to allocate extra space at the end of the structure.
Load from different locations
You cannot specify where you want DllImport to look for files at run time, but you can use one trick to do this.
DllImport calls LoadLibrary () to complete its work. If a particular DLL is already loaded in the process, LoadLibrary () succeeds 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 () in advance, thereby pointing your call to another DLL. 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 of definitions are incorrect. Here are a few common questions:
1.long!= Long. In C + +, Long is a 4-byte integer, but in C # it is a 8-byte integer.
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.