본문 바로가기

프로그래밍/Delphi

[강좌-Delphi]웹브라우저에서 자바스크립트로 델파이 코드 실행하기

1. 소개


  델파이로 작성하는 프로그램에서 웹브라우저 컴포넌트(TWebBrowser)를 이용하는 경우 웹브라우저에서 델파이 콘트롤을 제어하여 웹브라우저와 델파이 코드간의 소통이 필요한 경우가 있다.

  이를 위한 방법중 하나인 웹브라우저의 외부 객체(Web Browser's external object)를 확장하여 해결하는 방법을 설명한다.

  • 외부객체(External Object)는 TWebBrowser의 문서모델인 DOM의 일부분
  • 이 객체는 윕브라우저와의 상호 제어를 활성화 시킨다.
  • 자바스크립트(Javascript) 또는 VBScript를 이용하지 않고 델파이 코드로 구현하여 웹브라우저의 외부 객체로 확장 시킬 수 있다.
  • 델파이에서는 요구된 기능이 구현된 COM 자동화 객체를 생성하고 이를 웹브라우저(TWebBrowser)의 외부 객체로 지정한다.
  • 델파이로 군현된 COM 객체의 함수 또는 프로시저는 웹브라우저 내에서 JavaScript 또는 VBScript를 통해서 호출 될 수 있다.
  • 이 호출에 의해서 COM객체 내에 구현된 델파이 코드가 실행되는 결과를 얻을 수 있다.
  • 활용사례)

    1) 웹브라우저내 소스를 변경하여 alert(message) 소스를 델파이 확장 개체를 호출하도록 변경하여 Delphi 응용 

       프로그램에 전달.

    2) 웹페이지 내에서 버튼 클릭시 델파이 코드를 실행하도록 하여 델파이 응용 프로그램에 웹 페이지내의 데이터를 

       이용한 처리가 가능


2. 개요


다음과 같은 방법으로 웹브라우에서 델파이 코드를 직접 호출 할 수 있다.


  • 델파이에서 요구된 기능이 구현된 COM 자동화 객체를 생성
  • 웹브라우저에 확장 외부객체로 등록한다. (델파이에서 IDocHostUIHandler 인터페이스에 대한 구현부를 작성하고 TWebBrowser에서 이를 사용할 수 있도록 활성)
  • 웹브라우저의 실행되는 HTML Source 내의 JavaScript에서 이 확장 외부 객체의 함수를 호출 한다.


3. 외부 객체의 구현(Implementing the external object)


COM 자동화 객체를 생성하는 방법은 델파이의 TAutoIntfObject를 상속받아서 새로운 클래스를 만들면 되는데 TAutoIntfObject는 IDispach 인터페이스를 구현한 클래스 중 하나이다.

TAutoIntfObject는 Type Library가 필요한데 델파이에서 Type Library Editor를 이용하여 새로운 인터페이스를 정의한 적당한 Type Library를 생성할 수 있다.


  Type Library를 생성하는 방법


  1) 새로운 프로젝트를 생성한다.

     - VCL Form Application으로 프로젝트를 하나 생성하고 전체 저장하여 "WebControlProject1"(임의) 으로 저장한다


  2) Delphi>File>New>Other를 클릭한다

  3) 다이얼로그 "New Item"창에서 "Delphi Projects/ActiveX 탭에서 "Type Library"를 선택하고 "OK"버튼 클릭



  4) 저장하면 TypeLibrary 관련 다음과 같은 파일이 생성된다.

      - WebControlProject1.ridl

      - WebControlProject1.tlb

      - WebControlProject1_TLB.pas


  5) 인터페이스를 생성하고 요구 기능을 실행할 메소드를 정의 한다.

      - 메소드 정의시 확인 해야 할 사항

        . IDispach를 상속 받은 인터페이스 인지 확인

        . 모든 메소드가 HRESULT의 리턴 값을 가지는지

        . Automation 호환 타입의 파라미터를 사용하는지

        . 모든 [out] 파라미터가 포인터 타입을 가지는지(예를 들어 "BSTR *"  )

        . [out, retval] 지시자를 가진 파라미터를 사용하는 메소드로 부터 값이 리턴 되는지






  위 과정까지 생성된  소스 코드



WebControlProject1.ridl Source Code ]

