Перевод демонстрации расширения JS (CEF4Delphi) из Delphi в C++Builder завершается с ошибкой OnWebKitInitialized

Я хочу создать демонстрационный проект расширения JS, включенный в CEF4Delphi, загруженный с https://github.com/salvadordf/CEF4Delphi, который был установлен на C++Builder XE7. Моя цель - отправлять сообщения и переменные из Chromium (веб-страница, javascript) в собственную функцию C++.

Я нашел демо-версию Delphi, которая работает хорошо. Но мне нужно перевести на C++Builder, а я перевел почти весь код. Это работает хорошо, если я не устанавливаю член OnWebKitInitialized TCefApplication. Но мне нужно установить, чтобы мое расширение было зарегистрировано, поэтому, когда я это делаю, приложение компилируется и строится хорошо, но браузер имеет белый фон и ничего не показывает. Мне нужно, чтобы эта демонстрация работала в C++Builder. Я прикрепил исходный код ниже.

Единица 1.ч

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include "uCEFChromium.hpp"
#include "uCEFWinControl.hpp"
#include "uCEFWindowParent.hpp"
#include <Vcl.ComCtrls.hpp>
#include <Vcl.ExtCtrls.hpp>

#include "uTestExtensionHandler.h"
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TPanel *NavControlPnl;
    TEdit *Edit1;
    TButton *GoBtn;
    TStatusBar *StatusBar1;
    TCEFWindowParent *CEFWindowParent1;
    TChromium *Chromium1;
    TTimer *Timer1;
    void __fastcall Chromium1AfterCreated(TObject *Sender, ICefBrowser * const browser);
    void __fastcall Chromium1BeforeClose(TObject *Sender, ICefBrowser * const browser);
    void __fastcall Chromium1BeforeContextMenu(TObject *Sender, ICefBrowser * const browser,
          ICefFrame * const frame, ICefContextMenuParams * const params,
          ICefMenuModel * const model);
    void __fastcall Chromium1BeforePopup(TObject *Sender, ICefBrowser * const browser,
          ICefFrame * const frame, const ustring targetUrl, const ustring targetFrameName,
          TCefWindowOpenDisposition targetDisposition, bool userGesture,
          const TCefPopupFeatures &popupFeatures, TCefWindowInfo &windowInfo,
          ICefClient *&client, TCefBrowserSettings &settings, bool &noJavascriptAccess,
          bool &Result);
    void __fastcall Chromium1Close(TObject *Sender, ICefBrowser * const browser, bool Result);
    void __fastcall Chromium1ContextMenuCommand(TObject *Sender, ICefBrowser * const browser,
          ICefFrame * const frame, ICefContextMenuParams * const params,
          int commandId, DWORD eventFlags, bool Result);
    void __fastcall Chromium1ProcessMessageReceived(TObject *Sender, ICefBrowser * const browser,
          TCefProcessId sourceProcess, ICefProcessMessage * const message,
          bool Result);
    void __fastcall Timer1Timer(TObject *Sender);
    void __fastcall GoBtnClick(TObject *Sender);
    void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
    void __fastcall FormCreate(TObject *Sender);
    void __fastcall FormShow(TObject *Sender);
    void __fastcall FormDestroy(TObject *Sender);





private:    // User declarations
public:     // User declarations
    __fastcall TForm1(TComponent* Owner);
protected:
    // Variables to control when can we destroy the form safely
    bool FCanClose;  // Set to True in TChromium.OnBeforeClose
    bool FClosing;  // Set to True in the CloseQuery event.
    void __fastcall BrowserCreatedMsg(TMessage &Message);
    void __fastcall BrowserDestroyMsg(TMessage &Message);
    void __fastcall WMMove(TMessage &Message);
    void __fastcall WMMoving(TMessage &Message);
    void __fastcall WMEnterMenuLoop(TMessage &Message);
    void __fastcall WMExitMenuLoop(TMessage &Message);

    BEGIN_MESSAGE_MAP
     MESSAGE_HANDLER(CEF_AFTERCREATED, TMessage, BrowserCreatedMsg)
     MESSAGE_HANDLER(CEF_DESTROY, TMessage, BrowserDestroyMsg)
     MESSAGE_HANDLER(WM_MOVE, TMessage, WMMove)
     MESSAGE_HANDLER(WM_MOVING, TMessage, WMMoving)
     MESSAGE_HANDLER(WM_ENTERMENULOOP, TMessage, WMEnterMenuLoop)
     MESSAGE_HANDLER(WM_EXITMENULOOP, TMessage, WMExitMenuLoop)
    END_MESSAGE_MAP(TForm)

};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Unit1.cpp

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "uCEFChromium"
#pragma link "uCEFWinControl"
#pragma link "uCEFWindowParent"
#pragma resource "*.dfm"
TForm1 *Form1;

