0

I want to receive a callback in my application when a photo on a connected mobile phone was shot (WPD_EVENT_OBJECT_ADDED). I implemented a WPD client and the IPortableDeviceEventCallback like shown in the Windows Dev Center.

Now my problem is that the IPortableDeviceEventCallback::OnEvent() methode get only invoked if I have opened the DCIM/Camera folder on the phone via Windows Explorer and the explorer starts to creating thumbnails for the images. After I did this once, I receive every event from the phone till I dis- and reconnect it. But if I do not, onEvent() never gets called except I disconnect the phone.

Tested with different Android and iOS phones. Does anyone have an idea whats going on?

DeviceEventsCallback::DeviceEventsCallback(MyPortableDevice* parent) : IPortableDeviceEventCallback(), cRef(1)
{
    parentDevice = parent;
}

DeviceEventsCallback::~DeviceEventsCallback()
{

}

HRESULT __stdcall DeviceEventsCallback::QueryInterface(const IID& riid, LPVOID* ppvObj)
{
    static const QITAB qitab[] = {
        QITABENT(DeviceEventsCallback, IPortableDeviceEventCallback),
        { },
    };

    return QISearch(this, qitab, riid, ppvObj);

    //    HRESULT hr = S_OK;
    //    if (ppvObj == NULL) {
    //        hr = E_INVALIDARG;
    //        return hr;
    //    }

    //    if ((riid == IID_IUnknown) ||
    //        (riid == IID_IPortableDeviceEventCallback)) {
    //        AddRef();
    //        *ppvObj = this;
    //    }
    //    else {
    //        *ppvObj = NULL;
    //        hr = E_NOINTERFACE;
    //    }
    //    return hr;
}

ULONG __stdcall DeviceEventsCallback::AddRef()
{
    InterlockedIncrement((long*) &cRef);
    return cRef;
}

ULONG __stdcall DeviceEventsCallback::Release()
{
    ULONG refCount = cRef - 1;
    long ref = InterlockedDecrement(&cRef);

    if (ref == 0) {
        delete this;
        return 0;
    }

    return refCount;
}

HRESULT __stdcall DeviceEventsCallback::OnEvent(IPortableDeviceValues* pEventParameters)
{       
    HRESULT hr = S_OK;

    if (pEventParameters == NULL) {
        hr = E_POINTER;
        return hr;
    }

    // The pEventParameters collection contains information about the event that was
    // fired. We'll at least need the EVENT_ID to figure out which event was fired
    // and based on that retrieve additional values from the collection

    // Display the event that was fired
    GUID EventId;

    if (EventId == WPD_EVENT_DEVICE_CAPABILITIES_UPDATED) {
        return S_OK;
    }

    if (hr == S_OK) {
        hr = pEventParameters->GetGuidValue(WPD_EVENT_PARAMETER_EVENT_ID, &EventId);
    }

    if (EventId == WPD_EVENT_DEVICE_REMOVED) {
        return S_OK;
    }

    LPWSTR pwszEventId = NULL;

    if (hr == S_OK) {
        hr = StringFromCLSID(EventId, &pwszEventId);
    }

    if (pwszEventId != NULL) {
        CoTaskMemFree(pwszEventId);
    }

    // Display the ID of the object that was affected
    // We can also obtain WPD_OBJECT_NAME, WPD_OBJECT_PERSISTENT_UNIQUE_ID,
    // WPD_OBJECT_PARENT_ID, etc.
    LPWSTR pwszObjectId = NULL;

    if (hr == S_OK) {
        hr = pEventParameters->GetStringValue(WPD_OBJECT_ID, &pwszObjectId);
    }

    if (parentDevice != nullptr && pwszObjectId != nullptr && EventId == WPD_EVENT_OBJECT_ADDED) {
        qDebug() << "invoked method";
        QMetaObject::invokeMethod(parentDevice, "onNewFileOnDevice", Qt::DirectConnection, Q_ARG(QString, QString::fromStdWString(pwszObjectId)));
    }

    if (pwszObjectId != NULL) {
        CoTaskMemFree(pwszObjectId);
    }

    // Note that we intentionally do not call Release on pEventParameters since we
    // do not own it

    return hr;
}  

