본문 바로가기

파이어몽키

[FMX] Firemonkey 구조 - 어떻게 하나의 코드로 여러 플랫폼에서 실행 될까?

이번에는 이리저리 파이어몽키 소스 보며 익혔던 간단한 구조에 대해 설명하려 합니다.

(딱, 제가 아는 만큼만 소개합니다.^^)


파이어몽키는 멀티 플랫폼을 지원하는 델파이 프레임웤 입니다.

어떻게 파이어몽키는 하나의 소스로 여러개의 플랫폼을 지원할까요?

여러개의 플랫폼을 지원하는 열쇠는 FMX.Platform.pas의 Platform: TPlatform에 있습니다.
(C:\Program Files (x86)\Embarcadero\RAD Studio\9.0\source\fmx에 소스파일이 있습니다.)


FMX.Platform의 TPlatform 클래스를 보시면 대부분(거의 다)이 추상메소드(virtual; abstract;) 입니다. 구현이 안되어 있다는 것이죠.

그리고 메소드명이 상당히 플랫폼에 종속적인 냄새를 풍깁니다.

CreateWindow(창을 생성하는 건 OS에서 해주겠죠.)
CreateTimer(타이머도 OS에서 이벤트를 발생해 줍니다.)
WaitMessage(메시지 처리도 OS와 연관이 있지요.) 등등 모두 플랫폼에 종족적인 메소드들입니다.

그리고 소스 디렉토리에 보시면 FMX.Platform.Win과 FMX.Platform.Mac 두개의 플랫폼 관련 유닛파일을 보실 수 있습니다.

즉, FMX.Platform의 TPlatform을 상속받은 클래스들이 구현되어 있겠죠.

맞습니다. 각 파일을 보시면 TPlatformWin과 TPlatformCocoa 두개의 클래스가 TPlatform을 상속받습니다.

물론 추상메소드들도 모두 구현되어 있습니다.

TPlatformWin은 Windows API를 이용하여 구현이 되어 있고, TPlatformCocoa는 OS X API로 구현이 되어 있습니다.


그럼 어떻게 Firemonkey는 플랫폼에 맞는 TPlatform 객체를 사용할까요?

위의 질문에 답하기 위해 먼저 프로젝트 소스파일을 들여다 보겠습니다.

Delphi XE2에서 <Firemonkey HD Application> 프로젝트를 생성하시면 아래와 같은 소스코드를 기본적으로 생성합니다.

program Project1;

uses
  FMX.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

위의 프로젝트 소스를 보면 도무지 참조하는 유닛이 FMX.Forms 밖에 없습니다.


그럼 FMX.Forms 유닛을 살펴보겠습니다.

주욱 보시면 VCL에서도 익숙한 TApplication, 앞으로 친하게 지내야될 TCommonCustomForm 등등의 클래스 등이 보입니다.

그리고 제일 아래에 보면 우리가 찾던 Platform 관련된 코드가 보입니다.

initialization
  RegisterFmxClasses([TApplication], [TApplication]);
  Screen := TScreen.Create(nil);
  Platform := PlatformClass.Create(nil);
  System.Classes.RegisterFindGlobalComponentProc(FindGlobalComponent);

finalization
  System.Classes.UnregisterFindGlobalComponentProc(FindGlobalComponent);
  FreeAndNil(Screen);
  // Platform global is freed in FMX.Types

위 코드의 4번째 줄을 보시면

Platform := PlatformClass.Create(nil);

이라고 PlatformClass를 이용해 TPlatform 객체를 생성해 할당합니다.


자 그럼 다시 PaltformClass를 따라가 봅니다.

FMX.Platform.pas에 PlatformClass 함수가 구현되어 있습니다.

function PlatformClass: TPlatformClass;
begin
  Result := ActualPlatformClass;
end;

그럼 ActualPlatformClass는 어디에 구현되어 있을까요?


예상하시는 분들도 있겠지만 위에서 설명한 Platform별 유닛(FMX.Platform.Win, FMX.Platform.Mac)에 구현되어 있습니다.

MSWINDOWS의 경우 TPlatformWin을 반환하고

MACOS의 경우 TPlatformCocoa를 반환합니다.

근데 의문이 듭니다. 어떻게 델파이는 2개중에 하나를 선택할까요?

그건 바로 Conditional define($IFDEF)을 이용합니다.

FMX.Platform의 구현부(implementation) 시작을 보시면 아래와 같은 코드를 보실 수 있습니다.

{$IFDEF IOS}
uses
  FMX.Platform.iOS, FMX.Canvas.iOS, FMX.Context.GLES;
{$ENDIF}

{$IFDEF MACOS}
uses
  FMX.Platform.Mac, FMX.Canvas.Mac, FMX.Context.Mac;
{$ENDIF}

{$IFDEF MSWINDOWS}
uses
  FMX.Platform.Win, FMX.Context.DX9;
{$ENDIF}

보시면 아시겠지만 IOS, MACOS, MSWINDOWS 별로 다른 유닛을 참조하도록 되어있습니다.

위의 3가지는 모두 Firemonkey가 지원하는 플랫폼입니다.

즉 Project Manager의 Target platform에서 Platform을 선택하면 그에 맞는 값(IOS, MACOS, MSWINDOWS)이 내부적으로 정의됩니다.

그래서 결국은 Target platform에 맞는 TPlatform 객체를 이용하여 OS에 맞는 API를 사용하여 실행됩니다.


좀더 설명을 하자면 위의 프로젝트 소스를 보시면 아래와 같이 Application을 실행(RUN)하는 코드를 볼 수 있습니다.

Application.Run;

Run 메소드를 살펴보면 아래와 같이 Platform에 따른 코드를 사용하여 Application을 실행합니다.

procedure TApplication.Run;
begin
  FRunning := True;
  AddExitProc(DoneApplication);
  try
    Platform.Run; // Platform의 Run 실행
  finally
    FRunning := False;
  end;
end;

// MSWINDOWS에 맞게 Application 실행
procedure TPlatformWin.Run;
begin
  { checking for canvas }
  if GlobalUseDirect2D then
    SetD2DDefault;

  Application.RealCreateForms;
  repeat
    try
      Application.HandleMessage;
    except
      Application.HandleException(Self);
    end;
  until Application.Terminated;
end;

// MACOS 맞게 Application 실행
procedure TPlatformCocoa.Run;
begin
  Application.RealCreateForms;
  CreateApplicationMenu;
  FRunLoopObserver := CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, True, 0, RunLoopObserverCallback, nil);
  CFRunLoopAddObserver(CFRunLoopGetCurrent, FRunLoopObserver, kCFRunLoopCommonModes);
  NSApp.Run;
end;

다른 메서드 또는 다른 유닛(FMX.Types 등)에서 Platform 전역변수를 이용하도록 구현되어 있습니다.


다시한번 정리하면, 

1, Project 소스에서 FMX.Forms 참조(uses)

2, FMS.Forms의 Initialization에서 FMS.Platform.pas 유닛의 PlatformClass를 통해 Platform 생성

3, FMS.Platform는 각 플랫폼 유닛의 ActualPlatformClass 함수 호출하여 Platform에 맞는 TPlatform 객체 형 방ㄴ환

4, 단, Platform에 맞는 유닛을 $IFDEF를 이용하여 결정($IFDEF는 Target platform 선택 시 정의)


이상으로 Firemonkey에서 Platform에 맞게 실행되는 구조를 두서없이 설명했습니다.

감사합니다. 끝~


PS - 다음에는 Firemonkey를 실질적으로 사용하기 위한 팁들을 몇개 소개하겠습니다.^^