当前位置: 移动技术网 > IT编程>开发语言>Delphi > CEF4Delphi初识

CEF4Delphi初识

2019年11月28日  | 移动技术网IT编程  | 我要评论

代码模块与职责

所有的代码都在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接口对接的注册函数的类型。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网