[
  uuid(2614EBB6-F61C-476C-A661-A36A09F7E09D),
  version(1.0)

]
library WebControlProject1
{

  importlib("stdole2.tlb");
  importlib("stdvcl40.dll");

  interface IMyExternal;


  [
    uuid(146F26DF-8992-4D4C-8A73-ECDE2929B9A4),
    dual,
    oleautomation
  ]
  interface IMyExternal: IDispatch
  {
    [id(0x000000C9)]
    HRESULT _stdcall Foo([in] long Param1, [out, retval] BSTR* Result);
    [id(0x000000CA)]
    HRESULT _stdcall Bar([in] BSTR Param1);
  };

};

WebControlProject1_TLB Source Code ]

const
  // TypeLibrary Major and minor versions
  WebControlProject1MajorVersion = 1;
  WebControlProject1MinorVersion = 0;

  LIBID_WebControlProject1: TGUID = '{2614EBB6-F61C-476C-A661-A36A09F7E09D}';

  IID_IMyExternal: TGUID = '{146F26DF-8992-4D4C-8A73-ECDE2929B9A4}';
type

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary
// *********************************************************************//
  IMyExternal = interface;
  IMyExternalDisp = dispinterface;

// *********************************************************************//
// Interface: IMyExternal
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {146F26DF-8992-4D4C-8A73-ECDE2929B9A4}
// *********************************************************************//
  IMyExternal = interface(IDispatch)
    ['{146F26DF-8992-4D4C-8A73-ECDE2929B9A4}']
    function Foo(Param1: Integer): WideString; safecall;
    procedure Bar(const Param1: WideString); safecall;
  end;

// *********************************************************************//
// DispIntf:  IMyExternalDisp
// Flags:     (4416) Dual OleAutomation Dispatchable
// GUID:      {146F26DF-8992-4D4C-8A73-ECDE2929B9A4}
// *********************************************************************//
  IMyExternalDisp = dispinterface
    ['{146F26DF-8992-4D4C-8A73-ECDE2929B9A4}']
    function Foo(Param1: Integer): WideString; dispid 201;
    procedure Bar(const Param1: WideString); dispid 202;
  end;


  6) IAutoIntfObject를 상속받은 파생 클래스를 정의하고 IMyIntf 인터페이스의 메서드를 구현


interface
uses Winapi.Windows, System.Classes, System.Variants, System.Win.StdVCL,
  Vcl.Graphics, Winapi.ActiveX, System.Win.ComObj, Dialogs, SysUtils;

type
  TMyExternal = class(TAutoIntfObject,IMyExternal, IDispatch)
  private

  protected
    { IMyExternal methods }
    function Foo(Param1: Integer): WideString; safecall;
    procedure Bar(const Param1: WideString); safecall;

    constructor Create;
  end;
...

implementation

{ TMyExternal }

constructor TMyExternal.Create;
var
  TypeLib: ITypeLib;    // 타입라이브러리 인터페이스
  ExeName: WideString;  // 실행 프로그램 명 = ParamStr(0)
begin

  // 현재 실행된프로그램의 이름을 얻음
  ExeName := ParamStr(0);
  // 프로그램의 리소스에 포함된 타입라이브러리를 얻는다.
  OleCheck(LoadTypeLib(PWideChar(ExeName), TypeLib));
  // 얻어진 유형의 타입라이브러리를 생성한다.
  inherited Create(TypeLib, IMyExternal);
  // ...
  // 필요시 다른 초기화 정보 코딩...
  // ...
end;

procedure TMyExternal.Bar(const Param1: WideString);
begin
  ShowMessage('Call TMyExternal.Bar : ' + Param1);
end;

function TMyExternal.Foo(Param1: Integer): WideString;
begin
  ShowMessage('Call TMyExternal.Foo : ' + intToStr(Param1));
end;


