欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

客户端内嵌页 基于Chromium Embedded Framework (CEF)

程序员文章站 2022-03-16 11:24:51
...

Chromium Embedded Framework (CEF)是个基于Google Chromium项目的开源Web browser控件,支持Windows, Linux, Mac平台。除了提供C/C++接口外,也有其他语言的移植版。–百度百科


前言:

由于我们项目的客户端比较老了,用户的电脑的默认的ie内核版本也比较低(老用户很多,环境比较复杂)。导致现在内嵌页的前端开发困难,很多现行的web标准不能使用。为了解决这个问题,打算用cef嵌入客户端,这样就不用考虑ie内核的问题了,能使用哪些web特性只要看使用的cef版本是否支持就可以了。


cef 源码可以谷歌的官方下载:cef 官方 下载
cef github 源码: github 源码下载
cef 已经编好的库: 编好的链接库(旧)
编译好的库(新)

因为自己下载源码,自己编译有点复杂,时间也比较久,所以这里先用官方给我们编好的链接库。等以后有时间了再用自己编源码的方式。

我使用的是 cef_binary_1.1180.832_windows.zip 可以用vs2005。 刚好我们客户的代码也是vs2005的。


解压编译

  1. 下载好 cef_binary_1.1180.832_windows.zip 后进行解压:
    客户端内嵌页 基于Chromium Embedded Framework (CEF)

  2. cefclient 是示例程序。 lib 里面的 libcef.lib 是已经编好的库,在客户端代码工程需要引入。libcef.lib是用c写的,所以为了c++方便使用,libcef_dell_wrapper 是c++ 对 libcef 封装, 源码在 libcef_dll目录下

  3. 用vs2005打开 cefclient2005.sln
    客户端内嵌页 基于Chromium Embedded Framework (CEF)

  4. 工程目录中,cefclient->include 和 libcef_dll_wrapper->include 是libcef.lib 库的引用头文件,就是解压根目录下的include 中的文件。我们客户端的工程中也引入这些头文件。

  5. 在编 cefclient 的时候,遇到了 atlthunk.lib 找不到的错误。弄了好久没解决,后面索性在工程属性中Linker->input 中把这个依赖库去掉。不影响编译。libcef_dll_wrapper 编译基本没遇到问题。


嵌入客户端工程


先研究下 cefclient 示例程序中的代码,不是很复杂。基本流程如下:


  // 详细的代码在 cefclient_win.cpp 
  // 这里只是一些片段
  // Program entry point function.
  int APIENTRY wWinMain(HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPTSTR    lpCmdLine,
                      int       nCmdShow) {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // Retrieve the current working directory.
    if (_getcwd(szWorkingDir, MAX_PATH) == NULL)
      szWorkingDir[0] = 0;

    // Parse command line arguments. The passed in values are ignored on Windows.
    // 初始化命令行参数
    AppInitCommandLine(0, NULL);

    CefSettings settings;
    CefRefPtr<CefApp> app;

    // Populate the settings based on command line arguments.
    // 内嵌页的一些配置设置,详细内容要看CefSettings类
    AppGetSettings(settings, app);

    // Initialize CEF.
    // cef 开始进行初始
    // 这里在我们自己程序运行的时候,如果根目录没有把示例程序根目录的locales拷到客户端跟目录。
    // 初始化就会崩溃,而且没有任何提示和报错(坑爹的google o(╥﹏╥)o 找了好久才发现)。
    // 具体下面的注意点会提到。
    CefInitialize(settings, app);
    ........

    // 其他自己逻辑代码.......

    HACCEL hAccelTable;

    // Initialize global strings
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_CEFCLIENT, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization
    if (!InitInstance (hInstance, nCmdShow))
      return FALSE;

    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CEFCLIENT));

    // Register the find event message.
    uFindMsg = RegisterWindowMessage(FINDMSGSTRING);

    int result = 0;

    // settings.multi_threaded_message_loop 是 true 还是 false 的处理 会影响到
    // 关闭 CefShutdown() 函数,如果是 settings.multi_threaded_message_loop = true
    // 单处理不是 CefRunMessageLoop() , 在准备退出并调用 CefShutdown 就会崩溃。
    if (!settings.multi_threaded_message_loop) {
      // Run the CEF message loop. This function will block until the application
      // recieves a WM_QUIT message.
      CefRunMessageLoop();
    } else {
      MSG msg;

      // Run the application message loop.
      while (GetMessage(&msg, NULL, 0, 0)) {
        // Allow processing of find dialog messages.
        if (hFindDlg && IsDialogMessage(hFindDlg, &msg))
          continue;

        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
      }

      result = static_cast<int>(msg.wParam);
    }

    // Shut down CEF.
    / cefShutdown() 崩溃下面会提到。
    CefShutdown();

    return result;
  }

    // 创建内嵌页
    // 详细的代码在 cefclient_win.cpp 
    // Create the single static handler class instance
  g_handler = new ClientHandler();
  g_handler->SetMainHwnd(hWnd);

  // Create the child windows used for navigation
  RECT rect;
  GetClientRect(hWnd, &rect);

  CefWindowInfo info;
  CefBrowserSettings settings;

  // Populate the settings based on command line arguments.
  AppGetBrowserSettings(settings);

  // Initialize window info to the defaults for a child window
  info.SetAsChild(hWnd, rect);

  // Creat the new child browser window
  int nRet = CefBrowser::CreateBrowser(info,
  static_cast<CefRefPtr<CefClient> >(g_handler),
  g_handler->GetStartupURL(), settings);

