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

CEF4Delphi初识

程序员文章站 2022-03-10 12:47:18
代码模块与职责 所有的代码都在src目录下,这会导致一上手的时候无法快速划分模块,不便于理解,如果分类然后放文件夹就会好一些。 最关键的部分在于uCEFApplication,是和dll链接的部分 ,可以在这个文件内找到所有关于接口类型的声明,抽象了基本类型使用的接口,结构清晰。几乎是个功能都能找到 ......

代码模块与职责

所有的代码都在src目录下,这会导致一上手的时候无法快速划分模块,不便于理解,如果分类然后放文件夹就会好一些。

最关键的部分在于ucefapplication,是和dll链接的部分

ucefinterfaces.pas,可以在这个文件内找到所有关于接口类型的声明,抽象了基本类型使用的接口,结构清晰。几乎是个功能都能找到对应的接口。和cef提供的接口有高度一致性。除了cef相关的接口外,还有自定义的一些工具接口。
ucefclient.pas,继承自icefclient,用于实现获取handler的接口
uceftypes.pas,这个文件声明了大量的类型,但是不知道是不是所有的类型声明都在这里面。
ucefchromium.pas,存放了tchromium的类型声明,是实现功能的关键类。
ucefloadhandler,存放了loadhandler相关类型,回调处理器的一个具体实现,还有很多其他的handler。
ucefapplication.pas是核心,涉及到关键部分,有关键的类tcefapplication,加载了dll并获取函数,是使用cef4delphi的入口。

关键类型和关键接口

icefbrowser:主要是浏览器级别的接口,获取frame,后退前进,中断加载等接口(浏览器进程的功能接口);
icefframe:加载网页的对象,loadurl功能就是它提供的。可以看成加载网页的那个frame。针对于网页级别的接口(加载网页,复制粘贴等在网页级别的操作接口);
icefclient:接口,提供获取各种各样handler的接口。其中handler是回调处理器;
ichromiumevents:关键接口,用于执行浏览器的关键操作,和handler回调处理器一起工作,实现连接libcef.dll的事件传递,该接口是自定义接口,非cef接口;

附件区域

事件传递流程

libcef.dll中注册的回调事件是如何通知到tchromium对象的呢?

在文件ucefchromium.pas中,tchromium对象的常用工作流程createbrowser,会进行handler的注册

function tchromium.createbrowser(      aparenthandle  : hwnd;
                                       aparentrect    : trect;
                                 const awindowname    : ustring;
                                    const acontext       : icefrequestcontext;
                                 const aextrainfo     : icefdictionaryvalue) : boolean;
begin
  result := false;

  try
    // globalcefapp.globalcontextinitialized has to be true before creating any browser
    // even if you use a custom request context.
    // if you create a browser in the initialization of your app, make sure you call this
    // function when globalcefapp.globalcontextinitialized is true.
    // use the globalcefapp.oncontextinitialized event to know when
    // globalcefapp.globalcontextinitialized is set to true.

    if not(csdesigning in componentstate) and
       not(fclosing)         and
       (fbrowser     =  nil) and
       (fbrowserid   =  0)   and
       (globalcefapp <> nil) and
       globalcefapp.globalcontextinitialized  and
       createclienthandler(aparenthandle = 0) then
      begin
        getsettings(fbrowsersettings);
        initializewindowinfo(aparenthandle, aparentrect, awindowname);

        if globalcefapp.multithreadedmessageloop then
          result := createbrowserhost(@fwindowinfo, fdefaulturl, @fbrowsersettings, aextrainfo, acontext)
         else
          result := createbrowserhostsync(@fwindowinfo, fdefaulturl, @fbrowsersettings, aextrainfo, acontext);
      end;
  except
    on e : exception do
      if customexceptionhandler('tchromium.createbrowser', e) then raise;
  end;
end;

其中就有使用到createclienthandler这个函数(这里的client说的就是继承自icefclient类型,是否和cef的client类似还有待商榷)
createclienthandler会调用tcustomclienthandler.create(self);创建tcustomclienthandler的对象,而且这个函数的传参就是tchromium这个对象自身(因为tchromium继承自ichromiumevents表示event处理机),见下述代码

function tchromium.createclienthandler(aisosr : boolean) : boolean;
begin
  result := false;

  try
    if (fhandler = nil) then
      begin
        fisosr   := aisosr;
        fhandler := tcustomclienthandler.create(self);
        result   := true;
      end;
  except
    on e : exception do
      if customexceptionhandler('tchromium.createclienthandler', e) then raise;
  end;
end;

tcustomclienthandler的对象在构造的同事会再创建一堆handler的对象(我jio得tcustomclienthandler像是一个封装,封装了多个handler并对它们进行管理),以其中的onloaderror为例(这个事件是当加载一个网页失败的时候会触发),tcustomclienthandler会创建一个tcustomloadhandler类型的对象(当然依旧是把tchromium对象传递过去)