4. TWebBrowser에 외부객체(External Object) 등록하기


  위에서 작성된 외부 객체를 TWebBrowser가 인식 할 수 있도록 등록 하려면 

    - 웹브라우저 콘트롤을 호스팅하고 IDocHostUIHandler 인터페이스를 구현하는 컨테이너 객체를 만듦

    - 모든 웹브라우저 컨트롤 콘테이너 객체는 IOleClientSite 인터페이스도 구현

    - IDocHostUIHandler에는 GetExternal 메서드가 있는데 이 메서드를 구현 할 때 사용자 지정 외부 개체에 대한 

      참조를 웹브라우저에 전달해야 함.  


  IDocHostUIHandler 인터페이스에는 구현해야 할 많은 수의 메소드가 있지만 필요한 메서드만 구현 해 주면 된다.

  아무 메소드도 구현 하지 않은 TNulWBContainer를 이용하여 외부 컨테이너를 구현해 보도록 한다.

  TNulWBContainer는 IDocHostUIHandler 및 IOleClientSite의 구현을 단순화 시킨 것으로 TExternalContainer 작성을 간단하게 해 준다.

  (TNulWBContainer는 앞 강좌 [강좌-Delphi]웹브라우저(TWebBrowser)의 사용자 인터페이스 커스터마이징 를 참고)


  TNulWBContainer를 상속받는 TExternalContainer의 선언과 구현은 다음과 같다.




type
  TExternalContainer = class(TNulWBContainer,
    IDocHostUIHandler, IOleClientSite)
  private
    fExternalObj: IDispatch;  // 외부 구현 객체 
  protected
    {GetExternal 메서드 오버라이드 구현 }
    function GetExternal(out ppDispatch: IDispatch): HResult; stdcall;
  public
    constructor Create(const HostedBrowser: TWebBrowser);
  end;
....

constructor TExternalContainer.Create(
  const HostedBrowser: TWebBrowser);
begin
  inherited Create(HostedBrowser);
  fExternalObj := TMyExternal.Create;
end;

function TExternalContainer.GetExternal(
  out ppDispatch: IDispatch): HResult;
begin
  ppDispatch := fExternalObj;
  Result := S_OK;
end;


 생성자에서 외부 객체를 저장할 TMyExternal 객체를 생성하고 GetExternal에서 이 확장 객체가 지정 될 수 있도록 했다. 생성자에는 호스팅 하려고 하는 웹브라우저를 파라미터로 전달 해 준다.

지금까지 웹브라우저에게 외부 객체를 등록하기 위한 필수 코드를 작성 했으니 웹브라우저내의 자바스크립트에서 이 외부 객체를 어떻게 호출 하는 알아 보자.



5. JavaScript에서 Delphi Code 호출


  4절에서 웹브라우저에 델파이로 작성한 외부 객체의 메소드를 웹브라우저 내 자바스크립트에서 어떻게 호출하는지알아보자. 물론 VB스크립트로도 확장 객체의 메소드를 호출 할 수 있다. 자바 스크립트로 확장 객체의 Foo 메소드를 호출 하는 방법은 다음과 같다.

external.Foo();

외부 객체(external)의 메소드 들은 각종 HTML Element의 이벤트 핸들러나 JavaScript 코드 블럭내 어디에서든지 실행 시킬 수 있다. 다음과 같이 외부 인터페이스가 구현되어 있고 확장 객체로 웹브라우저에 등록 되어 있는 경우의 예를 살펴 보면


type
  ITest = interface(IDispatch)
    ['{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}']
    procedure SetLabel(const Msg: WideString);
      safecall;
    function GetEMail(const Name: WideString): WideString;
      safecall;
  end;

SetLabel은 TLabel의 Caption 값을 변경하는 것을 의미 할 것이고, GetEMail은 아마도 사용자에 대한 이메일 정보를 DB에서 사용자 명으로 얻는 메소드라 생각해 보면 TForm에 Embedding된 TWebBrowser내의 HTML 코드에서 다음과 같은 호출이 가능해 진다.

 ...
<body>
  <a href="#" onclick="external.SetLabel('Hello Delphi');">클릭</a>
  ...
</body>

or


  ...
<body>
  <p>Fred's email address is:
  <script type="text/JavaScript">
    <--
    document.write( external.GetEMail("홍길동") );
    //-->
  </script>
  </p>
  ...
</body>

 TWebBrowser에 한 번 External 객체를 등록해 놓으면 쉽게 추가적인 다른 메소드들을 작성하고 호출 할 수 있게 된다. 


Demo Project  : [Demo-Delphi]웹브라우저에서 자바스크립트로 델파이 코드 실행하기

Reference : [강좌-Delphi]웹브라우저(TWebBrowser)의 사용자 인터페이스 커스터마이징

How to call Delphi code from scripts running in a TWebBrowser