Cross stack communications

The subject of how to talk to another device stack has come up again, and since I only briefly touched on it a year ago, I thought it would be good provide some code snippets and a little more background on how to accomplish such a feat.  The gist of what we are trying to do here is load a UMDF based driver for one device, open a handle to a different device stack and submit I/O to that stack.  To do that in UMDF we need to establish a UMDF I/O target and the foundation of that I/O target object is a file handle.

So the basic building blocks are as follows;

     //
    // Add these to your base class
    //
    IWDFIoTarget m_ExternalTarget;
    HANDLE m_ExternalHandle;

You can add these to any of your existing classes, but it probably serves best to house these elements in your Device or Queue classes.  They are tied to the lifetime of the I/O you are sending to the external target, so where you house them is really down to your design and implementation.

Based on your needs and how you design your driver, you will need to determine when you proceed with the following sections.  For simplicity, I’ll establish the connection to the remote stack in the Device Initialization routine, but the same can be done at just about any other point of initialization or even during a later phase of driver operations.

     IWDFFileHandleTargetFactory * pFileHandleTargetFactory = NULL;

   // .......
   // abstracted device init code for brevity
   // ........

    if (SUCCEEDED (hr)) 
    {
        m_FxDevice = fxDevice;

        //
        // We can release the reference as the lifespan is tied to the 
        // framework object.
        //
        fxDevice->Release();
    }

    // Open the device and get the handle.

    m_ExternalHandle = CreateFile (
        DeviceStack,  // path device stack to open
        GENERIC_READ | GENERIC_WRITE, // these flags are driven more by the target stack.
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL,         
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED, // You must open the handle with this flag
        NULL);        



    if (INVALID_HANDLE_VALUE == m_ExternalHandle) 
    {
        DWORD err = GetLastError();

        TraceEvents(
            TRACE_LEVEL_ERROR, 
            TEST_TRACE_DEVICE, 
            "%!FUNC! Cannot open handle to device %!winerr!",
            err);

        hr = HRESULT_FROM_WIN32(err);
    }

    if (SUCCEEDED(hr)) 
    {
        hr = m_FxDevice->QueryInterface(IID_PPV_ARGS(&pFileHandleTargetFactory));

        if (FAILED(hr))
        {
            TraceEvents(
                TRACE_LEVEL_ERROR, 
                TEST_TRACE_DEVICE, 
                L"ERROR: Unable to obtain target factory for creating FileHandle based I/O target %!hresult!",
                hr);
        }
    }

    if (SUCCEEDED(hr)) 
    {
        hr = pFileHandleTargetFactory->CreateFileHandleTarget(m_ExternalHandle, &m_ExternalTarget);
    }

You’ll need to release the reference to the pFileHandleTargetFactory object, either by a SAFE_RELEASE macro, direct call to the Release(); method, or by using a CComPtr /CComQIPtr  class wrapper on the object on declaration.  You’ll also need to clean up the Windows file handle when it is no longer needed.  And IF the IWDFIoTarget object's lifetime scope is narrower than the device object's lifetime, you will need to call DeleteWdfObject method on m_ExternalTarget.

At this point, you have your remote target object to use in sending I/O to that secondary device stack.

What I’ll cover next is going to be some hefty theory on when you should and don’t need to use queue objects and how to send I/O to an external stack based on whether you have a queue or not.  Fun! ;)