CEF4Delphi初识
代码模块与职责
所有的代码都在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接口对接的注册函数的类型。