Let's define this simple worker function which registers for device interface arrival notifications for a given handle (either an HWND or service HANDLE):

HDEVNOTIFY
RegisterInterfaceNotificationWorker(
    HANDLE Recipient,
    LPCGUID Guid,
    DWORD Flags
    )
{
    DEV_BROADCAST_DEVICEINTERFACE dbh;

    ZeroMemory(&dbh, sizeof(dbh));

    dbh.dbcc_size = sizeof(dbh);
    dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    CopyMemory(&dbh.dbcc_classguid, Guid, sizeof(GUID));

    return RegisterDeviceNotification(Recipient, &dbh, Flags);
}

I have seen lots of different developers pass a GUID to this function, but it was the wrong category of GUID.  Why would someone make that mistake?  Well, a pnp device is typically associated with two different GUIDs, a device interface GUID and a device class GUID.  Why are there 2 different GUIDS?  Well, let's look at the role of each one.

A device class GUID defines a broad category of devices.  If you open up device manager, the default view is "by type."  Each type is a device class, where each class is uniquely ID's by the device class GUID.  A device class GUID defines the icon for the class, default security settings, install properties (like a user cannot manually install an instance of this class, it must be enumerated by PNP), and other settings.  The device class GUID does not define an I/O interface (see Glossary), rather think of it as a grouping of devices.  I think a good clarifying example is the Ports class.  Both COM and LPT devices are a part of the Ports class, yet each has its own distinct I/O interface which are not compatible with each other.  A device can only belong to one device class.  The device class GUID is the GUID you see at the top of an INF file.

A device interface GUID defines a particular I/O interface contract.  It is expected that every instanceof the interface GUID will support the same basic set of I/Os.  The device interface GUID is what the driver will register and enable/disable based on PnP state.  A device can register many device interfaces for itself, it is not limited to one interface GUID.  If need be, the device can even register multiple instances of the same GUID (assuming each have their own ReferenceString), although I have never seen a real world need for this.  A simple I/O interface contract is the keyboard device interface to the raw input thread.  Here is the keyboard device contract that each instance of the keyboard device interface GUID must support.

  1. IRP_MJ_READ.  Each read buffer must be an integral number of KEYBOARD_INPUT_DATAs
  2. IRP_MJ_INTERNAL_DEVICE_CONTROL with the following IOCTLs
    • IOCTL_KEYBOARD_QUERY_ATTRIBUTES
    • IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION
    • IOCTL_KEYBOARD_QUERY_INDICATORS
    • IOCTL_KEYBOARD_SET_INDICATORS
    • IOCTL_KEYBOARD_QUERY_TYPEMATIC
    • IOCTL_KEYBOARD_SET_TYPEMATIC
  3. IRP_MJ_SYSTEM_CONTROL with the following GUIDs
    • MSKeyboard_PortInformation_GUID
    • MSKeyboard_ExtendedID_GUID

Unfortunately I have seen instances where the class and interface GUIDs were the same value.  I would not recommend doing this for your own custom classes and devices if you have a choice. 

So how can I tell the difference betwee the 2 GUIDs?  Well, there is a naming convention in place.  This convention was started in the XP DDK after we realized in the Windows 2000 timeframe that the naming was inconsistent and confusing developers.  A device class GUID will have DEVCLASS in its name, a device interface GUID will have DEVINTERFACE in its name.  Also, a good clue that the GUID is a device class GUID is if it appears as the class GUID in an INF.  So for our keyboard example, we have the following 2 GUIDs

  • GUID_DEVINTERFACE_KEYBOARD found in ntddkbd.h
  • GUID_DEVCLASS_KEYBOARD found in devguid.h