CreateEvent
creates or opens a named or unnamed event object.
Using P/Invoke to Access Win32 APIs
by James Park02/19/2002
While Microsoft has incorporated much functionality into the .NET Framework class libraries, significant additional functionality resides outside of the managed world of .NET. COM interoperability is necessary in order to access native system APIs, such as shell integration, DirectX, Microsoft Office, and the Windows Registry, as well as custom legacy COM objects. COM interoperability in .NET can be a tricky issue for developers, who have to deal with issues such as figuring out the appropriate data types to use and marshalling data between managed and unmanaged code.
.NET provides access to COM components through its P/Invoke facility. P/Invoke allows developers to invoke native unmanaged methods from managed code. In this article, we will walk through an example of COM interoperability from within C#.
A Simple Plan
Most applications need some way of storing and retrieving configuration data. On Windows, this is usually done using the Windows registry. .NET conveniently provides managed classes to manipulate keys in the registry, but does not expose any way to subscribe to receive notification of changes made to registry keys. Key change notifications are necessary for any long-lived processes, such as servers, since it is not ideal to have to restart a server every time a configuration change is made.
Fortunately for the .NET developer, there exists a way to register for key change notifications via the P/Invoke facility.
Listing 1: Simple registry key change notification console application
using System.Runtime.InteropServices;
class RegistryTester {
const int INFINITE = - 1 ;
const long HKEY_LOCAL_MACHINE = 0x80000002L ;
const long REG_NOTIFY_CHANGE_LAST_SET = 0x00000004L ;
[DllImport( " kernel32.dll " , EntryPoint = " CreateEvent " )]
static extern IntPtr CreateEvent(IntPtr eventAttributes,
bool manualReset, bool initialState, String name);
[DllImport( " advapi32.dll " , EntryPoint = " RegOpenKey " )]
static extern IntPtr RegOpenKey(IntPtr key, String subKey,
out IntPtr resultSubKey);
[DllImport( " advapi32.dll " , EntryPoint = " RegNotifyChangeKeyValue " )]
static extern long RegNotifyChangeKeyValue(IntPtr key,
bool watchSubTree, int notifyFilter, IntPtr regEvent, bool
async);
[DllImport( " kernel32.dll " , EntryPoint = " WaitForSingleObject " )]
static extern long WaitForSingleObject(IntPtr handle, int timeOut);
[DllImport( " kernel32.dll " , EntryPoint = " CloseHandle " )]
static extern IntPtr CloseHandle(IntPtr handle);
public static void Main(String[] args) {
IntPtr myKey;
IntPtr myEvent = CreateEvent((IntPtr) null , false , false , null );
if (args.Length < 1 ) {
Console.WriteLine( " Usage: RegistryTester KEY " );
return ;
}
String key = args[ 0 ];
unchecked {
RegOpenKey( new IntPtr(( int )HKEY_LOCAL_MACHINE),
key, out myKey);
}
RegNotifyChangeKeyValue(myKey, true ,
( int )REG_NOTIFY_CHANGE_LAST_SET, myEvent, true );
Console.WriteLine( " Waiting on " + key + "
![](https://www.cnblogs.com/Images/dot.gif)
WaitForSingleObject(myEvent, INFINITE);
Console.WriteLine(key + " has been modified. " );
CloseHandle(myEvent);
}
}
To accomplish registry key notification, we need to call a variety of Win32 registry and synchronization functions.
RegOpenKey
opens the specified registry key.RegNotifyChangeKeyValue
notifies the caller about changes to the attributes or contents of a specified registry key.WaitForSingleObject
returns when a specified object is in the signalled state or when the time-out interval elapses.
For each Win32 function call, you need to declare a static extern
method and apply a DllImport
attribute to that method. For this example, we use two parameters of the DllImport
attribute to indicate the specific DLL and DLL entry point we need to call.
Here is a complete list and description of the parameters for the DllImport
attribute (from MSDN):
CallingConvention
indicates theCallingConvention
value used when passing method arguments to the unmanaged implementation.CharSet
controls name mangling and indicates how to marshalString
arguments to the method.EntryPoint
indicates the name or ordinal of the DLL entry point to be called.ExactSpelling
indicates whether the name of the entry point in the unmanaged DLL should be modified to correspond to theCharSet
value specified in theCharSet
field.PreserveSig
indicates whether or not the name of the entry point in the unmanaged DLL should be modified to correspond to theCharSet
value specified in theCharSet
field.SetLastError
indicates the caller may call the Win32 APIGetLastError
function to determine if an error occurred while executing the method.
The trickiest part of COM interop is determining how to map types between the Win32 call and the static extern
method that you declare. A simple rule of thumb to follow is that whenever a Win32 method needs a handle or pointer, you should use an IntPtr
. For pointers to strings, you can simply use the .NET String
type. For example, in RegOpenKey
, the first parameter is an HKEY
type, which is a handle to an open registry key. We can map this to a .NET IntPtr
type. The second parameter is a LPCTSTR
type, which is a pointer to a null-terminated string containing the name of the key to open. This can be mapped to a .NET String
type.
Some Win32 functions might need constant values declared in external header files. You can search for these constants in the header files included with Visual C++ or Cygwin. For this example, we located the constant HKEY_LOCAL_MACHINE
in winreg.h and REG_NOTIFY_CHANGE_LAST_SET
in winnt.h.
Compile and run this program, providing a registry key under the HKEY_LOCAL_MACHINE
node that you wish to monitor for changes. An example key might be SOFTWARE"Microsoft
. The program creates an event object and opens the specified registry key. It calls RegNotifyChangeKeyValue
with both of these objects. This function tells Windows to set the event object to a signalled state when the specified registry key is modified. By calling WaitForSingleObject
on the event, the program blocks until the registry key is modified and the event is signalled.
Mo Keys
A real-world application will probably need notification of changes to multiple keys. However, since WaitForSingleObject
is a blocking method, one would have to spawn a thread for every key. If you need notification of changes to a large number of keys, this would be an inefficient use of resources. One can instead use the Win32 API call WaitForMultipleObjects
.
[DllImport("kernel32.dll", EntryPoint="WaitForMultipleObjects")]
static extern unsafe int WaitForMultipleObjects(int numHandles,
IntPtr* handleArrays, bool waitAll, int timeOut);
To request change notification of multiple keys, you must create an event object for each key and wait on them to be signalled. The second parameter to this call is a pointer to such an array of event handles: HANDLE*
, which can be mapped to an IntPtr*
. Because we are using pointers and therefore dealing with unsafe code, we must mark this method as unsafe
. By using unsafe code, you can access memory directly, like in C/C++, but you have to give up some of the nice things about .NET, such as garbage collection and bounds checking. Pointer operations, such as &
, *
, and ->
, are all available within a method marked unsafe
. WaitForMultipleObjects
returns when either any one or all of the specified event objects in handleArrays
are in the signalled state or when the time-out interval elapses.
Listing 2: Multiple registry key change notification console application
using System.Runtime.InteropServices;
class RegistryTester {
const int INFINITE = - 1 ;
const int WAIT_OBJECT_0 = 0 ;
const long HKEY_LOCAL_MACHINE = 0x80000002L ;
const long REG_NOTIFY_CHANGE_LAST_SET = 0x00000004L ;
[DllImport( " advapi32.dll " , EntryPoint = " RegNotifyChangeKeyValue " )]
static extern long RegNotifyChangeKeyValue(IntPtr key,
bool watchSubTree, int notifyFilter, IntPtr regEvent, bool
async);
[DllImport( " advapi32.dll " , EntryPoint = " RegOpenKey " )]
static extern IntPtr RegOpenKey(IntPtr key, String subKey,
out IntPtr resultSubKey);
[DllImport( " kernel32.dll " , EntryPoint = " CreateEvent " )]
static extern IntPtr CreateEvent(IntPtr eventAttributes,
bool manualReset, bool initialState, String name);
[DllImport( " kernel32.dll " , EntryPoint = " WaitForMultipleObjects " )]
static extern unsafe int WaitForMultipleObjects( int numHandles,
IntPtr * handleArrays, bool waitAll, int timeOut);
[DllImport( " kernel32.dll " , EntryPoint = " CloseHandle " )]
static extern IntPtr CloseHandle(IntPtr handle);
public unsafe static void Main(String[] args) {
int ret = 0 ;
if (args.Length < 2 ) {
Console.WriteLine( " Usage: RegistryTester NUMKEYS KEY1
KEY2
![](https://www.cnblogs.com/Images/dot.gif)
return ;
}
int numKeys = 0 ;
try {
numKeys = int .Parse(args[ 0 ]);
} catch (Exception) {
Console.WriteLine( " Invalid argument for NUMKEYS " );
return ;
}
if (numKeys != args.Length - 1 ) {
Console.WriteLine( " Did not provide correct number
of key arguments. " );
return ;
}
String[] keys = new String[numKeys];
for ( int i = 0 ; i < numKeys; i ++ ) {
keys[i] = args[i + 1 ]; // Start at the 3rd command line arg
}
IntPtr[] eventHandles = new IntPtr[numKeys];
for ( int i = 0 ; i < numKeys; i ++ ) {
eventHandles[i] = CreateEvent((IntPtr) null , false , false ,
null );
IntPtr myKey;
unchecked {
RegOpenKey( new IntPtr(( int )HKEY_LOCAL_MACHINE),
keys[i], out myKey);
}
RegNotifyChangeKeyValue(myKey, true ,
( int )REG_NOTIFY_CHANGE_LAST_SET, eventHandles[i],
true );
}
Console.WriteLine( " Waiting on " + numKeys + " keys. " );
fixed (IntPtr * handlePtr = eventHandles) {
ret = WaitForMultipleObjects(numKeys, handlePtr, false ,
INFINITE);
}
Console.WriteLine(keys[ret - WAIT_OBJECT_0] + " was changed. " );
}
}
Once an array of event handlers is created, you must pass a pointer to them into the WaitForMultipleObjects
call:
fixed (IntPtr* handlePtr = eventHandles) {
ret = WaitForMultipleObjects(numKeys, handlePtr, false, INFINITE);
}
Because we are passing a pointer to an array into an unmanaged call, we must first pin the array in memory via the fixed
keyword. By fixing the array in memory, we are indicating to the .NET CLR garbage collector that this piece of memory should not be touched while within the scope of the fixed
keyword. The static void Main
method is also marked as unsafe, since it using unmanaged code. To compile this application, you must use the /unsafe
compiler flag.
Summary
While COM interopability can seem tricky in the beginning, by using a few simple rules of thumb, you can easily overcome most scenarios involving calls to COM objects.
James Park is a co-founder of O(1) Software, a soon-to-be-launched startup, which is developing .NET-based P2P software and providing consulting services based around .NET technologies.