How to use Win32 and other libraries in C #

zhaozj2021-02-17  55

C # users often ask two questions: "Why do I have to write code to use the function built in Windows? Why don't you complete this task in the framework?" When the frame team builds their .NET section When they evaluated the work that the .NET programmers can use to complete the work, it was found that the Win32 API set was very large. They don't have enough resources to write hosting interfaces for all Win32 APIs, test and write documents, so they can only deal with the most important part. Many common operations have a hosted interface, but there are still many complete Win32 parts without a hosted interface.

Platform call (P / invoke) is the most common method of completing this task. To use P / Invoke, you can write a prototype describing how to call the function, and then use this information to be called. Another method is to use Managed Extensions to C to package functions, which will be described in later columns.

To understand how to complete this task, the best way is to pass an example. In some examples, I only give some code; the complete code can be downloaded.

Simple example

In the first example, we will call the Beep () API to issue sound. First, I need to write appropriate definitions for beep (). View the definition in MSDN, I found it has the following prototype:

Bool Beep (DWORD DWFREQ, / / ​​Sound Frequency DWORD DWDURATION / / Sound Duration);

To write this prototype with C #, you need to convert the Win32 type to the corresponding C # type. Since DWORD is an integer of 4 bytes, we can use int or UINT as a C # corresponding type. Since int is a CLS compatible type (you can be used for 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 prototype with C #: Public Static Extern Bool Beep; This is a quite standard definition, but we use extern to indicate the actual code of the function. This prototype will tell the rule how to call the function; now we need to tell where to find the function. We need to review the code in the MSDN. In the reference information, we found that beep () is defined in kernel32.lib. This means that the runtime code is included in the kernel32.dll. We add DLLIMPORT attributes in the prototype to tell this information: [DLLIMPORT ("kernel32.dll")] This is all our work we have to do. Here is a complete example, which is common in science fiction movies in 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);}}}}

Its sound is enough to stimulate any listener! Since Dllimport allows you to call any code in Win32, there is a possibility to call malicious code. So you must be a completely trusted user, running the P / Invoke call when running. Enumeration and constant beep () can be used to make any sound, but sometimes we want to issue a specific type of sound, so we switch to MessageBeep (). MSDN gives the following prototype: BOOL MessageBeep (uint utype // sound type);

This looks very simple, but you can find two interesting facts from the comment. 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 an UINT type, INT will be more suitable. For uType parameters, use the Enum type is reasonable. MSDN lists the named constant, but does not give any prompts for the specific value. Due to this, we need to view the actual API. If you have Visual Studio and C , Platform SDK is located under Program FileSmicrosoft Visual Studio .Netvc7platformSDkinClude. In order 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 prototypes:

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 call it with the following statement: messagebeep (beeptype.iconquest);

Treatment structure Sometimes I need to determine the battery condition of my notebook. Win32 provides power management functions for this. Search MSDN can find a getSystemPowerStatus () function.

Bool getSystemPowerStatus (lpsystem_power_status lpsystempowerStatus);

This function contains a pointer to a structure, and we have not processed this. To handle the structure, we need to use the C # definition structure. We started from the 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, by replacing C # versions with C # types.

Struct systempowerStatus {byte aclinestatus; byte batteryflag; Byte BatteryLifePercent; Byte Reserved1; Int BatteryLifetime; Int BatteryFull1;}, you can easily write C # prototypes:

[DLLIMPORT ("kernel32.dll")] public static extern Bool getSystemPowerStatus (Ref SystemPowerStatus SystemPowerStatus);

In this prototype, we use "REF" to specify the conveying structure pointer instead of the structure value. This is a general method of processing structures passing through a pointer. This function is working well, but it is best to define the AclineStatus and Batteryflag fields as ENUM:

ENUM ACLINESTATUS: BYTE = 1, Unknown = 255,} enum batteryflag: Byte {high = 1, low = 2, critical = 4, charging = 8, NOSYSTEMBATTERY = 128, unknown = 255,}