And in my MTP implementation I register my DeviceEventsCallback() eventNotifier

void MyPortableDevice::registerEventNotification(ComPtr<IPortableDevice> pDevice)
{
    HRESULT hr = S_OK;
    PWSTR tempEventCookie = nullptr;

    if (pwszEventCookie != nullptr || pDevice == nullptr) {
        return;
    }

    eventNotifier = new(std::nothrow) DeviceEventsCallback(this);

    if (eventNotifier == nullptr) {
        hr = E_OUTOFMEMORY;
    }

    if (hr == S_OK) {
        hr = pDevice->Advise(0, eventNotifier, nullptr, &tempEventCookie);
    }

    if (hr == S_OK) {
        pwszEventCookie = tempEventCookie;
        tempEventCookie = nullptr; // relinquish memory to the caller
    }
    else {
        // Free the event registration cookie because some error occurred
        CoTaskMemFree(tempEventCookie);
        tempEventCookie = nullptr;
    }
}

void MyPortableDevice::unregisterEventsNotification(ComPtr<IPortableDevice> pDevice)
{
    if (pDevice == nullptr || pwszEventCookie == nullptr) {
        return;
    }

    HRESULT hr = pDevice->Unadvise(pwszEventCookie);

    CoTaskMemFree(pwszEventCookie);
    pwszEventCookie = nullptr;
}

My open() function looks like this:

bool MyPortableDevice::open(IPortableDevice** ppDevice)
{
    retrieveClientInformation(&clientInformation);
    IPortableDevice* pDevice = nullptr;

    HRESULT hr = CoCreateInstance(CLSID_PortableDeviceFTM, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevice));

    if (SUCCEEDED(hr)) {
        wchar_t* wID = new wchar_t[ID.length() + 1];
        ID.toWCharArray(wID);
        wID[ID.length()] = '\0';

        hr = pDevice->Open(wID, clientInformation.Get());

        if (hr == E_ACCESSDENIED) {
            qDebug() << "Failed to Open the device for Read Write access, will open it for Read-only access instead" << hr;
            clientInformation->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, GENERIC_READ);
            hr = pDevice->Open(wID, clientInformation.Get());

            readOnly = true;
        }
        if (SUCCEEDED(hr)) {
            // The device successfully opened, obtain an instance of the Device into
            // ppDevice so the caller can be returned an opened IPortableDevice.
            hr = pDevice->QueryInterface(IID_IPortableDevice, (VOID**)ppDevice);
            if (FAILED(hr)) {
                qDebug() << "Failed to QueryInterface the opened IPortableDevice";
                return  false;
            }
        }

        if (pDevice != nullptr) {
            pDevice->Release();
            pDevice = nullptr;
        }

        delete [] wID;
        wID = nullptr;

        if (clientInformation != nullptr) {
            clientInformation.Reset();
            clientInformation = nullptr;
        } 

        return true;
    }
    else {
        qDebug() << "! Failed to CoCreateInstance CLSID_PortableDeviceFTM, hr = 0x%lx\n" << hr;
        return false;
    }
} 
RobRobRob
  • 67
  • 2
  • 10
  • Without a [mcve] this question will be impossible to answer. – zett42 Mar 07 '18 at 16:13
  • I'll try but even an minimal example would be about 500 lines of code. And the link to Windows Dev Center shows a minimal example of the IPortableDeviceEventCallback class. – RobRobRob Mar 07 '18 at 16:22
  • Does this example exhibit the same issue like your own code? – zett42 Mar 07 '18 at 16:40
  • Yes, and I have to correct myself it is not neccessary that the explorer have to create the thumbnails in the DCIM folder. I just have to browse into this folder and the notification works fine. – RobRobRob Mar 08 '18 at 07:59
  • Could you show the part of code, where you are getting pDevice object? (Did you call Open(...) for it, what parameter you have specified, etc) The code of your OnEvent() looks strange in some places (you are comparing EventId to WPD_EVENT_DEVICE_CAPABILITIES_UPDATED before EventId got some value), but I suppose you checked in the debugger that OnEvent() really not called. – Arthur Bulakaiev Mar 09 '18 at 10:27

0 Answers0