CEF4Delphi初識

代碼模塊與職責

全部的代碼都在src目錄下,這會致使一上手的時候沒法快速劃分模塊,不便於理解,若是分類而後放文件夾就會好一些。api

最關鍵的部分在於uCEFApplication,是和dll連接的部分瀏覽器

uCEFInterfaces.pas,能夠在這個文件內找到全部關於接口類型的聲明,抽象了基本類型使用的接口,結構清晰。幾乎是個功能都能找到對應的接口。和cef提供的接口有高度一致性。除了cef相關的接口外,還有自定義的一些工具接口。
uCEFClient.pas,繼承自ICefClient,用於實現獲取Handler的接口
uCEFTypes.pas,這個文件聲明瞭大量的類型,可是不知道是否是全部的類型聲明都在這裏面。
uCEFChromium.pas,存放了TChromium的類型聲明,是實現功能的關鍵類。
uCEFLoadHandler,存放了loadHandler相關類型,回調處理器的一個具體實現,還有不少其餘的handler。
uCEFApplication.pas是核心,涉及到關鍵部分,有關鍵的類TCefApplication,加載了dll並獲取函數,是使用CEF4Delphi的入口。app

關鍵類型和關鍵接口

ICefBrowser:主要是瀏覽器級別的接口,獲取frame,後退前進,中斷加載等接口(瀏覽器進程的功能接口);
ICefFrame:加載網頁的對象,LoadUrl功能就是它提供的。能夠當作加載網頁的那個frame。針對於網頁級別的接口(加載網頁,複製粘貼等在網頁級別的操做接口);
ICefClient:接口,提供獲取各類各樣Handler的接口。其中Handler是回調處理器;
IChromiumEvents:關鍵接口,用於執行瀏覽器的關鍵操做,和handler回調處理器一塊兒工做,實現鏈接libcef.dll的事件傳遞,該接口是自定義接口,非cef接口;函數

附件區域

事件傳遞流程

libcef.dll中註冊的回調事件是如何通知到TChromium對象的呢?工具

在文件uCEFChromium.pas中,TChromium對象的經常使用工做流程CreateBrowser,會進行handler的註冊oop

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處理機),見下述代碼this

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對象傳遞過去)code

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接口對接的註冊函數的類型。

相關文章
相關標籤/搜索