将Libcef打造为win32控件之二:初次封装,拿到浏览器HWND

修改这些源码:cefclient_win.cc、cefclient_handler.cc、main_message_loop_multithreaded_win.cc。

第一步,封装出 bwCreateBrowser 方法,用于创建浏览器控件。


HINSTANCE g_hInstance;

HWND hwnd;

client::MainContextImpl * pMainContextImpl;

client::MainMessageLoopMultithreadedWin* looper;

extern "C" __declspec(dllexport) HWND bwGetHWNDForBrowser(void* pBrowser)
{
    CefBrowser* browser = (CefBrowser*)pBrowser;
    return browser?browser->GetHost()->GetWindowHandle():0;
}

extern "C" __declspec(dllexport) int bwCreateBrowser(HWND hParent, CHAR* URL, BrowserCallback bwCallback) {
    if(IsWindow(hParent))
    {
        if(!looper)
        {
            // Enable High-DPI support on Windows 7 or newer.
            //CefEnableHighDPISupport();
            CefMainArgs main_args(g_hInstance);

            void* sandbox_info = nullptr;

#if defined(CEF_USE_SANDBOX)
            // Manage the life span of the sandbox information object. This is necessary
            // for sandbox support on Windows. See cef_sandbox_win.h for complete details.
            CefScopedSandboxInfo scoped_sandbox;
            sandbox_info = scoped_sandbox.sandbox_info();
#endif

            // Parse command-line arguments.
            CefRefPtr command_line = CefCommandLine::CreateCommandLine();

            CefRefPtr app = new ClientAppBrowser();

            TCHAR cmd_extra[MAX_PATH];
            decorateCommandLine(cmd_extra, 0); // 修改命令行参数

            // Execute the secondary process, if any.
            int exit_code = CefExecuteProcess(main_args, app, sandbox_info);
            if (exit_code >= 0)
                return exit_code;
            
            pMainContextImpl = new MainContextImpl(command_line, true); // Create the main context object.

            CefSettings settings;
            settings.Set({}, 0);
            CefString(&settings.application_client_id_for_file_scanning).FromString("9A8DE24D-B822-4C6C-8259-5A848FEA1E68");
            //context->PopulateSettings(&settings);
            settings.command_line_args_disabled=0;
            settings.no_sandbox = false;
            settings.log_severity=LOGSEVERITY_DISABLE;
            settings.multi_threaded_message_loop=1;

            // Initialize CEF.
            pMainContextImpl->Initialize(main_args, settings, app, sandbox_info);
            // Initialize CEF.

            looper = new MainMessageLoopMultithreadedWin();
            looper->Run();
        }

        CefRefPtr  g_handler = new ClientHandlerStd(0, URL);
        CefBrowserSettings browser_settings;
        CefWindowInfo window_info;
        RECT rc;
        GetWindowRect(hParent, &rc);

        //设置父窗口信息
        window_info.SetAsChild(hParent, rc);
        CefRefPtr extra_info;
        CefRefPtr request_context;
        g_handler->bwCallback = bwCallback;
                
        //最终创建控件
        CefBrowserHost::CreateBrowser(window_info, g_handler, URL, browser_settings, extra_info, request_context);
    }
}

使用 CefBrowserHost::CreateBrowser 也就是 cef_browser_host_create_browser 创建浏览器时,无法直接得到浏览器指针,需要在OnAfterCreated回调中处理:

typedef const void * (__cdecl * BrowserCallback)(CefBrowser*);


void ClientHandler::OnAfterCreated(CefRefPtr browser) {
  ……
  if(bwCallback)
  {
      bwCallback(browser);
  }
  NotifyBrowserCreated(browser);
}

关于 ClientHandler,每一个浏览器控件都会新建一个。兼容原生win32的while(GetMessage)消息循环,是通过MainMessageLoopMultithreadedWin实现的,多个浏览器控件可以共用一个MainMessageLoopMultithreadedWin,所以在封装bwCreateBrowser之时我用looper变量判断cef是否已经加载并初始化。