constructor tcustomclienthandler.create(const events : ichromiumevents; adevtoolsclient : boolean);
begin
  inherited create;

  initializevars;

  fevents := pointer(events);

  if (events <> nil) then
    begin
      if adevtoolsclient then
        begin
          if events.mustcreatekeyboardhandler    then fkeyboardhandler    := tcustomkeyboardhandler.create(events);
        end
       else
        begin
          if events.mustcreateloadhandler        then floadhandler        := tcustomloadhandler.create(events);
          if events.mustcreatefocushandler       then ffocushandler       := tcustomfocushandler.create(events);
          if events.mustcreatecontextmenuhandler then fcontextmenuhandler := tcustomcontextmenuhandler.create(events);
          if events.mustcreatedialoghandler      then fdialoghandler      := tcustomdialoghandler.create(events);
          if events.mustcreatekeyboardhandler    then fkeyboardhandler    := tcustomkeyboardhandler.create(events);
          if events.mustcreatedisplayhandler     then fdisplayhandler     := tcustomdisplayhandler.create(events);
          if events.mustcreatedownloadhandler    then fdownloadhandler    := tcustomdownloadhandler.create(events);
          if events.mustcreatejsdialoghandler    then fjsdialoghandler    := tcustomjsdialoghandler.create(events);
          if events.mustcreatelifespanhandler    then flifespanhandler    := tcustomlifespanhandler.create(events);
          if events.mustcreaterenderhandler      then frenderhandler      := tcustomrenderhandler.create(events);
          if events.mustcreaterequesthandler     then frequesthandler     := tcustomrequesthandler.create(events);
          if events.mustcreatedraghandler        then fdraghandler        := tcustomdraghandler.create(events);
          if events.mustcreatefindhandler        then ffindhandler        := tcustomfindhandler.create(events);
          if events.mustcreateaudiohandler       then faudiohandler       := tcustomaudiohandler.create(events);
        end;
    end;
end;

在文件ucefloadhandler.pas中,则定义了相关函数

procedure tcustomloadhandler.onloaderror(const browser   : icefbrowser;
                                         const frame     : icefframe;
                                               errorcode : tceferrorcode;
                                         const errortext : ustring;
                                         const failedurl : ustring);
begin
  if (fevents <> nil) then ichromiumevents(fevents).doonloaderror(browser, frame, errorcode, errortext, failedurl);
end;

其中fevents就是tchromium的对象(类型转换后调用),这样就把tchromium对象的事件处理函数连接到这里来了。

之后再往前追溯。是如何把delphi的函数注册给c接口的dll的。在文件ucefloadhandler.pas中,有下面这么一个函数,命名风格和delphi截然不同,初步怀疑就是它被注册的

procedure cef_load_handler_on_load_error(      self      : pcefloadhandler;
                                               browser   : pcefbrowser;
                                               frame     : pcefframe;
                                               errorcode : tceferrorcode;
                                         const errortext : pcefstring;
                                         const failedurl : pcefstring); stdcall;
var
  tempobject : tobject;
begin
  tempobject := cefgetobject(self);

  if (tempobject <> nil) and (tempobject is tcefloadhandlerown) then
    tcefloadhandlerown(tempobject).onloaderror(tcefbrowserref.unwrap(browser),
                                               tcefframeref.unwrap(frame),
                                               errorcode,
                                               cefstring(errortext),
                                               cefstring(failedurl));
end;

它唯一被引用的地方如下

constructor tcefloadhandlerown.create;
begin
  inherited createdata(sizeof(tcefloadhandler));

  with pcefloadhandler(fdata)^ do
    begin
      on_loading_state_change := {$ifdef fpc}@{$endif}cef_load_handler_on_loading_state_change;
      on_load_start           := {$ifdef fpc}@{$endif}cef_load_handler_on_load_start;
      on_load_end             := {$ifdef fpc}@{$endif}cef_load_handler_on_load_end;
      on_load_error           := {$ifdef fpc}@{$endif}cef_load_handler_on_load_error;
    end;
end;

inherited createdata(sizeof(tcefloadhandler));代表调用父类的createdata函数,主要目的是开辟一块内存空间,大小是tcefloadhandler类型大小那么大的内存空间,而fdata就是指向那块内存的地址。

其中on_load_error的定义如下on_load_error : procedure(self: pcefloadhandler; browser: pcefbrowser; frame: pcefframe; errorcode: tceferrorcode; const errortext, failedurl: pcefstring); stdcall;
是一个函数对象

tcefloadhandlerown则是继承自下面这个类型

  // /include/capi/cef_load_handler_capi.h (cef_load_handler_t)
  tcefloadhandler = record
    base                    : tcefbaserefcounted;
    on_loading_state_change : procedure(self: pcefloadhandler; browser: pcefbrowser; isloading, cangoback, cangoforward: integer); stdcall;
    on_load_start           : procedure(self: pcefloadhandler; browser: pcefbrowser; frame: pcefframe; transition_type: tceftransitiontype); stdcall;
    on_load_end             : procedure(self: pcefloadhandler; browser: pcefbrowser; frame: pcefframe; httpstatuscode: integer); stdcall;
    on_load_error           : procedure(self: pcefloadhandler; browser: pcefbrowser; frame: pcefframe; errorcode: tceferrorcode; const errortext, failedurl: pcefstring); stdcall;
  end;

根据这里的注释,找到了cef中的c接口描述文件,发现tcefloadhandler类型和c接口定义的_cef_load_handler_t结构体一致,到此处就基本能确定了,tcefloadhandler就是和c接口对接的注册函数的类型。