We’ve seen know how to get some useful information (like vendor ID and serial number) from attached Windows devices, using a C DLL. Interfacing the DLL with Visual Basic, was then just a matter of declaring the DLL functions in a standard module. With some more basic wrapping, we’ve obtained a simple API function and a data structure, to query for device information in Access.
Reacting to real time device arrival/removal notifications in VB/A
Getting notified in real time (well, as near as that as we can get, at least), when a USB device is plugged or unplugged from the system is the next step. This will allow us later to do things like build an Access application that can use a USB device as a multifactor authentication device (MFA).
Receiving notifications about device arrival or removal in windows is achieved by registering for that, via the RegisterDeviceNotification() Win32 API function. By then listening for the WM_DEVICECHANGE windows message, we can react accordingly.
Subclassing
VB/A cannot listen for windows messages on its own. We have to use a “subclassing” technique, where we redirect all the messages Windows sends to a VB Form (which itself is a window), to a custom routine. I use a subclassing technique derived from “Sensei” Bruce McKinney’s “Hardcore VB 5” book (free link), where we end up responding to the Windows messages we choose by implementing an ISubclass interface, in a Visual Basic Form. Here’s the snippet in frmMain that handles what we need:
1 2 3 4 5 6 7 8 9 10 11 |
'Will receive only WM_DEVICECHANGE messages 'MSDN: https://docs.microsoft.com/en-us/windows/win32/devio/detecting-media-insertion-or-removal Private Function ISubclass_WindowProc(ByVal plhWnd As Long, ByVal plMsg As Long, ByVal pwParam As Long, ByVal plParam As Long) As Long '... Select Case pwParam Case DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE '... End Select '... End Function |
Building an out-of-process ActiveX exe server
An ActiveX out-of-process server written in (classic) Visual Basic, is an independent 32 bits executable that serves objects running on their own threads, in an external process. In VBA hosts like Access or Excel, the Visual Basic code runs in a single thread of execution, so it cannot handle simultaneously the tasks of continuously responding to Windows messages and running the VBA application. I already explained why it would be a bad idea to use subclassing techniques in a VBA host application, talking about how to detect Windows session lock/unlock in VBA.
Object classes that are exposed by an ActiveX OOP server have the additional advantage of being accessible in 32 and 64 bits VBA hosts, thanks to (automatic) COM inter process communication marshalling.
I’ve used this technique in the three ActiveX servers I’m presenting on this series on my blog:
-
AxSessionLockDetector
- Provides classes and events to detect and react when a user locks or unlocks his/her Windows session.
-
AxDeviceDetector
- Provides classes and events to detect then there’s a device (or media) arrival or removal from the system (The one we’re discussing here).
-
AxHttpSrv
- Provides classes and events to implement an HTTP web server in Microsoft Access.
- Although this is the last one of this series, it is the oldest (I wrote it back in 2011), and it served as a base template for the two others.
However, when writing the ActiveX server we’re discussing here, I ran into a quite unexpected problem.
While trying to raise events or invoking OLE callbacks to notify the host for events, automation errors where popping up, instead of raising events (or executing OLE callbacks), basically rendering the solution useless. The first actionable information I found about that by googling around is in this KB article, located in a quite interesting github repository (it’s a static repository of Microsoft knowledge base articles).
A nice workaround consists in queuing the Windows messages we receive in the function implementing the ISubclass interface, and notifying them a bit later, using a timer to pop them back from the queue.
So, I quickly added a fixed size queue, stored in a circular memory array, specializing the CQueue class I presented in an earlier article where I was illustrating how a circular queue implementation works.
Strangely, I did not have to use this queue/timer technique for the two other ActiveX EXE server, but, alas, I can’t explain why is the difference between all of them.
Events or OLE callbacks ?
For an ActiveX server (or DLL for that matter), we can use two techniques to notify an object instance owner:
-
Events
- Technically, they’re disguised OLE callbacks. You first declare the event in a class definition module, then raise the event with the RaiseEvent VB keyword;
-
OLE Callbacks
- The client process has to give a reference to an (untyped) Object, to the ActiveX server class, that will keep a reference on it.
- The ActiveX server class then tries a late bound call on a specific method name on the object reference, that’s the late bound call or OLE callback.
- One key difference with raising events is that you control the callback sequence in the ActiveX server, when you chain multiple OLE callbacks; when you raise events, you can’t control in what sequence the client receives them.
The DeviceDetector class in the AxDeviceDetector ActiveX server allows for both methods to be used. For each of the two methods, you also have the option of passing parameters to the event procedures or OLE callbacks or not. If not, then the client can call methods on the class to get them. This is commented in the class source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
'Notification method: ' -------------------------------+--------------------------------------------------------- ' 1) Callback object is nothing | Raise events OnDeviceArrival/OnDeviceRemoval ' UseNoParamEvents is False | (this is the default behavior) ' -------------------------------+--------------------------------------------------------- ' 2) Callback object is nothing | Raise events OnDeviceArrivalSignal/OnDeviceRemovalSignal ' UseNoParamEvents is True | ' -------------------------------+--------------------------------------------------------- ' 3) Callback object is set | Callback OnDeviceArrival/OnDeviceRemoval ' UseNoParamEvents is False | ' -------------------------------+--------------------------------------------------------- ' 4) Callback object is set | Callback OnDeviceArrivalSignal/OnDeviceRemovalSignal ' UseNoParamEvents is True | ' -------------------------------+--------------------------------------------------------- ' For 2) and 4), class user has to call GetLastArrivalEventParams/GetLastRemovalEventParams ' to get the event parameters. |
Then
You should see a schema illustrating a bit more nicely how this ActiveX Server works, as the image of this post.
Head to the github repository to download all the related material.
In the next post, we’ll see a real life possible use case for this ActiveX server.
I’ll explain the Access Demo application that you can see here in the mean time:
Downloads
- All the material is available in my AxDeviceDetector github project
Browse the source code for more specific comments and explanations.
Recent Comments