Using the plain win32 API, I would create a named event (i.e. supply a name for CreateEvent()). This can be use across process boundaries, including across the kernel/userspace boundary. You would then simply use WaitForSingleObject() and related functions to check the event state.
If you have a stream driver, you can also call ReadFile() from the application and simply stall inside the according handler function of the driver. This makes it pretty easy to add data to the event, too. Further, it provides separation between different processes that access the driver, or even different instances within the same process. Compare this with the event above, which is effectively system-wide visible and can also be set by different processes, although you can restrict this to some extent.
Another alternative is to use window messages (PostMessage(), SendMessage() etc) which also work across process boundaries. I'm not sure if this works from the kernel though. These would then end up in the "normal" message queue of e.g. the applications main window or any other window you previously told the driver. Compared to the other two approaches, you need a window as target, so it only works one way, and you don't know where a message came from.