根据自己项目的不同,不一定要和示例程序完全一样,不过核心的步骤一定要有:

  // Parse command line arguments. The passed in values are ignored on Windows.
  AppInitCommandLine(0, NULL);

  CefSettings settings;
  CefRefPtr<CefApp> app;

  // Populate the settings based on command line arguments.
  AppGetSettings(settings, app);
// 初始化
  // Initialize CEF.
  CefInitialize(settings, app);

  g_handler = new ClientHandler();
  g_handler->SetMainHwnd(hWnd);

// 创建
  int nRet = CefBrowser::CreateBrowser(info,
          static_cast<CefRefPtr<CefClient> >(g_handler),
          g_handler->GetStartupURL(), settings);
// 关闭
  // Shut down CEF.
  CefShutdown();

客户端工程引入

  1. 拷贝解压根目录中的 include 目录到客户端工程目录, 并配置好引入目录(属性->c/c++ ->General ->Additionnal Include Directionories)。
  2. 将解压目录 lib->[Debug|Release] ->libcef.lib , 根目录 [Debug|Release] -> lib -> libcef_dll_wrapper.lib 两个链接库放到客户端工程链接库目录,并配置好引入目录和库配置(属性-> Linker -> Additional Library Directories 和 属性 -> Linker -> input -> Additional Dependencies)。
  3. 参照示例程序写好内嵌页后就可以编译客户端了, 编译过程中可能会出现一些问题, 下面是我遇到的。

    -4503 各种 这些类似的警告和报错。参照示例 cefclient 的 project 属性 -> c/c++ -> Advanced -> Disable Specific Warnings 里面的配置,拷贝到客户端的对应配置。可能还会有一些其他的报错,可以google 下自己找。

    -还有一些 error C2220: warning treated as error - no ‘object’ file generated 类似的错误。有可能是Runtime Library 配置的问题。例如我们客户的: 属性 -> c/c++ -> Coode Generation -> Runtime Library 的配置是 Multi-threaded Debug (/MTd), 但 libcef_dll_wrapper 中的配置是Multi-threaded Debug DLL (/MDd)。 把libcef_dll_wrapper 中的配置改成和客户端的一样然后重新编译 libcef_dll_wrapper .lib 。

    -可能还会有其他错误。可以对比客户端和示例项目cefclient的工程配置有哪些不一样的进行查验。比如cefclient中的预定义,有几个可能客户端中没有进行定义。


注意点:

  • 客户端编好后,需要把cef_binary_1.1180.832_windows.zip解压后根目录下 Debug或 Release 目录下 libGLESv2.dll , libEGL.dll, libcef.dll, icudt.dll 和 locales 目录都拷到客户端程序的根目录, 如果如果不放心就把所有的文件都拷过去。 我一开始没拷locales, 导致了程序在执行初始化CefInitialize(settings, app); 的时候就崩溃。 然后不是源码编译libcef.lib不能进行DUBG调试,找网上找了好久。

  • setting.multi_threaded_message_loop是true 还是 false。要根据自己的程序而定。我们的客户端是要配置成true的。所以不需要调用CefRunMessageLoop()处理消息。如果配置setting.multi_threaded_loop 不对,就有可能在调用CefgShutdown()的时候崩溃。


就先写到这里吧,有其他问题,或自己通过源码编译libcef.lib以后再研究。