代碼模塊與職責
所有的代碼都在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接口對接的注冊函數的類型。