void GlobalCEFApp_OnWebKitInitialized()
{
  String TempExtensionCode;
  //ICefv8Handler *TempHandler;
  _di_ICefv8Handler TempHandler;

  // This is a JS extension example with 2 functions and several parameters.
  // Please, read the "JavaScript Integration" wiki page at
  // https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md

  TempExtensionCode = "var myextension;\
                       if (!myextension)\
                         myextension = {};\
                       (function() {\
                         myextension.mouseover = function(a) {\
                           native function mouseover();\
                           mouseover(a);\
                         };\
                         myextension.sendresulttobrowser = function(b,c) {\
                           native function sendresulttobrowser();\
                           sendresulttobrowser(b,c);\
                         };\
                       })();";

  try {
    TempHandler = TTestExtensionHandler();
    CefRegisterExtension("myextension", TempExtensionCode, TempHandler);
  }
  __finally {
    TempHandler = NULL;
  }
}
class TWebKitInitRef : public TCppInterfacedObject<TOnWebKitInitializedEvent>
{
public:
    //TWebKitInitRef(){  Invoke();}
    INTFOBJECT_IMPL_IUNKNOWN(TInterfacedObject);
    void __fastcall Invoke() {    GlobalCEFApp_OnWebKitInitialized(); }
};

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    GlobalCEFApp = new TCefApplication();
    GlobalCEFApp->CheckCEFFiles =true;
    GlobalCEFApp->OnWebKitInitialized = _di_TOnWebKitInitializedEvent(new TWebKitInitRef());

    GlobalCEFApp->LogFile             = "debug.log";
    GlobalCEFApp->LogSeverity         = LOGSEVERITY_INFO;

    if(! GlobalCEFApp->StartMainProcess())Form1->Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1AfterCreated(TObject *Sender, ICefBrowser * const browser)

{
 PostMessage(Handle, CEF_AFTERCREATED, 0, 0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1BeforeClose(TObject *Sender, ICefBrowser * const browser)

{
 FCanClose = true;
 PostMessage(Handle, WM_CLOSE, 0, 0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1BeforeContextMenu(TObject *Sender, ICefBrowser * const browser,
          ICefFrame * const frame, ICefContextMenuParams * const params,
          ICefMenuModel * const model)
{
  // Adding some custom context menu entries
  model->AddSeparator();
  model->AddItem(MENU_ID_USER_FIRST + 1,  "Set mouseover event");
  model->AddItem(MENU_ID_USER_FIRST + 2,  "Visit DOM in JavaScript");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1BeforePopup(TObject *Sender, ICefBrowser * const browser,
          ICefFrame * const frame, const ustring targetUrl, const ustring targetFrameName,
          TCefWindowOpenDisposition targetDisposition, bool userGesture,
          const TCefPopupFeatures &popupFeatures, TCefWindowInfo &windowInfo,
          ICefClient *&client, TCefBrowserSettings &settings, bool &noJavascriptAccess,
          bool &Result)
{
  Result = targetDisposition==WOD_NEW_FOREGROUND_TAB || targetDisposition==WOD_NEW_BACKGROUND_TAB || targetDisposition==WOD_NEW_POPUP || targetDisposition==WOD_NEW_WINDOW ? true: false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1Close(TObject *Sender, ICefBrowser * const browser,
          bool Result)
{
 PostMessage(Handle, CEF_DESTROY, 0, 0);
 Result = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1ContextMenuCommand(TObject *Sender, ICefBrowser * const browser,
          ICefFrame * const frame, ICefContextMenuParams * const params,
          int commandId, DWORD eventFlags, bool Result)

{
  Result = false;

  // Here is the code executed for each custom context menu entry

  switch( commandId)
  {
    case (MENU_ID_USER_FIRST + 1) :
      if ((browser != NULL) && (browser->MainFrame != NULL))
        browser->MainFrame->ExecuteJavaScript("document.body.addEventListener('mouseover', function(evt){\
            function getpath(n){\
              var ret = '<' + n.nodeName + '>';\
              if (n.parentNode){return getpath(n.parentNode) + ret} else \
              return ret\
            };\
            myextension.mouseover(getpath(evt.target))})",
            // This is the call from JavaScript to the extension with DELPHI code in uTestExtensionHandler.pas
          "about:blank", 0);

    case (MENU_ID_USER_FIRST + 2) :
      if ((browser != NULL) && (browser->MainFrame != NULL))
        browser->MainFrame->ExecuteJavaScript("var testhtml = document.body.innerHTML;\
          myextension.sendresulttobrowser(testhtml, " + QuotedStr((AnsiString)"customname") + ");",  // This is the call from JavaScript to the extension with DELPHI code in uTestExtensionHandler.pas
          "about:blank", 0);
  }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chromium1ProcessMessageReceived(TObject *Sender, ICefBrowser * const browser,
          TCefProcessId sourceProcess, ICefProcessMessage * const message,
          bool Result)
{
  if ((message == NULL) || (message->ArgumentList == NULL)) exit;

  // This function receives the messages with the JavaScript results

  // Many of these events are received in different threads and the VCL
  // doesn't like to create and destroy components in different threads.

  // It's safer to store the results and send a message to the main thread to show them.

  // The message names are defined in the extension or in JS code.

  if (message->Name == "mouseover")
    {
      StatusBar1->Panels->Items[0]->Text = message->ArgumentList->GetString(0);
      Result = true;
    }
  else
    if (message->Name == "customname")
      {
        StatusBar1->Panels->Items[0]->Text = message->ArgumentList->GetString(0);
        Result = true;
      }
  else Result = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
   Timer1->Enabled = false;
  if(! (Chromium1->CreateBrowser(CEFWindowParent1, "")) && !(Chromium1->Initialized))
    Timer1->Enabled = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::GoBtnClick(TObject *Sender)
{
  Chromium1->LoadURL(Edit1->Text);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  //Action = FCanClose ? caFree : caNone;

  if (!FClosing)
    {
      FClosing = true;
      Visible  = false;
      Chromium1->CloseBrowser(true);
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 FCanClose = false;
 FClosing  = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
   StatusBar1->Panels->Items[0]->Text= "Initializing browser. Please wait...";

  // GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser
  // If it's not initialized yet, we use a simple timer to create the browser later.
  if (!Chromium1->CreateBrowser(CEFWindowParent1, "")) Timer1->Enabled = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
 GlobalCEFApp->~TCefApplication();
 DestroyGlobalCEFApp();
}
//---------------------------------------------------------------------------


void __fastcall TForm1::WMMove(TMessage &Message)
{
  if (Chromium1 != NULL) Chromium1->NotifyMoveOrResizeStarted();
}

void __fastcall TForm1::WMMoving(TMessage &Message)
{
  if (Chromium1 != NULL) Chromium1->NotifyMoveOrResizeStarted();
}

void __fastcall TForm1::WMEnterMenuLoop(TMessage &Message)
{
  if ((Message.WParam == 0) && (GlobalCEFApp != NULL)) GlobalCEFApp->OsmodalLoop = true;
}

void __fastcall TForm1::WMExitMenuLoop(TMessage &Message)
{
  if ((Message.WParam == 0) && (GlobalCEFApp != NULL)) GlobalCEFApp->OsmodalLoop = false;
}

void __fastcall TForm1::BrowserCreatedMsg(TMessage &Message)
{
  StatusBar1->Panels->Items[0]->Text = "";
  CEFWindowParent1->UpdateSize();
  NavControlPnl->Enabled = true;
  GoBtn->Click();
}

void __fastcall TForm1::BrowserDestroyMsg(TMessage &Message)
{
  CEFWindowParent1->Free();
}

uTestExtensionHandler.h

// ************************************************************************
// ***************************** CEF4Delphi *******************************
// ************************************************************************
//
// CEF4Delphi is based on DCEF3 which uses CEF3 to embed a chromium-based
// browser in Delphi applications.
//
// The original license of DCEF3 still applies to CEF4Delphi.
//
// For more information about CEF4Delphi visit :
//         https://www.briskbard.com/index.php?lang=en&pageid=cef
//
#ifndef uTestExtensionHandlerH
#define uTestExtensionHandlerH


#include "uCEFRenderProcessHandler.hpp"
#include "uCEFBrowserProcessHandler.hpp"
#include "uCEFInterfaces.hpp"
#include "uCEFProcessMessage.hpp"

#include "uCEFv8Context.hpp"
#include "uCEFTypes.hpp"
#include "uCEFv8Handler.hpp";

class TTestExtensionHandler : public _di_ICefv8Handler//public ICefv8Handler
{
    protected:
      //bool __fastcall Execute(ustring name, ICefv8Value *obj, TCefv8ValueArray arguments, ICefv8Value *retval, ustring exception);
      bool __fastcall Execute(const Uceftypes::ustring name, const _di_ICefv8Value obj, const TCefv8ValueArray arguments, _di_ICefv8Value &retval, Uceftypes::ustring &exception);

};



//uses uCEFMiscFunctions, uCEFConstants, uJSExtension;

bool __fastcall  TTestExtensionHandler::Execute(const Uceftypes::ustring name, const _di_ICefv8Value obj, const TCefv8ValueArray arguments, _di_ICefv8Value &retval, Uceftypes::ustring &exception)
{
  ICefProcessMessage *msg;
  bool Result;
  if (name == "mouseover")
  {
      if ((arguments.Length > 0) && arguments[0]->IsString())
        {
          msg = TCefProcessMessageRef::New("mouseover");
          msg->ArgumentList->SetString(0, arguments[0]->GetStringValue());

          TCefv8ContextRef::Current()->Browser->SendProcessMessage(PID_BROWSER, msg);
        }

      Result = true;
  }
   else
    if (name == "sendresulttobrowser")
      {
        if ((arguments.Length > 1) && arguments[0]->IsString() && arguments[1]->IsString())
          {
            msg = TCefProcessMessageRef::New(arguments[1]->GetStringValue());
            msg->ArgumentList->SetString(0, arguments[0]->GetStringValue());

            TCefv8ContextRef::Current()->Browser->SendProcessMessage(PID_BROWSER, msg);
          }

        Result = true;
      }
     else
      Result = false;
 return Result;

}

#endif

Я ожидаю, что правильно зарегистрирую свое расширение, чтобы иметь возможность возвращать сообщения и переменные результатов с веб-страницы в собственную функцию С++. Я сделал почти то же самое из демо-версии Delphi, но не могу заставить его работать в C++Builder. Что я должен изменить, чтобы заставить это работать?

ОБНОВЛЕНИЕ:

Я пробовал второй метод:

#include "uCEFv8Value.hpp"

class MyV8Handler : public _di_ICefv8Handler {
public:
MyV8Handler() {} ;

virtual bool __fastcall Execute(const Uceftypes::ustring name, const _di_ICefv8Value obj, const TCefv8ValueArray arguments, _di_ICefv8Value &retval, Uceftypes::ustring &exception)
{         
  if (name == "myfunc") { 
// Extract argument values
// Return my string value.
 retval = TCefv8ValueRef::NewString("My Value!");
return true;
}

// Function does not exist.
return false;
}

// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(MyV8Handler);
};

class TContextRef : public TCppInterfacedObject<TOnContextCreatedEvent>
{
public:
    //TContextRef(){  Invoke();}
    INTFOBJECT_IMPL_IUNKNOWN(TInterfacedObject);
    void __fastcall Invoke(const _di_ICefBrowser browser, const _di_ICefFrame frame, const _di_ICefv8Context context)
    {    
      // Retrieve the context's window object.
      _di_ICefv8Value object = context->GetGlobal();

        // Create an instance of my CefV8Handler object.
        ICefv8Handler *handler = dynamic_cast<ICefv8Handler*>(new MyV8Handler());

        // Create the "myfunc" function.
        _di_ICefv8Value func = TCefv8ValueRef::NewFunction("myfunc", handler);

        // Add the "myfunc" function to the "window" object.
        object->SetValueByKey("myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
    }
};

...

GlobalCEFApp->OnContextCreated = _di_TOnContextCreatedEvent(new TContextRef());

Chromium1->ExecuteJavaScript("alert(window.myfunc('someString'));", "", 0);

Я думаю, что GlobalCEFApp->OnContextCreated не вызывается, как и GlobalCEFApp->OnWebKitInitialized. Может быть, ссылка на функцию передана неправильно?


person Aziz    schedule 23.03.2019    source источник
comment
Может связаны? Rg. OnWebKitInitialized не вызывается   -  person Remy Lebeau    schedule 23.03.2019
comment
Если вы получаете белый экран при включении кода GlobalCEFApp_OnWebKitInitialized, значит процесс рендеринга дает сбой. CEF4Delphi по умолчанию использует несколько процессов, а GlobalCEFApp.OnWebKitInitialized выполняется в процессе рендеринга. Вам необходимо включить режим одного процесса для отладки этого кода, установив для GlobalCEFApp.SingleProcess значение true. Этот режим используется только для отладки и никогда не должен включаться в вашей окончательной сборке, но он позволяет вам получать сообщения об ошибках. Установите точку останова в GlobalCEFApp_OnWebKitInitialized и другую в TTestExtensionHandler::Execute.   -  person Salvador Díaz Fau    schedule 23.03.2019
comment
@RemyLebeau Я попробовал второй метод, вы можете видеть в обновленном вопросе, но это то же самое, и я думаю, что GlobalCEFApp->OnContextCreated не вызывается, например GlobalCEFApp->OnWebKitInitialized не вызывается. Может быть, ссылка на функцию передана неправильно?   -  person Aziz    schedule 23.03.2019
comment
@SalvadorDíazFau Я сделал то, что вы сказали, и если я сделаю SingleProcess, он вылетит без ошибок. Но я вижу в debug.log это: [0323/195017.740:ERROR:CEF4Delphi(1)] TCefApplication.ExecuteProcess error : Access violation at address 10CDBC59 in module 'libcef.dll'. Read of address 00000000 . Я думаю, проблема может быть вызвана неправильной ссылкой на функцию OnWebKitInitialized или OnContextCreated, потому что она не вызывается   -  person Aziz    schedule 23.03.2019
comment
Попробуйте изменить объявление TOnWebKitInitializedEvent в коде CEF4Delphi на что-то другое, что проще назначить процедуре в C++. Извините за неясность, но мои знания C++ Builder ограничены.   -  person Salvador Díaz Fau    schedule 23.03.2019
comment
@AyayMatty проблема не в том, как назначаются обработчики событий. Происходит что-то еще. В сообщении об ошибке говорится, что осуществляется доступ к указателю NULL. Используйте отладчик, чтобы перейти к адресу, указанному в сообщении об ошибке, и посмотреть, какой код фактически выполняется по этому адресу, и посмотреть, к какому указателю он пытается получить доступ.   -  person Remy Lebeau    schedule 24.03.2019
comment
@SalvadorDíazFau Пожалуйста, скажите мне, что такое TValue из class procedure Register(const name: ustring; const value: TValue; SyncMainThread: Boolean = False); в TCefRTTIExtension = class(TCefv8HandlerOwn) Если возможно, покажите мне фрагмент кода в delphi, как вы создаете и передаете TValue в процедуре регистрации   -  person Aziz    schedule 24.03.2019
comment
TValue определяется в System.Rtti: docwiki.embarcadero.com/Libraries/ Рио/en/System.Rtti.TValue   -  person Salvador Díaz Fau    schedule 24.03.2019
comment
Если вы хотите избежать использования TValue, попробуйте преобразовать другую демонстрацию. JSRTTIExtension использует класс TCefRTTIExtension, но JSExtension делает то же самое без него. Единственный код, который я могу вам показать, это: github.com/salvadordf/CEF4Delphi/blob/   -  person Salvador Díaz Fau    schedule 24.03.2019
comment
@SalvadorDíazFau Я хочу знать, для чего используется этот объект TValue, какие данные я должен поместить в объект TValue. Я нашел это в паскалевских файлах uCEFv8Handler, и есть только одна процедура регистрации, которая имеет этот прототип: class procedure Register(const name: ustring; const value: TValue; SyncMainThread: Boolean = False); так что я понимаю другие параметры, но для чего используется второй параметр, что я должен в нем указать?   -  person Aziz    schedule 24.03.2019
comment
Процедура TCefRTTIExtension.Register, используемая в демонстрации JSRTTIExtension, представляет собой другой способ регистрации расширения. Демонстрация JSExtension следует инструкциям CEF для создания расширения к письму. У него есть TTestExtensionHandler, который выполняет код расширения, а событию OnWebKitInitialized достаточно вызвать CefRegisterExtension с кодом JS и TTestExtensionHandler для регистрации расширения. Однако демонстрация JSRTTIExtension использует класс TCefRTTIExtension для более простой регистрации расширения ‹продолжение в следующем комментарии›   -  person Salvador Díaz Fau    schedule 24.03.2019
comment
TCefRTTIExtension.Register требуется только имя расширения и тип класса с несколькими функциями класса в качестве второго параметра (TValue). В случае JSRTTIExtension он использует тип класса TTestExtension в качестве второго параметра в TCefRTTIExtension.Register. TTestExtension имеет 2 функции класса, которые выполняются, когда код JavaScript вызывает myextension.mouseover или myextension.sendresulttobrowser.   -  person Salvador Díaz Fau    schedule 24.03.2019
comment
Я не знаю, легко ли это перевести в C++ Builder, но если вы посмотрите на код внутри TCefRTTIExtension.Register, вы также увидите, что он использует defineGetter и defineSetter, которые устарели. Вместо этого я бы предложил вам использовать демонстрацию JSExtension.   -  person Salvador Díaz Fau    schedule 24.03.2019