# 크로미움으로 새로운 브로우저 만들기
# 핵심 강의
동영상 준비 중
# 강의 개요
크로미움을 이용해서 자신만의 브로우져를 만드는 방법을 설명합니다. 하이브리드 방식으로 어플리케이션을 개발할 때 활용할 수 있습니다.
아래는 이 강의에서 사용하는 방법으로 만든 프로그램의 예입니다.
# 강의 전 준비 사항
- Delphi 7 또는 그 이후 버전
- git
- https://github.com/salvadordf/CEF4Delphi (opens new window) 설치
- git checkout 이후 델파이에서 콤포넌트 등록 및 설치
- https://github.com/ryujt/ryulib-delphi (opens new window) 설치
- git checkout 이후 델파이에서 콤포넌트 등록 및 설치
- 예제 소스 https://github.com/ryujt/cef-delphi (opens new window) 참고
# 이 강의에서 다룰 내용
- 크로미움을 이용한 자신만의 브로우저 만들기
- 크로미움 사용 시 주의해야 할 것들
- 자바스크립트 연동하기
# 프로젝트 시작하기
# 프로젝트 파일 코드 설명
크로미움 콤포넌트를 사용하기 위해서는 프로젝트 파일의 소스를 수정해야 합니다.
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
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()" 호출하면 실제 코드는 델파이 쪽에서 실행됩니다.
# 메인폼 코드 설명
아래는 메인폼의 소스를 일부분 가져와서 설명한 내용입니다.
# 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
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
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
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
2
3
4
5
6
- 프로그램을 곧바로 중료하면 병목을 일으여 멈추는 등의 문제가 있다면 tmBye.OnTimer 이벤트 핸들러 안에서 처리하면 됩니다. 필자는 사용 중인 스레드 종료처리 등으로 프로그램 종료가 오래 결리는 경우에 이 방법을 많이 사용합니다.
- 3: 메인 폼이 닫겨도 프로그램이 종료되지 않도록합니다.
- 4: 메인 폼은 감춰서 프로그램 종료가 오래 걸려서 답답함을 느끼지 않도록 합니다.
- 5: 간격을 두고 종료처리가 될 수 있도록 타이머를 작동시킵니다.
procedure TfmMainOfCEF.tmByeTimer(Sender: TObject);
begin
Application.Terminate;
end;
1
2
3
4
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
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
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: 라인에서는 여러분들이 메시지를 수신하고 싶은 폼의 클래스 이름을 입력해주시면 됩니다.