Click here to Skip to main content
13,556,387 members
Click here to Skip to main content
Add your own
alternative version


95 bookmarked
Posted 6 Dec 2010
Licenced CPOL

An UMDF Driver for a Virtual Smart Card Reader

, 23 Jan 2011
Rate this:
Please Sign up or sign in to vote.
A simple implementation of a driver for a virtual smart card reader, based on UMDF
This is an old version of the currently published article.


Working with smart cards and PKI stuff is an interesting field. You can see the state-of-art of computer security and how it can be used in the real environment from real users. But, sometimes, debugging and testing applications that work with smart cards is a real pain, especially when you have to deal with negative test cases and, as it often happens, you don't have many test smart cards to play with. What if you accidentally block a PIN? Or your CSP issues a wrong command, leaving the card in an inconsistent state? These and many other issues are quite common in this field, so one of the first things I realized when I started to work with smart cards was that I needed an emulator: something to play with without the risk of doing any damage. In this article, I will not speak about smart card OS emulation (perhaps it will be covered in the future...), but about a driver for a virtual smart card reader.
Searching the internet for virtual drivers leads you to find many interesting resources, but not the “guide for dummies” that I was hoping to find. I’m not an expert in driver developing; this is not by any means an article on “how to write drivers”. I’m just explaining my approach to a new subject, hoping that it will be useful for someone.

An alternative approach to the driver is just writing your own version on winscard.dll, and put it in the folder of the application you wish to debug. That's easier, in some cases, but has some drawbacks:

  • To fully emulate the behavior of Windows Smart Card Resource Manager, you must implement lots of functions.
  • It could be a pain to implement functions like SCardGetStatusChange, specially if you should mix real and simulated readers.
  • You can't replace system's real winscard.dll, since it's subject to system file protection, so it could be tricky to override it in some applications.

Having tried both approaches, I think that developing a driver is better, having learned some base lessons on how to do it (or having this article as a guide :)).


It needed just a few clicks on Google to realize that, to keep things easy, I had to use UMDF (User Mode Driver Framework) as a basis for the development of the driver. From my point of view, and my understanding of the subject, the main reasons are:

  • If you make a mistake, you don't get an ugly blue screen - so, easy developing
  • You can debug your code with your old good user mode debugger - eg. VS2008 - no need for kernel mode debugging - so, easy debugging
  • In my case performance is not critical, and the little overhead introduced by the framework is not a problem </lo />

These are the reasons that led me to use UMDF. Considering the little effort and the satisfaction with the result, I think it was a good choice.
The code is base on the UMDFSkeleton sample of WDK 7.1. I will first comment on the important points of the code, then I will explain the installation procedure.
As an addiction, the virtual card reader will communicate with a desktop application to provide the virtual smart card behavior; so, we'll see some IPC between a UMDF driver and a user process.

A Look at an UMDF Driver Structure

As I said, UMDF simplifies the development of a driver a lot. You just need to write some COM (actually, COM-like) objects implementing some core interfaces and that's it. Let's take a look at how it all works.

A user mode driver is like a COM object. So, like a COM object, we are building a DLL that exposes a DllGetClassObject function, that will be called by the UMDF framework to obtain a ClassFactory to create the actual driver object.
With ATL, it is very easy to create COM objects, so we'll use it to further simplify our job. The only function exposed by the DLL is:

STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID* ppv) 
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv); 

Nothing strange here. The object we are creating (CMyDriver) must implement the IDriverEntry interface, that defines the main entry points of our driver. We can use the OnInitialize method to do all initialization stuff before the actual job begins, but it is not needed in our case.

The OnDeviceAdd method is called by the framework whenever a device is connected to the system that is managed by our driver. In our case, we create a CMyDriver object (through CMyDevice::CreateInstance method), that will hold a reference to a IWDFDevice object, created by the CreateDevice function. This is the initialization of CMyDriver:

    __in IWDFDriver *FxDriver,
    __in IWDFDeviceInitialize * FxDeviceInit
    CComObject<cmydevice>* device = NULL;
    HRESULT hr;

    // Allocate a new instance of the device class.
    hr = CComObject<cmydevice>::CreateInstance(&device);

    if (device==NULL)
        return E_OUTOFMEMORY;

    // Initialize the instance.

    CComPtr<iunknown> spCallback;
    hr = device->QueryInterface(IID_IUnknown, (void**)&spCallback);

    CComPtr<iwdfdevice> spIWDFDevice;
    if (SUCCEEDED(hr))
        hr = FxDriver->CreateDevice(FxDeviceInit, spCallback, &spIWDFDevice);

    if (spIWDFDevice->CreateDeviceInterface(&SmartCardReaderGuid,NULL)!=0)
        OutputDebugString(L"CreateDeviceInterface Failed");


    return hr;

