Calling Win32 DLLs in C# with P/Invoke
UINT uType // beep type
static extern Boolean MessageBeep (UInt32 beepType);
1.The MessageBeep method was declared as static.
2.This is a requirement for P/Invoke methods because there is no consistent notion of an instance in the Window API.
1. It is not a method call, but an extern method definition.
2. This is your hint to the compiler that you mean for the method to be implemented by a function exported from a DLL, and therefore there is no need for you to supply a method body.
3. P/Invoke methods are nothing more than metadata that the just-in-time (JIT) compiler uses to wire managed code to an unmanaged DLL function at run time.
4. An important piece of information required to perform this wiring to the unmanaged world is the name of the DLL from which the unmanaged method is exported.
6. The DllImport custom attribute that precedes the MessageBeep method declaration. In this case, you can see that the MessageBeep unmanaged API is exported by the User32.dll in Windows.
7. The call into the unmanaged MessageBeep function can be performed by any managed code that finds the extern MessageBeep declaration within scope.
8. The call is made like any other call to a static method. It is this commonality with any other managed method calls that introduces the requirement of data marshaling.
9. One of the rules of C# is that its call syntax can only access CLR data types such as System.UInt32 and System.Boolean.
10.C# is expressly unaware of C-based data types used in the Windows API such as UINT and BOOL, which are just typedefs of the C language types.
11.The extern method must be defined using CLR types, as you saw in the preceding code snippet.
12. This requirement to use CLR types that are different from, but compatible with, the underlying API function types is one of the more difficult aspects of using P/Invoke.
Figure 1 MessageBeep, Interop Done Well
Starting from the top, you will notice that an entire type named Sound is devoted to MessageBeep. If I need to add support for playing waves using the Windows API function PlaySound, I could reuse the Sound type. However, I am not offended by a type that exposes a single public static method. This is application code, after all. Notice also that Sound is sealed and defines an empty private constructor. These are just details to keep a user from mistakenly deriving from or creating an instance of Sound.
The next feature of the code in Figure 1 is that the actual extern method where P/Invoke occurs is a private method of Sound. This method is exposed only indirectly by the public MessageBeep method, which takes a parameter of type BeepTypes. This extra level of indirection is a critical detail that provides the following benefits.
First, should a future managed method of beeping be introduced in the class library, you can re-tool your public MessageBeep method to use the managed API without having to change the rest of the code in your application.
A second benefit of the wrapper method is this: when you P/Invoke, you waive your right to the protection from access violations and other low-level catastrophes, normally provided by the CLR. A buffer method, even if it does nothing but pass parameters through, allows you to protect the rest of your application from access violations and the like. The buffer method localizes any potential bugs introduced by the P/Invoke call.
The third and final benefit of hiding your private extern methods behind a public wrapper is the opportunity to add some minimum CLR style to the method. For example, in Figure 1 I converted a Boolean failure returned by the Windows API function into a more CLR-like exception. I also defined an enumerated type named BeepTypes whose members correspond to the define values used with the Windows API. Since C# doesn't support defines, managed enumerated types are used to avoid scattering magic numbers throughout your application code.
This final benefit of a wrapper method is admittedly minor for a simple Windows API function like MessageBeep. But as you begin to call into more complex unmanaged functions, you will find a manual translation from the Windows API style to a more CLR-friendly approach increasingly beneficial. The more you plan to reuse your interop functionality throughout your applications, the more design thought you should put into the wrapper. Meanwhile I see no shame in non-object-oriented static wrapper methods with CLR-friendly parameters.