Tom Ward

API Hooking on Windows

A while back I was porting an application to Qt 4.8 on Windows and, due to the fact the program couldn’t deal with varying DPI settings, I had to make sure Qt didn’t call the “SetProcessDPIAware()” function. The simple way to do this would be to simply recompile Qt to remove this offending line, but as we wanted to share the exact same library with other products, I didn’t have that luxury.

So I did something really really naughty: I used a trick to modify the API call to not actually do anything. Welcome to API Hooking! This is actually used quite a lot by profilers or memory tracking software to hook into low-level API’s and track various tasks, but we’re going to use it in this case to simply early out of the given function.

Sounds quite complex, but it basically boils down to something like this:

typedef BOOL (WINAPI *SetProcessDPIAwarePtr)(VOID);

INT APIENTRY DllMain(HMODULE hDLL, DWORD reason, LPVOID reserved)
{
    if (reason == DLL_PROCESS_ATTACH )
    {
        // Make sure we're not already DPI aware
        assert( !IsProcessDPIAware() );

        // First get the DPIAware function pointer
        SetProcessDPIAwarePtr lpDPIAwarePointer = (SetProcessDPIAwarePtr)
        GetProcAddress(GetModuleHandle("user32.dll"),
        "SetProcessDPIAware");

        // Next make the page writeable so that we can change the function assembley
        DWORD oldProtect;
        VirtualProtect((LPVOID)lpDPIAwarePointer, 1, PAGE_EXECUTE_READWRITE, &oldProtect);

        // write "ret" as first assembly instruction to avoid actually setting HighDPI
        BYTE newAssembly[] = {0xC3};
        memcpy(lpDPIAwarePointer, newAssembly, sizeof(newAssembly));

        // change protection back to previous setting.
        VirtualProtect((LPVOID)lpDPIAwarePointer, 1, oldProtect, NULL);
    }
    return TRUE;
}

That’s not too scary, right?! In my use case this seems to have worked perfectly, and saved me from the hassle of recompiling Qt!