We don't want synchronization issues, so we use SetLockingConstraint(WdfDeviceLevel): only one event handler of the device can run at a given moment. Then we ask the IWDFDriver object to create a IWDFDevice.

These objects are the actual objects maintained by UMDF through which we interact with the underlying driver and device. Since these objects are tightly coupled, in CMyDevice we keep a reference to the IWDFDevice object.

Moreover, we need to call CreateDeviceInterface to create an interface for the device of a type specified by a GUID. In our case, a Smart Card Reader. This interface is automatically enabled by the framework.

We should note, at this point, that our CMyDevice objects implement some interfaces:

  • IPnpCallbackHardware
  • IPnpCallback
  • IRequestCallbackCancel

In the CreateDevice call, we passed spCallback (a pointer to the IUnknown interface of CMyDriver), to inform the UMDF framework that we want to be notified about some events. The framework, according to the interfaces implemented by the callback object, calls its methods when specific events are fired.

IPnpCallbackHardware contains methods to manage hardware insertion and removal.
IPnpCallback contains methods to manage lifetime events on the driver.
IRequestCallbackCancel contains method to manage deletion of I/O Request received by the device. We'll see it in detail later.

The first notification received by our driver is OnPrepareHardware: the hardware is ready and the driver should prepare to use it:

HRESULT CMyDevice::OnPrepareHardware(
        __in IWDFDevice* pWdfDevice
    // Store the IWDFDevice pointer
    m_pWdfDevice = pWdfDevice;

    // Configure the default IO Queue
    HRESULT hr = CMyQueue::CreateInstance(m_pWdfDevice, this);

    DWORD pipeThreadID;
    return hr;

HRESULT CMyQueue::CreateInstance(__in IWDFDevice*  pWdfDevice, CMyDevice* pMyDevice)
    CComObject<cmyqueue>* pMyQueue = NULL;

    if(NULL == pMyDevice)
        return E_INVALIDARG;

    HRESULT hr = CComObject<cmyqueue>::CreateInstance(&pMyQueue);

        // AddRef the object

        // Store the parent device object
        pMyQueue->m_pParentDevice = pMyDevice;

        // Increment the reference for the lifetime of the CMyQueue object.

        CComPtr<iunknown> spIUnknown;
        hr = pMyQueue->QueryInterface(IID_IUnknown, (void**)&spIUnknown);

            // Create the framework queue
            CComPtr<iwdfioqueue> spDefaultQueue;
            hr = pWdfDevice->CreateIoQueue( spIUnknown,
                                            TRUE,                        // DefaultQueue
                                            WdfIoQueueDispatchParallel,  // Parallel 
								// queue handling 
                                            FALSE,                       // PowerManaged
                                            TRUE,              // AllowZeroLengthRequests
            if (FAILED(hr))
		OutputDebugString (L"IoQueue NOT Created");
		OutputDebugString (L"IoQueue Created");


        // Release the pMyQueue pointer when done. 
        // Note: UMDF holds a reference to it above

    return hr;

We do two things: first, we create the default queue for this driver, and attach a callback interface to it (a CMyQueue object that implements IQueueCallbackDeviceIoControl, that will receive I/O events notifications). A driver queue receives all I/O requests from the system when applications try to interact with our device.

Second, since our driver needs to communicate with another processes, we should start a thread that will handle this communication. We'll see later how this works.
At this point, our driver is ready to receive requests and send appropriate responses to the system. This happens by means of calls made to the CMyQueue::OnDeviceIoControl:

STDMETHODIMP_ (void) CMyQueue::OnDeviceIoControl(
    __in IWDFIoQueue*     pQueue,
    __in IWDFIoRequest*   pRequest,
    __in ULONG            ControlCode,
         SIZE_T           InputBufferSizeInBytes,
         SIZE_T           OutputBufferSizeInBytes
void CMyDevice::ProcessIoControl(__in IWDFIoQueue*     pQueue,
                                    __in IWDFIoRequest*   pRequest,
                                    __in ULONG            ControlCode,
                                         SIZE_T           inBufSize,
                                         SIZE_T           outBufSize)
	wchar_t log[300];
	swprintf(log,L"[IOCT]IOCTL %08X - 
		In %i Out %i",ControlCode,inBufSize,outBufSize);

	else if (ControlCode==IOCTL_SMARTCARD_IS_PRESENT) {
	else if (ControlCode==IOCTL_SMARTCARD_GET_STATE) {
	else if (ControlCode==IOCTL_SMARTCARD_IS_ABSENT) {
	else if (ControlCode==IOCTL_SMARTCARD_POWER) {
	else if (ControlCode==IOCTL_SMARTCARD_TRANSMIT) {
        pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);


OnDeviceIoControl is called when the driver receives a request, and it just dispatches the request to the CMyDevice object. ControlCode contains the IO control code of the request, and through the pRequest object, we gain access to the associated input and output memory buffers.

The memory buffers are accessed through IWDFMemory objects. The interface is quite straightforward, and doesn't need many explications.
In the code, there are some helper functions to set and get an integer, a buffer or a string to and from the output and input buffers.

I/O Control Codes

Let's see which are the I/O control codes that a Smart Card Reader driver can receive:


Quite easy. We just need to answer some easy questions: which is the vendor name, the reader name, the device unit (in case we have more than one reader with the same name), the communication protocol we support (our reader only supports T=1) and the ATR string of the inserted card. The ATR is the only element that requires to communicate with the virtual card, as we'll see later.

These I/O requests are immediately completed, so pRequest->CompleteWithInformation is called at the end of the ProcessIoControl method.


This is a bit trickier. We are asked if a smart card is in the reader. This is the code:

void CMyDevice::IoSmartCardIsPresent(IWDFIoRequest* pRequest,
	SIZE_T inBufSize,SIZE_T outBufSize) {
	BYTE ATR[100];
	if (QueryATR(ATR,&ATRSize))
		pRequest->CompleteWithInformation(STATUS_SUCCESS, 0);
	else {
		IRequestCallbackCancel *callback;

We try to communicate with the virtual card to ask its ATR; if the request succeeds, there's a card in the reader, otherwise not. In the first case, we just complete the I/O request to confirm that a card is present.

In the second case, we are actually starting to monitor the reader for card insertion. The I/O request is left pending (if we do not call CompleteWithInformation, the UMDF framework automatically handles the pending request), and it will be completed as soon as a card is inserted. We just store the pointer to the pending request in waitInsertIpr to remember that this request is still open.
Moreover ,we should call pRequest->MarkCancelable to inform the framework that this request is cancellable (in case the device is deactivated, or when the system is shut down). CMyDevice implements IRequestCallbackCancel, so it can be notified of the deletion request.

For IOCTL_SMARTCARD_IS_ABSENT it is obviously the opposite: the I/O request is completed when the smart card is removed.


    We are queried for the device state. This is quite easy, in our case: we just support two states: card absent and card present with protocol negotiated. In a real driver, we should of course handle more precise states. We just ask the virtual card ATR to check if it is present or not.


    The card should be reset or unpowered. In case of a reset, we also return the ATR (if the virtual card is present).


    We could just ignore it. We just return SUCCESS in case the SCARD_ATTR_DEVICE_IN_USE parameter is set.


    A command APDU should be sent to the smart card, and we should return the response. Not difficult, just some stuff to communicate to and from the virtual smart card handling process. We should remember to remove the SCARD_IO_REQUEST structure before the APDU, and insert it before the response.

IPC with the Virtual Smart Card Process

Obviously, a driver can't have a user interface. But if I need to change the behavior of the virtual smart card, perhaps load and save its state, or just simulate its insertion and removal from the virtual reader, I definitely need a user interface to do it! So, the virtual reader driver should happily communicate with the outer world, sending requests and receiving responses to a process that will simulate the virtual smart card behavior. But - because there's always a but - perhaps we should remember that a driver, even a user mode driver, is not exactly a simple application. In this case, the problem is that this application lives in Session 0, isolated from the rest of the Session 1 - world.

I will not explain in detail the concept of Session 0 and 1, and the isolation of Session 0 in Vista and later OS. I will just say that a Session 0 and a Session 1 process can't communicate in some IPC modes: no thread and window messages, no Memory Mapped Files and no global names, so no shared synchronization objects (I also don't want the session 1 application to be elevated)... so, I see two alternatives:

  • Named pipes
  • TCP/IP

And I choose Named Pipes. Fast, easy synchronization, reliable... that's enough. Perhaps in other scenarios I could make a different choice, but in this case it seemed to me the best alternative. So, let's go back to the communication thread and look at how it works:

DWORD CMyDevice::pipeServer() {
	while (true) {
		HANDLE _pipe=CreateNamedPipe(L\\\\.\\pipe\\SCardSimulatorDriver,
		HANDLE _eventpipe=CreateNamedPipe
		OutputDebugString(L"Pipe created");
		OutputDebugString(L"Pipe connected");
		if (waitInsertIpr!=NULL) {
			// if I'm waiting for insertion, 
               		// I verify if a card is already present
			if (CheckATR()) {
				if (waitInsertIpr->UnmarkCancelable()==S_OK)
		while (true) {
			// wait for an event
			DWORD command=0;
			DWORD read=0;
			if (!ReadFile(eventpipe,&command,sizeof(DWORD),&read,NULL)) {
				if (waitRemoveIpr!=NULL) {// card removed
					if (waitRemoveIpr->UnmarkCancelable()==S_OK)
					CompleteWithInformation(STATUS_SUCCESS, 0);
				if (waitInsertIpr!=NULL) {// card inserted
			if (command==0 && waitRemoveIpr!=NULL) {// card removed
				if (waitRemoveIpr->UnmarkCancelable()==S_OK)
						(STATUS_SUCCESS, 0);
			else if (command==1 && waitInsertIpr!=NULL) {// card inserted
				if (waitInsertIpr->UnmarkCancelable()==S_OK)
						(STATUS_SUCCESS, 0);
	OutputDebugString(L"Pipe quit!!!");
	return 0;

First of all, we create two Named Pipes. Why two? Easy: The first (SCardSimulatorDriver) is for requests from the driver to the virtual smart card; the second (SCardSimulatorDriverEvents) is for event notifications from the virtual smart card to the driver (insertion and removal).
With ConnectNamedPipe, we wait for a client to connect. The call is blocking, so until someone opens the pipe, the thread stands waiting.

The utility function CreateMyDACL (straight from MSDN) is used to set the appropriate DACL for the Named Pipe. In fact, if we used NULL as DACL, the object would inherit the default settings of the parent process, and it would become inaccessible to client applications. Our custom DACL grants access to authenticated users, so all user processes are allowed to connect.

When the connection is established, we check if a card is already inserted and we are monitoring (waitInsertIpr and CheckATR()); in this case, in fact, it is not sure that the virtual card would notify the driver, since there's no insertion event. The driver would continue to believe that there's no card inserted.

Then the main communication loop starts. The driver thread stands waiting for events from the virtual smart card, calling ReadFile. The data sent on this pipe is trivial: a single DWORD with value 0 for removal and 1 for insertion. When an event arrives, if we are waiting for that event (waitRemoveIpr or waitInsertIpr), we complete that I/O request accordingly. Since these requests were marked as cancellable, we need to unmak them with UnmarkCancelable and, if possible, complete them (UnmarkCancelable could fail if it is too late and the request was already cancelled) (note that in the OnCancel callback, we don't need to call UnmarkCancelable).

If a ReadFile call fails, it means that we lost the connection with the virtual card. Perhaps the application was closed, and so the pipes. In this case, we simply notify the removal of the card (if requested) and we restart waiting for a new client to connect.

Let's see what happens when the driver needs to send a request to the virtual smart card; the method QueryTransmit does this job:

bool CMyDevice::QueryTransmit(BYTE *APDU,int APDUlen,BYTE *Resp,int *Resplen) {
	if (pipe==NULL)
		return false;
	DWORD command=2;
	DWORD read=0;
	if (!WriteFile(pipe,&command,sizeof(DWORD),&read,NULL)) {
		return false;
	if (!WriteFile(pipe,&dwAPDUlen,sizeof(DWORD),&read,NULL)) {
		return false;
	if (!WriteFile(pipe,APDU,APDUlen,&read,NULL)) {
		return false;
	DWORD dwRespLen;
	if (!ReadFile(pipe,&dwRespLen,sizeof(DWORD),&read,NULL)) {
		return false;
	if (!ReadFile(pipe,Resp,dwRespLen,&read,NULL)) {
		return false;
	return true;

First we check if a pipe is connected; otherwise, we return a fail. Then we write on the pipe the APDU command. The protocol is very simple:

  • a DWORD containing the command code (TRANSMIT=2)
  • a DWORD containing the length of the APDU
  • the APDU buffer

We don't need anything more... we just wait for the response to come. we read a DWORD containing the length of the response, and the response buffer. That's it. If any operation in the pipe fails, probably the communication is broken, so we return a fail and we wait for a new connection to come.

Every time we write something on the pipe, we should always call FlushFileBuffers to be sure that the buffer is sent to the other side of the pipe and is not buffered; otherwise, the listening application could not receive our data.


This implementation of IPC is very simple. A bit too simple. The I/O requests are synchronized, but the events received from the virtual smart cards are not. so, before setting the pipe handle to NULL, it would be wise to acquire a lock on it... I'm lazy, it works 99% of times and I just need it for testing purposes... so I didn't do it. Shame on me.

A Sample Virtual Smart Card Application


In the VirtualSmartCard folder, you can find a sample .NET 3.5 application for a virtual smart card that communicates with the driver. The card answers to the ATR request and can be inserted and removed, but all the APDUs will return 9000.

The application just uses two NamedPipeClientStream objects for data end event pipe, waits for requests on the data pipe and sends notifications to the event pipe:

  void PipeClient()
    pipe = new NamedPipeClientStream(".", "SCardSimulatorDriver", 
		PipeDirection.InOut, PipeOptions.Asynchronous);
    eventPipe = new NamedPipeClientStream(".", "SCardSimulatorDriverEvents", 
		PipeDirection.InOut, PipeOptions.Asynchronous);
    chkCardPresent.Enabled = true;
    BinaryReader brPipe = new BinaryReader(pipe);
    BinaryWriter bwPipe = new BinaryWriter(pipe);
    bwEventPipe = new BinaryWriter(eventPipe);
    while (true)
            int command = brPipe.ReadInt32();
            switch (command)
                case 0:
                case 1:
                    if (chkCardPresent.Checked)
                        bwPipe.Write(ATR, 0, ATR.Length);
                    else {
                    if (command == 0)
                case 2:
                    int apduLen = brPipe.ReadInt32();
                    byte[] APDU = new byte[apduLen];
                    brPipe.Read(APDU, 0, apduLen);
                    byte[] resp = new byte[] { 0x90, 0x00 };
                    bwPipe.Write(resp, 0, resp.Length);
        catch (Exception e)
            if (running)
                MessageBox.Show("Error listening driver pipe");

private void chkCardPresent_CheckedChanged(object sender, EventArgs e)
    Int32 command;
    if (chkCardPresent.Checked)
        logMessage("Card Inserted");
        command = 1;
        logMessage("Card Removed");
        command = 0;

Requests are monitored on a specific thread, while event notifications are sent on the event handler of the user interface.
The application is really simple: it just notifies insertion and removal events in response of state change of a checkbox, and answers to requests coming from the driver sending the ATR or a fixed response 9000. As we already said, after all writes to the BinaryWriter, we flush it to ensure that the driver receives the data. Not that the NamedPipeClientStream are created with the PipeOptions.Asynchronous option because, even if we do not use asynchronous reads and writes, we want to be able to Close the stream from the UI thread while another thread is waiting for a ReadInt32 to return. If we do not specify the PipeOptions.Asynchronous flag, the call to Close would be blocked until the ReadInt32 returns. The same thing is accomplished in the driver using the FILE_FLAG_OVERLAPPED flag.

Compile, Install and Debug

No surprise, to compile the driver you need to install WDK 7.1. Just 620MB to download from Microsoft and we are ready to build the driver. Compiling a driver is not exactly the same as compiling a regular DLL. For example, the subsystem is not WINDOWS but is NATIVE; and the library path should be set accordingly to the architecture for which the driver is being compiled. This is the reason why we don't have a Visual Studio Solution for it, but we'll use the build environment that comes with the WDK, already correctly configured, from a command line. If you installed the WDK correctly, in the start menu, you have the links to various environments: Start > Programs > Windows Drivers Kit > WDK 7600.16385.1 > Build Environments > %OS version% > %TargetCPU% Free/Checked Build Environment.
The Checked Build environment is like a Debug build: optimizations are turned off and conditional code for debugging is included.
The Free Build is like a Release build: the code is optimized and debugging code is disabled.
Note that even in a Free Build, you can use a user mode debugger to step through your code.

To compile the virtual driver, open the correct Build Environment, cd to the directory that contains the code and the subdir for the correct OS version (tested by now on Win 7 and Win XP - the settings of the sources file are slightly different), and just type build.
Et voilà, the DLL is built! ... as long as there's no compilation error. Note that in this build environments, warnings are treated as errors!. They are not reported by the build tool, but they are dumped to a file named, for example, "buildfre_win7_amd64.wrn", if you are building a driver for windows 7, x64, Free Build.
To configure the build tool, modify the sources file, adding new source files or libraries to link against if needed.

Once the driver if built, you can find the DLL in the folder (if Win7, x64, Free) objfre_win7_amd64\amd64, together with the .inf file for installation.
To install the driver, we'll use the DevCon utility that comes with WDK (in %WinDDK%\tools\devcon\%Architecture%). Also, we need to copy the file WUDFUpdate_01009.dll (in %WinDDK%\redist\wdf\%Architecture%) to the path where our DLL is located.
To install the driver, run from an elevated shell:

devcon install BixVReader.inf root\BixVirtualReader

The syntax of devcon install is:

devcon [-r] install <inf> <hwid>

Where <inf> is the path to the .inf file, and <hwid> is the hardware ID of the device that we are installing. It should match the ID in the .inf file at the line:


The -r switch asks to reboot only if a reboot is required.
If the installation succeeds, the user is asked for confirmation, since the driver is not signed, and the virtual reader is installed.


You can download the zip archive with the compiled binaries for Windows 7, 32 and 64 bits and Windows XP. In the zip file, you can find the DLL and the inf file, but you'll need devcon.exe for your architecture (it is not redistributable, but is part of WDK).

If you start the Virtual Smart Card application, you should be able to connect to the virtual card. In Win7, as soon as you insert the card, some minidrivers will try to communicate with the card, sending various commands. Since the card always answers 9000 (that is "OK"), a minidriver could be faked to match it, and you'll see a known card in the device manager! In the log listbox of the application, you'll see the APDUs sent to the card.

Debugging the driver is actually very easy: since it's a user-mode driver, you can use a user-mode debugger: Visual Studio, for example. All you have to do is attach to a running process, and select WUDFHost.exe. Since you could have more than one UMDF driver in your system, you could also have more than one instance of WUDFHost.exe. Which is the correct one? You can use Procexp from Sysinternals to search which processes load your DLL, and check the PID of the correct WUDFHost.exe. Remember that both Procexp and Visual Studio should be Run as Administrator, otherwise they will not be able to attach to the driver process.

When you need to re-compile and re-test your driver, you need to update the driver DLL file.
You can do it simply launching the command:

devcon update BixVReader.inf root\BixVirtualReader

from the same shell used to install it.

Note that the device will be disabled, updated and reenabled, but if you have pending I/O requests, the system will not be able to disable it, and you will need to reboot the system for changes to take place.

In that case, you can do the following:

  • Open Device Manager
  • Disable the virtual device
  • If there are still pending I/O requests, kill the process of WUDFHost.exe (the one that hosts BixVReader.dll)
  • Update the driver with the command line above
  • Enable the device

As soon as you enable the device, the driver is loaded and executed. If you need to debug your driver from the very beginning, you can set the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services\{193a1820-d9ac-4997-8c55-be817523f6aa}/HostProcessDbgBreakOnStartto the number of seconds that you want the host process to wait for a debugger to connect before starting.

It is useful, for debugging purposes, to have a trace or a kind of output from the driver. Even if WDK has a built in tracing system, through the WPP trace, I preferred to use a simple OutputDebugString. Just for speed of development, I preferred to use a well-known method instead of learning a new one, but this is just personal taste... Using Dbgview, also from Sysinternals, Run as Administrator, or attaching a debugger, you can easily see the trace from your driver.

The .INF File

The .INF file used to install the virtual device is almost identical to the one from the UMDFSkeleton example. Just one row was added:


To allow a kernel-mode driver to load above the user-mode driver and to deliver requests from the kernel-mode to the user-mode driver.
I'm not exactly sure of which kernel mode driver runs above the virtual reader driver, but removing this line from the inf file, we simply do not get any I/O request notifications in our Queue object.


As I already said above, this is not by far an article on how a driver should be made. I'm quite sure that an expert driver developer would scream looking at my code. This is just a development tool for who, like me, works often on smart cards and needs to "play" with them. I hope that it will be useful for someone, and of course I wait for some serious driver developer to tell me how it should have been done. :)


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Fabio Ottavi
Italy Italy
No Biography provided

You may also be interested in...


Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.
QuestionPipeReader usage Pin
Member 1336558012-Mar-18 3:53
memberMember 1336558012-Mar-18 3:53 
AnswerRe: PipeReader usage Pin
Fabio Ottavi26-Mar-18 1:30
professionalFabio Ottavi26-Mar-18 1:30 
QuestionDriver configuration failed Pin
Member 1358354718-Dec-17 15:39
memberMember 1358354718-Dec-17 15:39 
AnswerRe: Driver configuration failed Pin
Fabio Ottavi19-Dec-17 22:46
professionalFabio Ottavi19-Dec-17 22:46 
GeneralRe: Driver configuration failed Pin
Member 1358354720-Dec-17 16:50
memberMember 1358354720-Dec-17 16:50 
AnswerRe: Driver configuration failed Pin
Fabio Ottavi21-Dec-17 22:38
professionalFabio Ottavi21-Dec-17 22:38 
GeneralRe: Driver configuration failed Pin
Member 135835471-Jan-18 23:21
memberMember 135835471-Jan-18 23:21 
QuestionNot able to debug the code! Pin
Member 1340595620-Sep-17 20:07
memberMember 1340595620-Sep-17 20:07 
AnswerRe: Not able to debug the code! Pin
Fabio Ottavi25-Sep-17 0:24
professionalFabio Ottavi25-Sep-17 0:24 
QuestionBixVReader: IoRequests for Wait for Remove/Insert Card Pin
Member 899974325-Jun-17 1:51
memberMember 899974325-Jun-17 1:51 
AnswerRe: BixVReader: IoRequests for Wait for Remove/Insert Card Pin
Fabio Ottavi26-Jun-17 23:09
professionalFabio Ottavi26-Jun-17 23:09 
QuestionDo you work on minidriver? Pin
Member 1327324721-Jun-17 23:55
memberMember 1327324721-Jun-17 23:55 
QuestionHow to install on Windows7??? Pin
Member 130916807-May-17 21:09
memberMember 130916807-May-17 21:09 
AnswerRe: How to install on Windows7??? Pin
Fabio Ottavi21-Jun-17 23:19
professionalFabio Ottavi21-Jun-17 23:19 
Question[Solved] Last byte of APDU gets lost Pin
Olivier17578578578012-Apr-17 8:49
memberOlivier17578578578012-Apr-17 8:49 
QuestionWrong current protocol type reported, if card not removed and re-inserted Pin
Lascap29-Sep-16 2:58
memberLascap29-Sep-16 2:58 
AnswerRe: Wrong current protocol type reported, if card not removed and re-inserted Pin
Fabio Ottavi29-Sep-16 23:40
professionalFabio Ottavi29-Sep-16 23:40 
GeneralRe: Wrong current protocol type reported, if card not removed and re-inserted Pin
Lascap30-Sep-16 11:49
memberLascap30-Sep-16 11:49 
AnswerRe: Wrong current protocol type reported, if card not removed and re-inserted Pin
Fabio Ottavi3-Oct-16 4:16
professionalFabio Ottavi3-Oct-16 4:16 
GeneralRe: Wrong current protocol type reported, if card not removed and re-inserted Pin
Lascap3-Oct-16 21:14
memberLascap3-Oct-16 21:14 
QuestionSupport for extended length APDU (T=1) Pin
Member 830883321-Jul-16 13:14
memberMember 830883321-Jul-16 13:14 
AnswerRe: Support for extended length APDU (T=1) Pin
Fabio Ottavi21-Jul-16 22:43
professionalFabio Ottavi21-Jul-16 22:43 
Questionhow to simulate a sam card Pin
Member 1253639224-May-16 11:05
memberMember 1253639224-May-16 11:05 
AnswerRe: how to simulate a sam card Pin
Fabio Ottavi25-May-16 9:56
professionalFabio Ottavi25-May-16 9:56 
GeneralRe: how to simulate a sam card Pin
Member 1253639226-May-16 6:27
memberMember 1253639226-May-16 6:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.180515.1 | Last Updated 23 Jan 2011
Article Copyright 2010 by Fabio Ottavi
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid