See the question and my original answer on StackOverflow

You can get an IHtmlDocument2 reference from an IE's window handle, even out-of-process. This is documented here https://support.microsoft.com/en-us/help/249232/how-to-get-ihtmldocument2-from-a-hwnd, but not really supported by Microsoft. However it looks like it still works today, I've tested it with a Windows 10 box, and IE is now a frozen app, so not going to change any time soon.

Once you have the proper HWND for Internet Explorer, than you can get the DOM with a code like this. Make sure IE and your program run at the same security level The DOM is the same as when you're coding IE inprocess (host, activex, etc.), however for security reasons, some things may not work :

void DoSomeDomOperations(HWND hwnd)
{
    UINT msg = RegisterWindowMessage(L"WM_HTML_GETOBJECT");
    LRESULT result = 0;
    SendMessageTimeout(hwnd, msg, NULL, NULL, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&result);
    if (!result)
        return;

    // get main document object
    IHTMLDocument2 *doc = NULL;
    ObjectFromLresult(result, IID_IHTMLDocument2, NULL, (void**)&doc);
    if (!doc)
        return;

    // get document's url
    BSTR url = NULL;
    doc->get_URL(&url);
    wprintf(L"url:%s\n", url);
    SysFreeString(url);

    // get body element
    IHTMLElement *element = NULL;
    doc->get_body(&element);
    if (element)
    {
        BSTR text = NULL;
        element->get_innerText(&text);
        wprintf(L"text:%s\n", text);
        SysFreeString(text);
        element->Release();
    }

    // etc.
    // etc.

    doc->Release();
}

And here is a full sample console app that scans all current IE processes running:

BOOL CALLBACK GetIEServerWindowProc(HWND hwnd, LPARAM lParam)
{
    // enumerate all child windows to find IE's COM server
    wchar_t className[100];
    GetClassName(hwnd, className, 100);
    if (!wcscmp(className, L"Internet Explorer_Server"))
    {
        *((HWND*)lParam) = hwnd;
        return FALSE;
    }
    return TRUE;
}

HWND GetIEServerWindow(HWND hwnd)
{
    HWND serverHwnd = NULL;
    EnumChildWindows(hwnd, GetIEServerWindowProc, (LPARAM)&serverHwnd);
    return serverHwnd;
}

struct IEServer
{
    DWORD processId;
    HWND serverHwnd;
};

BOOL CALLBACK GetIEProcessServerWindowProc(HWND hwnd, LPARAM lParam)
{
    DWORD processId = ((IEServer*)lParam)->processId;
    DWORD pid;
    GetWindowThreadProcessId(hwnd, &pid);
    if (pid == processId)
    {
        HWND serverHwnd = GetIEServerWindow(hwnd);
        if (serverHwnd)
        {
            ((IEServer*)lParam)->serverHwnd = serverHwnd;
            return FALSE;
        }
    }
    return TRUE;
}

HWND GetIEProcessServerWindow(DWORD processId)
{
    IEServer ie = { processId, NULL };
    EnumWindows(GetIEProcessServerWindowProc, (LPARAM)&ie);
    return ie.serverHwnd;
}

void EnumerateIEProcesses()
{
    HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (h == INVALID_HANDLE_VALUE)
        return;

    PROCESSENTRY32 process;
    process.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(h, &process))
    {
        do
        {
            // we only consider IE processes
            if (!wcscmp(process.szExeFile, L"iexplore.exe"))
            {
                HWND serverHwnd = GetIEProcessServerWindow(process.th32ProcessID);
                if (serverHwnd)
                {
                    DoSomeDomOperations(serverHwnd);
                }
            }
        } while (Process32Next(h, &process));
    }
    CloseHandle(h);
}

int main()
{
    CoInitialize(NULL);

    EnumerateIEProcesses();
    CoUninitialize();
    return 0;
}