Note that since the field of structure is some bytes, we use BYTE as the basic type of the enum. Although there is only one .Net string type, this string type has several uniqueness in the non-hosted application. You can use character pointers and structures having an in-in-cell number, each array requires the correct encapsulation process. There are still two different strings in Win32 indicate: ANSI Unicode initial Windows uses single-byte characters, which can save storage, but complicated multi-byte encoding is required when handling many languages. After Windows NT?, It uses a double-byte Unicode encoding. In order to solve this difference, Win32 API has used very clever practices. It defines TCHAR types that are single-byte characters on the Win9x platform, which is a double-byte Unicode character on the Winnt platform. For each function that accepts a string or structure (which contains character data), the Win32 API defines two versions of the structure, indicating ANSI encoding with a suffix, using W to specify WIDE encoding (ie Unicode). If you compile C programs as single bytes, a variant is obtained if compiled into Unicode, then W variants are obtained. The Win9x platform contains an ANSI version, while the WinNT platform contains W versions. Since P / Invoke designers don't want you to worry about the platform, they provide built-in support to move using A or W versions. If the function you call does not exist, the interoperable layer will find and use a or W version. Through example, some exquisishes supported by the string are well described.

The simple string is a simple example of a function of accepting string parameters:

BOOL GetDiskFreeSpace (LPCTSTR lpRootPathName, // root LPDWORD lpSectorsPerCluster, // number of sectors per cluster LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters // number of bytes per sector, the number of sectors available // LPDWORD lpTotalNumberOfClusters // fan The total number of districts); the root path is defined as LPCTSTR. This is a string pointer independent of the platform. Since there is no function called getDiskFreespace (), the encapsulator will automatically find "A" or "W" variants, and call the corresponding function. We use an attribute to tell the encapsulator, the string type required by the API. The following is a complete definition of this function, just like I start definition:

[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. The problem is that, no matter which platform we are in, the encapsulator tries to find the ANSI version of the API by default. Since lptstr means using the Unicode string on the Windows NT platform, try to call with Unicode strings The ANSI function will fail. There are two ways to solve this problem: a simple way is to delete the Marshalas property. If this is done, the A version of this function will always be called if there is this version on all platforms you have, which is a good way. However, this reduces the execution speed of the code, because the encapsulator is to convert the .NET string from Unicode into multiple bytes, then call the A version of the function (convert a string back to Unicode), and finally call the function of the function version. To avoid this, you will need to tell the encapsulator, to find the A version on the Win9x platform, and look up the W version on the NT platform. To achieve this, you can set the charset to a part of the Dllimport property: [DLLIMPORT ("kernel32.dll", charset = charset.auto)] In my informal timeline test, I found this approach than the previous The method is about 5%. For most Win32 APIs, you can set the character string type and use LPTSTR. However, there are some functions that do not use A / W mechanisms, and for these functions must take different methods.

String buffers. The string type in the string buffer is the type of uncomfortable, which means that its value will remain unchanged. For functions to copy string values ​​to string buffers, strings will be invalid. Doing so at least the temporary buffer created by the encapsulator in the conversion string; when it is severely destroyed, this usually results in errors. No matter which case is not possible to get the correct return value. To resolve this issue, we need to use other types. The StringBuilder type is designed as a buffer, we will use it instead of strings. 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); this The function is simple:

Stringbuilder shortpath = new stringbuilder (80); int result = getshstpathname (@ "d: est.jpg", shortpath, shortpath.capacity; string s = shortpath.toString ();

Note that StringBuilder's Capacity passes the buffer size. Structures with an in-line character array, some functions accept structures having an inner embedded character array. For example, the getTimeZoneInformation () function accepts pointers that point 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;

There are two structures that use it in C #. One is systemTIME, its setting is 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, and its definition is complex:

[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;

This definition has two important details. The first is the Marshalas property: [Marshalas (unmanagedtype.byvaltstr, sizeconst = 32)] View Byvaltstr documents, we discover that this property is used for embedded characters arrays; the other is SIZECONST, which is used to set the size of the array. When I first prepared this code, I met the execution engine error. Usually this means that some interoperability covers some memory, indicating that there is an error in the size of the structure. I use Marshal.sizeOf () to get the size of the encapsulator used and the result is 108 bytes. I have further investigated and soon recall the default character type for interoperability is ANSI or single byte. The character type in the function definition is Wchar, which is double byte, thus causing this problem. I correctly correct it by adding the structlayout attribute. The structure is arranged in order by default, which means that all fields will be arranged in the order they are listed. The value of Charset is set to Unicode to always use the correct character type. After doing so, the function is normal. You may want to know why I don't use Charset.Auto in this function. This is because it does not have a and w variants, but always use the Unicode string, so I use the above method encoding. Functions with callback When the Win32 function needs to return multiple data, it is usually implemented by a callback mechanism. Developers pass the function pointer to the function and then call the developer's function for each item. There is no function pointer in the C #, but use "delegate" to use the delegate instead of the function pointer when calling the Win32 function. Enumdesktops () function is an example of such functions:

Bool Enumdesktops (Hwinsta Hwinsta, // Window Example Handle DesktopenumProc LpenumFunc, // Tonance Function LParam LPARAM // The value of the callback function);

The HWINSTA type is replaced by INTPTR, while the LPARAM is replaced by INT. More work needs to be required for DeskTopenumProc. The following is the definition in the MSDN:

Bool Callback Enumdesktopproc (LPTSTR LPSZDESKTOP, // Desktop Name LParam LPARAM // User-defined Value);

We can convert it to the following commission:

Delegate Bool Enumdesktopproc ([Marshalas (UnmanagedType.lptstr)] String DesktopName, int LPARAM;

After this definition is completed, 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 function can run normally. There is a very important trick when using the entrust in interoperability: Sealing Demolition creates a function pointer to the delegate, which is passed to the non-hosting function. However, the encapsulator cannot determine what the non-hosting function is to use the function pointer, so it assumes that the function pointer only needs to be valid when the function is called. The result is if you call functions such as setConsolectrol, where the function pointer will be saved to use, you need to make sure you are commissioned in your code. If this is not done, the function may be executed on the surface, but the delegate will be removed in the future memory recovery process and an error will occur. Other advanced functions have so far I have listed, but there are many more complex Win32 functions. Below is an example: DWORD STENTRIESINCL (Ulong CcountOfExPlicitEntries, // item PEXPLICIIT_ACCESS PLISTOFEXPLICIT_ACCESS PLISTOFEXPLICITRIES, / / ​​Buffer Pacl Oldacl, // Original ACL PACL * NewaCl // New ACL);

The processes of the first two parameters are relatively simple: ulong is simple, and unmanagedType.lparray can be used to encapsulate buffers. But there are some problems in the third and fourth parameters. The problem is how the ACL is defined. The ACL structure defines only ACL headers, and the rest of the buffer consists of ACE. ACE can have a variety of different types, and the length of these different types of ACE is also different. If you are willing to allocate space for all buffers, you can use C # to process. But the workload is very large, and the program is very difficult to debug. This API is easier to use using C . Other options for properties The DLLIMPORT and STRUCTLAYOUT properties have some very useful options that help P / Invoke use. All of these options are listed below: DLLIMPORT CALLINGCONVENTION You can use it to tell the encapsulator, and the function uses which calls are used. You can set it to your function call agreement. Usually, if this setting is incorrect, the code will not be executed. However, if your function is a CDECL function, and use stdcall (default) to call this function, the function can be executed, but the function parameters will not be removed from the stack, which will cause the stack to be filled. CHARSET control calls A variants or calls W variants. EntryPoint This property is used to set the name of the snap-of-hinter in the DLL. Once this property is set, you can rename the C # function to any name. EXACTSPELING Set this property to True, and the encapsulator will turn off the findings of A and W. PreservesIG COM interoperates makes functions with the final output parameter appear to be returned by it. This property is used to close this feature. SetLastError ensures that Win32 API setLastError () so that you can find out the error. The StructLayout Layoutkind structure is arranged in order by default and applies in most cases. If you need to fully control the location placed in the structure, you can use layoutkind.explicit, then add a fieldoffset property to each structure member. This usually needs to do when you need to create Union. Charset controls the default character type of Byvaltstr member. Pack Set the compression size of the structure. It controls the arrangement of the structure. If the C structure uses other compressed methods, you may need to set this property. Size sets the structure size. Not common; but if you need to assign additional space at the end of the structure, this property may be used. Loading from different locations You cannot specify where you want DLLIMPORT to find files while running, but you can use a technique to achieve this. DllImport calls loadLibrary () to complete its work. If a specific DLL has been loaded in the process, even if the specified load path is different, LoadLibrary () will succeed. This means that if loading loading directly () directly, you can load the DLL from any location, then DLLIMPORT LOADLIBRARY () will use the DLL. Due to this behavior, we can call LoadLibrary () in advance to point your call to other DLLs. If you are in writing a library, you can prevent this in this case by calling getModuleHandle () to ensure that the library is not loaded before the first call P / Invoke. P / Invoke Troubleshoot If your P / Invoke call fails, it is usually because some types of definitions are not correct.

转载请注明原文地址:https://www.9cbs.com/read-29056.html

New Post(0)