# 크로미움으로 새로운 브로우저 만들기

# 핵심 강의

동영상 준비 중

# 강의 개요

크로미움을 이용해서 자신만의 브로우져를 만드는 방법을 설명합니다. 하이브리드 방식으로 어플리케이션을 개발할 때 활용할 수 있습니다.

아래는 이 강의에서 사용하는 방법으로 만든 프로그램의 예입니다.

# 강의 전 준비 사항

# 이 강의에서 다룰 내용

  • 크로미움을 이용한 자신만의 브로우저 만들기
  • 크로미움 사용 시 주의해야 할 것들
  • 자바스크립트 연동하기

# 프로젝트 시작하기

# 프로젝트 파일 코드 설명

크로미움 콤포넌트를 사용하기 위해서는 프로젝트 파일의 소스를 수정해야 합니다.

program cef;

uses
  uCEFApplication,
  uCEFv8Handler,
  Vcl.Forms,
  _fmMain in '_fmMain.pas' {fmMainOfCEF},
  Core in 'Core\Core.pas',
  View in 'Core\View.pas',
  JavaScript in 'Core\JavaScript.pas',
  Globals in 'Globals.pas';

{$R *.res}

begin
  GlobalCEFApp := TCefApplication.Create;
  GlobalCEFApp.EnableGPU := true;
  GlobalCEFApp.UserAgent := GlobalCEFApp.UserAgent + 'AsomeCodeApp';
  GlobalCEFApp.DeleteCache := false;
  GlobalCEFApp.DeleteCookies := false;
  GlobalCEFApp.OnWebKitInitialized :=
    procedure ()
    begin
      TCefRTTIExtension.Register('App', TJSExtension);
    end;
  if not GlobalCEFApp.StartMainProcess then Exit;
  ///
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TfmMainOfCEF, fmMainOfCEF);
  Application.Run;
  //
  DestroyGlobalCEFApp;
end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  • 4-5: 라인과 같이 uCEFApplication, uCEFv8Handler 유닛 두 개를 uses 절에 추가합니다.
  • 16-26: 라인처럼 크로미움 어플리케이션 객체 생성 및 설정 코드를 삽입합니다.
  • 33: 라인처럼 크로미움 라이브러리의 리소스를 해제하는 코드를 삽입합니다.
  • 2-25: 라인은 자바스킙트를 연동하기 위해 삽입된 코드입니다.
  • 24: 라인에서 자바스크립트 핸들러를 "App"이라는 이름으로 등록합니다. TJSExtension는 여러분들이 직접 만들어야하는 클래스입니다. 예제 소스의 JavaScript.pas 유닛을 참고하시기 바랍니다.
    • html에서 자바스크립트로 "App.test()"라고 호출하면 TJSExtension의 test() 메소드를 찾아서 호출하게 됩니다. 즉, 웹에서 자바스크립트 함수 "App.test()" 호출하면 실제 코드는 델파이 쪽에서 실행됩니다.

# 메인폼 코드 설명

아래는 메인폼의 소스를 일부분 가져와서 설명한 내용입니다.

소스코드 원본 보기 (opens new window)

# interface 영역 설명

unit _fmMain;

interface

uses
  JsonData, Disk,
  uCEFApplication, uCEFInterfaces, uCEFTypes, uCEFStringVisitor, uCEFCookieManager,
  ....;

type
  TfmMainOfCEF = class(TForm)
    ...
  private
    procedure WMCopyData(var Msg:TWMCopyData); message WM_COPYDATA;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    procedure rp_GoHome(AJsonData:TJsonData);
  end;

var
  fmMainOfCEF: TfmMainOfCEF;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 6: 라인
    • JsonData: json 파싱을 위해서 필요합니다.
    • Disk: 디스크 입출력 및 기타 정보를 제공합니다.
  • 7: 크로미움을 이용하기 위해서 필요한 유닛들입니다. 자동으로 삽입되지 않는 유닛들은 직접 입력해야 합니다.
  • 14: 자바스크립로 델파이 함수를 호출할 때 크로미움의 프로세스에서 호출이 됩니다. 따라서 자바스크립트에서 호출된 함수에서 메인 스레드 또는 스레드 세이프하지 않은 코드를 실행하는 것은 위험합니다. 그래서 WM_COPYDATA를 이용하여 메인 스레드로 메시지를 전달하여 사용하는 방식을 사용하고 있습니다. 스레드 세이프한 작업은 자바스크립트가 호출한 함수에서 직접 처리해도 됩니다.
  • 19: RyuLib에서 제공하는 방식으로 WM_COPYDATA 메시지를 수신하면 메시지 안에 표기된 함수 이름으로 함수 자동으로 찾고 호출하는 방식입니다. 반드시 published 영역에 있어야 합니다. 자세한 설명은 동영상에서 다루도록 하겠습니다.

# 구현 코드 설명

constructor TfmMainOfCEF.Create(AOwner: TComponent);
begin
  inherited;

  Chromium.Options.FileAccessFromFileUrls := STATE_ENABLED;
  Chromium.Options.WebSecurity := STATE_DISABLED;

  TCore.Obj.View.Add(Self);
end;

destructor TfmMainOfCEF.Destroy;
begin
  TCore.Obj.View.Remove(Self);

  inherited;
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 5: 로컬 하드디스크의 html 파일 등을 읽을 수 있도록 합니다.
  • 6: CORS(Cross-Origin Resource Sharing)을 무시하도록 합니다. 로컬 html 파일에서 전혀 다른 도메인의 REST api를 호출 할 수 있도록 합니다.
  • 8: 메시지 수신만으로 원하는 메소드를 실행 할 수 있도록 이(Self) 객체를 등록합니다.
  • 13: 더 이상 메시지 수신으로 메소드를 실행하지 않습니다.
