CEF4Delphi初识

Stella981
• 阅读 615

代码模块与职责

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
2年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这