MainMessageLoopMultithreadedWin实现的原理是看不懂,不过它会调用PostQuitMessage终止消息循环,需要修改掉这一点。PostQuitMessage怎么可能由一个控件调用?一般都是在主窗口WNDPROCcase WM_DESTORY中调用的。

主窗口WNDPROC不能瞎写,比如始终返回true,或者在case WM_PAINT返回true,会导致CPU占用飙升。

封装就是这个样子,如下在.exe中调用,很简单吧!

wWinMain入口中,先新建主窗口,得主窗口hwnd

regWndClass(L"TESTWND", CS_HREDRAW | CS_VREDRAW);
hwnd = ::CreateWindowEx(WS_EX_APPWINDOW , L"TESTWND" , NULL
    , WS_OVERLAPPEDWINDOW | WS_VISIBLE , 0 , 0 , 840 , 680 , NULL , NULL , ::GetModuleHandle(NULL), NULL);
ShowWindow(hwnd, true);

再加载 cefclient.dll,新建浏览器控件。

typedef const int (__cdecl * BWCREATEBROWSER)(HWND, const CHAR*, LONG_PTR);

typedef const HWND (__cdecl * BWGETHWNDFORBROWSER)(void*);

BWCREATEBROWSER bwCreateBrowser;

BWGETHWNDFORBROWSER bwGetHWNDForBrowser;

void onBrowserPrepared(void* browserPtr)
{
    hBrowser = bwGetHWNDForBrowser(browserPtr);
    // 拿到了浏览器控件的HWND!
}

     // 加载cefclient.dll
     // 首先拼接出库目录,现只支持与exe同路径,
     //     未来考虑通过配置文件/环境变量来查找库文件
    TCHAR buffer[MAX_PATH]; 
    GetModuleFileName(NULL, buffer, MAX_PATH);
    PathRemoveFileSpec(buffer);
    PathAppend(buffer, L"cefclient.dll");
    TCHAR LibBwgtPath[MAX_PATH]; 
    PathCanonicalize(LibBwgtPath, buffer);
    if(PathFileExists(LibBwgtPath))
    {
        const DWORD dwFlags = GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "AddDllDirectory") != NULL ? LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0;
        auto hLibBwgt = ::LoadLibraryEx(LibBwgtPath, NULL, dwFlags);
        if(hLibBwgt) // 库文件加载成功
        {
            bwCreateBrowser = (BWCREATEBROWSER)GetProcAddress(hLibBwgt, "bwCreateBrowser");
            bwGetHWNDForBrowser = (BWGETHWNDFORBROWSER)GetProcAddress(hLibBwgt, "bwGetHWNDForBrowser");

            // 新建控件
            bwCreateBrowser(hwnd, "www.bing.com", (LONG_PTR)onBrowserPrepared);

            // 甚至可以在同一窗口再开一个浏览器控件 :
            bwCreateBrowser(hwnd, "www.baidu.com", (LONG_PTR)onBrowserPrepared2);
        }
    }

最后是常规的消息循环。

MSG msg;
{
    while (GetMessage(&msg, NULL, 0, 0)) {
        //if ((looper->dialog_hwnd_ && IsDialogMessage(looper->dialog_hwnd_ , &msg)))
        //  continue;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}




https://github.com/KnIfER/BrowserWidget/blob/master/BrowserUI/BrowserUI.cpp

https://github.com/KnIfER/BrowserWidget/blob/master/tests/cefclient/cefclient_win.cc

https://github.com/KnIfER/BrowserWidget/blob/master/tests/cefclient/browser/main_message_loop_multithreaded_win.cc



未完待续……

上一篇 初识Libcef
下一篇 资源拦截替换、JS调用C++ Native、首次运用

你可能感兴趣的:(将Libcef打造为win32控件之二:初次封装,拿到浏览器HWND)