Quick answer
Check if RegisterDragDrop succeeded before you ever call RevokeDragDrop—if it didn't, skip the revoke. The error means you're trying to unregister a drop target that was never registered.
Why this happens
What's actually happening here is that OLE's drag-and-drop subsystem keeps an internal map of window handles to IDropTarget pointers. When you call RevokeDragDrop(hwnd), it looks up that window in the map. If it doesn't find an entry, it returns DRAGDROP_E_NOTREGISTERED (0x80040100). The COM runtime isn't guessing—it literally can't find a registration for that handle.
Most people hit this when they write cleanup code that unconditionally calls RevokeDragDrop in a window's destructor or WM_NCDESTROY handler. But if RegisterDragDrop never ran, or ran and failed (say, because OLE wasn't initialized), you're revoking something that never existed. The classic scenario: you have a custom control that conditionally registers as a drop target only when a certain feature is enabled. You disable that feature, destroy the window, and boom—the destructor blindly calls RevokeDragDrop.
The fix
Stop revoking blindly. Track whether registration actually succeeded. Here's the pattern that works—I've used it in production C++ code for years:
- Store the result of
RegisterDragDrop. Keep a simplebool m_dropRegisteredmember, or even just stash the HRESULT. - Call
RevokeDragDroponly if that flag is true. After revoking, set it back to false. - Always call
OleInitializebefore any of this. If OLE isn't initialized,RegisterDragDropreturnsCO_E_NOTINITIALIZED, and you'll never get a valid registration.
In code, it looks like this:
// Member variables
bool m_dropRegistered = false;
// Registration
HRESULT hr = OleInitialize(nullptr);
if (SUCCEEDED(hr)) {
hr = RegisterDragDrop(m_hwnd, &m_dropTarget);
m_dropRegistered = SUCCEEDED(hr);
}
// Cleanup
if (m_dropRegistered) {
RevokeDragDrop(m_hwnd);
m_dropRegistered = false;
}
OleUninitialize();The reason this works is straightforward: you're only asking OLE to remove what you know you added. The flag is your source of truth, not the window's existence.
Alternative fixes when the main one fails
Maybe you're not writing the code—you're getting this error from a third-party app, or your code is already deployed and you can't change it easily. Try these:
- Check for double revocation. If you call
RevokeDragDroptwice on the same window, the second call returns this error. Some frameworks (older MFC, some ATL wrappers) do this internally. The fix is to wrap it in a guard or check the return value. - Verify the window handle is still valid. If the window was destroyed before you call
RevokeDragDrop, OLE may have already cleaned up the registration. Passing a dead handle can produce this error. Make sure you revoke beforeDestroyWindow, not after. - If you're using managed code (C#/VB.NET), you might be P/Invoking
RevokeDragDropincorrectly. The signature needsIntPtrfor the HWND, not an integer. A wrong type can truncate the handle, and OLE looks up a garbage window that was never registered. - For apps that suddenly show this on startup, check if you're running as administrator. UAC can change window handles in weird ways? No, that's nonsense—but it can cause OLE initialization to fail if the token is restricted. If
OleInitializefails, you'll never get a registration, and later revokes will fail with this error.
Prevention tip going forward
Don't treat RevokeDragDrop as a fire-and-forget call. Always pair it with the registration result. And here's a subtle one: if you have multiple windows in a hierarchy, each needs its own registration. A child window that delegates drop handling to its parent doesn't need its own RegisterDragDrop call—so don't revoke it either. Keep the registration and revocation in the same function or class, so they can't drift apart. That's the real fix—not checking error codes, but structuring the code so the mistake is impossible.