procedure TfmMainOfCEF.FormShow(Sender: TObject);
begin
  Chromium.CreateBrowser(CEFWindowParent);
  tmStart.Enabled := true;
end;

procedure TfmMainOfCEF.tmStartTimer(Sender: TObject);
begin
  tmStart.Enabled := false;
  rp_GoHome(nil);
end;

procedure TfmMainOfCEF.rp_GoHome(AJsonData: TJsonData);
begin
  Chromium.LoadURL(GetExecPath + 'index.html');
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 3: 메인 폼이 화면에 보이기도 전에 크로미움이 초기화되면 오류가 날 수 있습니다. 따라서 메인 폼의 OnShow 이벤트에서 크로미움을 초기화합니다.
  • 4: 초기화하고 곧바로 크로미움으로 웹 페이지를 오픈하면 오류가 날 수 있습니다. 타이머를 이용하여 약간의 간격을 두고 페이지 오픈을 실행하도록 합니다.
  • 9: 타이머가 반복해서 실행되지 않도록 합니다.
  • 10: 메인 페이지를 브라우져에 표시합니다.
procedure TfmMainOfCEF.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caNone;
  Hide;
  tmBye.Enabled := true;
end;
1
2
3
4
5
6
  • 프로그램을 곧바로 중료하면 병목을 일으여 멈추는 등의 문제가 있다면 tmBye.OnTimer 이벤트 핸들러 안에서 처리하면 됩니다. 필자는 사용 중인 스레드 종료처리 등으로 프로그램 종료가 오래 결리는 경우에 이 방법을 많이 사용합니다.
  • 3: 메인 폼이 닫겨도 프로그램이 종료되지 않도록합니다.
  • 4: 메인 폼은 감춰서 프로그램 종료가 오래 걸려서 답답함을 느끼지 않도록 합니다.
  • 5: 간격을 두고 종료처리가 될 수 있도록 타이머를 작동시킵니다.
procedure TfmMainOfCEF.tmByeTimer(Sender: TObject);
begin
  Application.Terminate;
end;
1
2
3
4
  • tmBye 타이머가 1초 후에 작동하면 프로그램을 완전히 종료합니다. 시간 간격은 필요한만큼 지정하시면 됩니다.
procedure TfmMainOfCEF.WMCopyData(var Msg: TWMCopyData);
var
  text : ansistring;
begin
  text := PAnsiChar(Msg.CopyDataStruct.lpData);
  TCore.Obj.View.AsyncBroadcast(text);
end;
1
2
3
4
5
6
7
  • 자바스크립트에서 델파이 함수를 호출했을 때 직접 처리하지 않고 WM_COPYDATA 메세지를 거쳐서 델파이의 메인 스레드와의 충돌 문제를 해결합니다.

# JavaScript 유닛 설명

unit JavaScript;

interface

uses
  HandleComponent,
  Windows, Messages, SysUtils, Classes, Dialogs;

type
  TJSExtension = class
    class function version:string;
    class procedure Command(text:string);
  end;

  TJavaScript = class (THandleComponent)
  private
    procedure WMCopyData(var Msg:TWMCopyData); message WM_COPYDATA;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    class function Obj:TJavaScript;

    procedure SendString(const AText:ansistring);
  end;

...

class function TJSExtension.version: string;
begin
  Result := '1.0';
end;

class procedure TJSExtension.Command(text:string);
begin
  TJavaScript.Obj.SendString(text);
end;

procedure TJavaScript.SendString(const AText: ansistring);
var
  receiverHandle : THandle;
  copyDataStruct : TCopyDataStruct;
begin
  receiverHandle := FindWindow('TfmMainOfCEF', nil);
  if receiverHandle = 0 then Exit;

  copyDataStruct.dwData := 0;
  copyDataStruct.cbData := 1 + Length(AText);
  copyDataStruct.lpData := PAnsiChar(AText);
  SendMessage(receiverHandle, WM_COPYDATA, integer(Handle), Integer(@copyDataStruct));
end;

...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  • 자바스크립트에서 "App.함수명()" 형식으로 델파이 코드를 호출 할 수 있도록 합니다. 대부분 코드는 그대로 사용하시면 되기 때문에 수정해야 할 부분들만 설명하도록 하겠습니다.
  • 10-13: 자바스크립트에서 호출할 함수를 정의할 클래스입니다. 모든 함수는 class 메소드로 작성하여야 합니다. 11-12: 처럼 클래스 내에 메소드를 정의하면 자동으로 자바스크립트 쪽에서 호출할 수 있게 됩니다.
  • 15-25: 자바스크립트에서 함수를 호출하면 델파이 메인 스레드로 메시지를 보내서 호출 내용을 델파이 메인 스레드에서 처리하는데 필요한 클래스입니다.
  • 29-32: 자바스크립트로 문자열을 리턴하는 함수의 예입니다.
  • 34-37: 자바스크립트에서 전송한 문자열을 델파이 메인 스레드에 전달하는 함수의 예입니다.
  • 44: 자바스크립트에서 함수를 호출하면 델파이와는 전혀 다른 프로세스에서 처리되기 때문에 윈도우 메시지를 전송해서 처리하고 있습니다. 이때 메인 폼에서 이 메시지를 수신하는데요, 여러분들이 사용하는 메인 폼이 다른 프로그램의 메인 폼과 클래스 이름이 다르게 하셔야 합니다. 44: 라인에서는 여러분들이 메시지를 수신하고 싶은 폼의 클래스 이름을 입력해주시면 됩니다.