1. Application domain에 대해

1.1 모듈화와 모듈화에 따른 application domain의 역할

 

프로젝트를 하다 보면 모듈화를 할 필요가 생긴다. 모듈화는 하나의 메인 응용 프로그램에 모든 기능이 있는 대신, 관련된 기능을 떼어내어 모듈을 만들어 필요한 시점에 동적으로 불러 사용할 수 있도록 만드는 것을 의미한다.

 

가령 Flex로 만든 블로그가 있다고 가정하면. 블로그에는 글쓰기, 리스트, 방명록, 관리, 메뉴 등이 존재할 것이다. 이러한 것이 하나의 SWF 파일에 모두 존재한다면 보여주지도 않을 방명록, 관리 부분까지 처음 프로그램 실행부터 전부 불러와야 하는 부담이 생긴다. 그래서 필요에 따라 동적으로 읽어서 사용할 수 있도록 글쓰기에 관련된 SWF, 리스트에 관련된 SWF 등으로 나눠서 만들어 놓으면 필요할 때 사용할 수 있으므로 더욱 효율적일 것이다.

 

이렇게 모듈화를 할 경우 블로그 메인 프로그램과 각 글쓰기 모듈 프로그램, 리스트 모듈 프로그램, 방명록 모듈 프로그램간에 통신을 하게 될 것이고, 때에 따라서는 메인 프로그램에서 정의한 자원을 모듈에 포함할 것 없이 재사용할 필요도 있을 것이다. 이렇게 하면 모듈에 메인 프로그램과 동일한 중복 코드를 가지고 있을 필요가 없어 용량이 줄어들 수 있기 때문이다. 그렇게 되면 자연스럽게 메인-모듈, 모듈-모듈간에 자원을 공유하는 일이 발생하게 된다.

 

이 문서는 모듈화를 하는 방법을 제시해준다기 보다는 모듈화를 위해 선행적으로 알아야 할 한가지를 언급하고자 한다. 그 중 하나가 바로 여기서 지금부터 다룰 application domain(응용 프로그램 영역)이다. application domain은 서로 다른 SWF 프로그램끼리 자원 공유를 위한 영역을 구분 지을 수 있도록 도와준다.

 

Namespace와의 비교

 

application domain에 대해서 이해하기 위해 Namespace와 비교해 보도록 하겠다.

 

Namespace는 이름, 용어 혹은 단어 항목들을 위한 문맥(context)을 제공하는 추상적인 컨테이너(container)라고 정의되어 있다. Namespace는 Java나 Flex, ActionScript3에서 사용하는 패키지(Package)와 거의 동일하다. Namespace는 프로그램 안에 같은 이름의 Class가 있을 때 구분해주는 역할을 한다. 가령 com.jidolstar.URLUtil과 flash.jidolstar.URLUtil 이 있다면 URLUtil로 이름은 같지만 Namespace는 다르므로 이 클래스는 이름만 같지 전혀 다른 클래스이다. 이렇듯 Namespace를 잘 활용하면 같은 이름의 클래스로 다른 동작을 하도록 만들 수 있고 또는 프로젝트 진행시 클래스 명이 충돌하는 경우도 방지할 수 있다.

 

그럼 application domain의 경우는 어떨까? Namespace는 단일 응용 프로그램에 같은 이름의 Class를 구분하기 위해 사용되는 반면 application domain은 여러 개의 응용 프로그램간에 같은 이름의 Class를 구분하기 위해 사용된다.

 

프로그램들이 하나의 application domain안에 포함되어 있다면 같은 이름을 가진 Class는 단 한 개만 존재가 가능하다. 가령 부모 프로그램의 Class 정의만 남고 자식 프로그램의 Class는 무시된다. 반면 프로그램들의 application domain이 서로 다르다면 같은 이름을 가진 Class라 할지라도 이들 Class는 서로 다른 application domain에 존재하는 것이므로 다른 것으로 해석된다.

 

 

1.2 Application domain 개념

 

아래는 application domain에 대해서 간단히 설명하고 있다.

application domain은 Loader의 load() 또는 loadBytes() 메쏘드를 이용해 ActionScript3.0으로 만들어진 SWF 파일을 불러들일 때만 사용된다.

application domain를 적용하기 위해 ApplicationDomain 클래스를 LoaderContext 클래스의 applicationDomain 속성에 대입하여 Loader의 load() 및 loadBytes() 메쏘드를 호출시 인자로 LoaderContext의 인스턴스를 넘겨주면 된다.

application domain은 동일한 보안 도메인(security domain)안에서 하나 이상의 SWF 파일 안에 제작된 ActionScript3.0 코드들을 목록화하여 관리한다. 다시 말하면 하나의 application domain안에서 여러 개의 SWF 파일에서 작성한 ActionScript3.0 코드를 관리할 수 있다. 또한 서로 다른 SWF 파일간에 다른 application domian을 안에 있다면 SWF 파일에 작성된 ActionScript3.0 코드는 중복되어지지 않는다. ActionScript3.0 코드는 Class 형태로 제작되며 결과적으로 application domain을 이용해 SWF 파일에 정의된 Class들이 서로 같은 영역에서 정의될 수 있고 또는 다른 영역에서 정의될 수도 있다. 또한 부모 SWF 파일에서 자식 SWF 파일에만 Class 정의 사용할 수 있도록 허용할 수 있다.

위처럼 application domain은 SWF 프로그램에 ActionScript 3.0으로 제작된 Class 정의를 원하는 영역(도메인)에 나누는 역할을 한다. 어도비 라이브 독(Adobe Livedocs)에 의하면 application domain에 대해서 아래 중요한 사실을 언급하고 있다.

 

  1. SWF 파일 안에 있는 모든 코드는 하나의 application domain 안에 존재되도록 정의된다. 메인 응용 프로그램(main application)의 application domain은 current domain이다. system domain current domain을 비롯하여 모든 application domain을 포함하기 때문에 모든 Flash Player 클래스를 가지고 있다.
  2. system domain을 제외한 모든 application domain은 그와 연결된 parent domain을 가진다. 메인 응용 프로그램의 application domain에 대한 parent domain은 system domain이다. 같은 application domain안에서 부모 프로그램과 자식 프로그램이 있다고 가정하자. 이때 부모 프로그램이 불러들인 자식 프로그램 안에 클래스들은 그 부모 안에 같은 이름의 클래스가 정의되어 있지 않을 때만 정의되고 사용할 수 있다. 또한 자식 프로그램의 클래스가 부모의 있는 클래스를 재정의(override) 할 수 없다.

 

같은 보안 도메인(scrutiny domain) 안에서 LoaderContext의 Application Domain 속성에 자신이 사용하고자 하는 Application Domain 속성을 아래 4가지 중 하나를 선택하면 되겠다.

 

  1. 부모의 application domain을 상속받은 application domain
    이것이 기본값이다.
    이 선택은 new ApplicationDomain(ApplicationDomain.currentDomain) 구문을 이용하는 것이다.
    이 방식은 자식의 application domain에서 부모에서 정의한 Class를 직접 사용할 수 있다. 가령 자식에서 ApplicationDomain.currentDomain.getDefinition(“부모에서 정의된 클래스명”)로 접근이 가능하다는 것을 의미한다. 반면 부모는 자신의 application domain에서 자식에서 정의된 Class에 접근이 불가능하다.
    이 방식의 장점은 자식에서 부모에 이미 정의된 Class와 이름이 같은 Class를 정의해도 오류가 나지 않는다. 자식은 해당 Class에 대한 부모의 정의를 상속받기만 하며, 부모와 충돌하는 클래스 정의는 무시된다.
  2. 부모 자신의 application domain
    ApplicationDomain.currentDomain 구문을 사용하는 것과 같다. 자식 SWF 파일을 불러오면 부모와 자식이 상대방에서 정의한 Class에 직접 접근이 가능하다. 서로 ApplicationDomain.currentDomain.getDefinition()을 이용해 접근이 가능하다. 단, 자식에서 부모와 같은 Class를 정의를 사용했다면 자식의 Class 정의는 무시된다.
  3. 시스템 application domain을 상속받은 application domain
    이는 new ApplicationDomain(null) 구문을 사용하는 것과 같다. 이렇게 하면 부모와 자식간에 정의된 Class는 서로 다른 application domain에 있기 때문에 자신의 application domain 안에서는 상대방에서 정의한 Class를 참고할 수 없게 된다. 부모와 자식간에 같은 이름의 Class가 있더라도 application domain이 다르므로 자식의 Class정의가 무시되는 경우는 없다.
  4. 임의의 application domain을 상속받은 application domain
    이 방법은 application domin 계층 구도가 복잡해질 수 있으므로 지양하는 방법이다. 자식 SWF을 자신의 security domain 안에 있는 임의의 application domain을 이용해 사용하는 것으로 가령, new ApplicationDomain(ApplicationDomain.currentDomain.parentDomain.parentDomain)을 사용하면 자식 SWF이 현재 application domain의 부모의 부모 domian을 상속받아 application domain을 만든다.

 

때때로 부모와 자식이 상대방의 application domain에 접근해야 하는 경우도 있다. 상대방이 application domain에 접근할 수 있는 경우 ApplicationDomain.currentDomain으로 상대방의 Class 정의를 가져올 수 있다. 또 다른 경우 부모가 자식 SWF를 로드가 완료했을 때, Loader 함수의 인스턴스가 loader라면 loader.contentLoaderInfo.applicationDomain.getDefinition()를 이용해서 자식의 application domian에 접근하여 자식에서 정의한 Class를 사용할 수 있다. 자식이 로드되는 방식을 알고 있는 경우 부모의 application domian에 접근할 수 있는데 기본적으로 ApplicationDomain.currentDomain.parentDomin을 이용하여 부모에서 정의한 Class에 접근이 가능하겠다.

 

 

2. ApplicationDomain Class 사용법에 따른 동작방식 해석

 

아래 <그림 1>은 단일 도메인(domain1.com)에 존재하는 다양한 SWF 프로그램(module1.swf, module3.swf, application2.swf)을 메인 SWF 프로그램(application1.swf)에서 Loader 클래스의 인스턴스를 통해 불러서 사용하는 모습을 묘사하고 있다. 이때 불러오는 SWF 프로그램들은 각자의 application domain에 등록되어 있다는 것을 알 수 있다.

 


<그림 1> Application domain과 SWF 프로그램 간의 관계

 

 

아래 예제는 [그림 1]의 Usage B의 경우와 같다.

 

package
{
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.*;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;

    public class ApplicationDomainExample extends Sprite
    {
        private var ldr:Loader;
        public function ApplicationDomainExample()
        {
            ldr = new Loader();
            var req:URLRequest = new URLRequest("Greeter.swf");
            var ldrContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
            ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
            ldr.load(req, ldrContext);    

        }

        private function completeHandler(event:Event):void
        {
            var ClassRef:Class = ApplicationDomain.currentDomain.getDefinition("Greeter") as Class;
            var myGreeter:* = new ClassRef();
            var message:String = myGreeter.welcome("Tommy");
            trace(message); // Hello, Tommy
        }
    }
}

 

위 예제를 간단하게 설명하면 메인 프로그램에서 모듈 SWF를 불러올 때 메인과 같은 application domain으로 설정한다.  모듈 SWF 로드를 완료하면 모듈 SWF 파일에 정의된 클래스(Greeter)를 이용해 인스턴스를 생성하고  그 클래스의 메서드에 접근하는 것을 확인할 수 있다.

 

이처럼 부모(메인)가 Loader 클래스를 이용해서 ActionScript 3.0으로 작성된 외부 SWF 파일을 자식으로 불러와 자식의 클래스와 메서드에 접근할 수 있다. Loader 클래스의 context 속성에 LoaderContext 객체를 넘겨주는데 LoaderContext에 바로 applicationDomain 매개변수를 넣을 수 있다. 위 예제에서는 메인 프로그램의 application domain인 current domain을 매개변수로 넘겨주었다.  자식의 SWF 파일에 부모와 동일한 application domain을 적용하겠다는 것을 의미한다. 자식을 부모의 application domain에 포함했기 때문에 부모는 자식에서 정의한 Greeter Class를 부모의 application domain 안에서 사용할 수 있게 된다.

 

위의 과정에 대해서 좀더 자세히 알아보자. <그림 1>에서 application domain 사용 예가 3가지 있다는 것을 알 수 있다. 아래 <그림 2>는 application domain 안에서 어떻게 Class가 공유되는지 살펴보기 위한 것이다.

 

<그림 2> application domain을 지정하는 3가지 방법

 

 

<그림 2>에서 클래스 명에 X표는 로드 후 정의에서 무시되는 클래스라는 것만 일단 기억하자. 지금부터 <그림 2>에서 표현하고 있는 application domain의 3가지 사용법에 대해서 언급하고 그에 따른 동작방식에 대해 이해해 보겠다.

 

 

2.1 ApplicationDomain 사용법A 새로운 application domain 사용

var loader:Loader = new Loader();
var request:URLRequest = new URLRequest("application2.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain = new ApplicationDomain();
loader.load(request,context);

 

위 코드는 메인 프로그램인 application1.swf 내에서 짜여진다. 이는 메인 SWF 프로그램과 전혀 다른 application domain 안에 존재하도록 만든다. 불러오는 컨텐츠의 이름이 module2.swf가 아닌 application2.swf로 표현한 이유는 서로 다른 application domain이기 때문에 독립적인 실행이 가능할 수 있다는 전제가 들어갈 수 있기 때문이다. application2.swf의 parent domain은 system domain이 된다.

 

<그림 3> 다른 application domain으로 외부 SWF를 불러오는 경우

 

<그림 3>에서 application1.swf와 application2.swf가 완전히 다른 application domain이기 때문에 서로의 각자 영역에서 정의된 com1::Class1과 com2::Class2는 상대방의 프로그램에 의해 영향을 받지 않는다. 같은 클래스 이름을 가지는 com1::Class의 경우 각자 다른 영역에 존재하므로 이름만 같은 다른 클래스로 이해할 수 있다.

 

var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

 

참고로 위에서 사용된 getDefinitionByName() 함수는 내부적으로 ApplicationDomain.currentDomain.getDefinition()을 호출하도록 만들어져 있다.

만약 application1.swf에서 위와 코드와 같이 접근하면 application2.swf를 불러온 상태에서도 application1.swf에 정의한 com1.Class1에 접근하게 된다. 반대로 application2.swf에서 같은 방식으로 접근할 때 application1.swf로 불려졌다 할지라도 서로 다른 application domain이므로 application2.swf의 com1.Class1에 접근하게 된다. 즉, 자기 자신의 application domain 영역에 정의된 Class를 사용하며 서로 간섭을 일으키지 않는다.

 

var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;

 

위의 접근 방식은 <그림 3>에서 application2.swf에서만 사용이 가능하다. com2.Class2는 application2.swf에서 정의되었고 application1.swf의 application domain이 application2.swf의 것과 다르기 때문에 application1.swf에서는 이 Class를 사용할 수 없다. 완전히 다른 application domain 영역이므로 unload시 바로 가비지 컬렉션 대상이 된다.

 

application1.swf에서 application2.swf의 com2.Class2에 접근하기 위해서는 application2.swf의 application domain의 참조를 알아야하는데 아래와 같이 참조가 가능하겠다.

var ClassRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("com2.Class2") as Class;

 

 

2.2 ApplicationDomain 사용법B 부모의 current domain을 사용

 

var loader:Loader = new Loader();
var request:URLRequest = new URLRequest("module1.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
loader.load(request,context);

 

위 코드에서 module1.swf는 부모와 같은 application domain을 사용하고 있다. 이것은 부모 프로그램의 자원을 공유하겠다는 의미와 동일하다.

 

<그림 4> 부모 프로그램과 동일한 application domain을 사용하는 경우

 

<그림 4>에서 볼 수 있듯이 동일한 application domain에 있기 때문에 자식인 module1.swf는 부모 application1.swf 내에 정의된 클래스를 사용할 수 있다. 만약 클래스가 부모 프로그램에 정의되어 있지 않는 경우 자식 프로그램에서 정의된 클래스가 부모 프로그램의 application domain에 등록되어 함께 공유하게 된다. 자식 프로그램과 부모 프로그램에 같은 이름의 Class가 존재한다면 자식 프로그램에서 정의한 Class는 무시된다.

 

var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

 

 

위와 같이 양쪽 프로그램에서 클래스 정의를 참조한다고 할 때 이미 application1.swf에 com1.Class1의 정의가 존재한 상태에서 module1.swf가 불려온 경우 module1.swf에 정의된 com1.Class1은 무시가 되어버린다. 결과적으로, 양쪽 프로그램에서 getDefinitionByName(“com1.Class1”)에 의해 얻어진 Class참조는 바로 application1.swf에 있는 com1.Class1이다.

 

var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;
var ClassRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("com2.Class2") as Class;

 

위 경우 두가지 모두 똑같은 com2.Class2의 클래스 정의를 얻는 방법이다. 같은 application domain에 있으므로 가능한 것이다. 단, application1.swf가 module1.swf를 로드하기 전에는 이와 같이 사용할 수 없다. 왜냐하면 application1.swf의 application domain에는 com2.Class2가 정의되어 있지 않기 때문이다. 하지만 module1.swf을 로드한 후에는 같은 application domain에 com2.Class2가 등록이 된다. 그러므로 application1.swf에서도 module1.swf에서 정의한 com2.Class2 클래스 정의를 이용할 수 있다.

 

이러한 방법은 RSLs(Runtime Shared Libraries)에서 사용되며 로드된 SWF는 RSL로 처리된다. 이 말은 RSL과 동일하게 가비지 컬렉션 대상이 될 수 없다는 것을 의미한다. 왜냐하면 메인 프로그램인 application1.swf의 application domain에 로드된 module1.swf의 클래스 정의가 이미 부모의 application domain 내에 등록되었기 때문에 자식인 module1.swf의 참조를 지우고 언로드(unload)하더라도 한번 등록된 Class 정의는 지워지지 않는다.

 

RSL과 다른 점은 RSL은 프로그램 실행시 특별한 코딩이 필요 없이 자동으로 로드하는 반면 이 방법은 필요한 시점에 동적 로드가 가능하다는 것이다. 자동으로 로드한 다는 것은 Flex 프레임워크가 자체 지원하고 있다는 것을 의미한다.

 

 

2.3 ApplicationDomain 사용법C 부모의 current domain를 상속받은 새로운 application domain 사용

 

var loader:Loader = new Loader();
var request:URLRequest = new URLRequest("module3.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain =
new ApplicationDomain(ApplicationDomain.currentDomain);
loader.load(request,context);

 

new ApplicationDomain()시 인자는 parentDomain이다. 즉, 새로운 application domain을 작성하되 부모의 current domain을 인자로 넘겨준다.

 

 

<그림 5> 부모의 application domain을 이용해 새로운 자식 application domain을 생성하는 경우

 

 

이렇게 되면 application domain 자체가 부모-자식관계가 된다. <그림 5>에서 보면 application domain이 걸쳐있는 것을 볼 수 있다.

 

var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

 

application1.swf가 module3.swf를 불러온 후 com1.Class1을 접근하면 자식 프로그램인 module3.swf에서 정의된 com1.Class1은 무시되어 버린다. 왜냐하면 application1.swf에 이미 com1.Class1이 정의되어 있고 module3.swf은 application1.swf의 같은 application domain을 참고하기 때문이다.

 

var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;

 

재미있는 사실은 이 경우이다.

 

application1.swf가 module3.swf를 불러왔지만 application1.swf에서 정의되지 않는 com2.Class2를 module3.swf에서 정의한 com2.Class2를 application1.swf의 application domain 에서는 이 클래스 정의를 참조할 수 없다. com2.Class2 정의는 자식 프로그램인 module3.swf의 application domain에서만 참조가 가능하다.

 

이 관계를 쉽게 이해하기 위해 Class를 상속받는 관계와 비교해 생각하면 된다. 자식은 부모에 있는 모든 자원을 사용할 수 있으나 부모는 자식에서 새로 정의된 자원은 사용할 수 없다라는 사실을 기억하면 이러한 관계가 쉽게 이해될 수 있을 것이다.

 

이 방법은 부모에서 정의된 클래스를 최대한 활용하면서 자식의 독립된 클래스 영역을 확보하는데 사용된다. 자식에서 정의된 클래스가 부모의 application domain에 포함되지 않으므로 부모가 자식의 SWF에 대한 참조를 포함하지 않는 경우, 자식에서 정의한 클래스는 모두 가비지 컬렉션 대상이 된다.

 

 

2.4 Application domain의 사용법에 따른 동작방식 표

 

지금까지 application domain을 적용하는 방법과 그에 따른 결과가 어떻게 되는지 알아보았다. 관련된 내용을 <표 1>에 정리해 보았다. 참고하길 바란다.

 

[표 1] Application domain 사용법에 따른 동작방식

 

3. Application Domain의 다양한 응용

앞에서 application domain의 사용법 및 동작방식에 대해서 알 수 있었다. 많은 경우 이 3가지 방법을 섞어가며 사용하게 될 것이다. 아래 몇 가지 예제를 통해서 어떻게 자원이 공유되는지 이해해보자.

 

<그림 6> application domain 응용 예시 도면 1

 

<그림 6>을 보자. 부모 프로그램인 application1.swf의 application domain안에서 module1.swf가 먼저 로드되고 module2.swf가 나중에 로드된다.

 

처음 로드될 때를 살펴보자. 앞에서 이미 언급한 것과 동일하게 module1.swf의 com1.Class1는 application1.swf에 이미 정의되어 있기 때문에 무시된다. 하지만 com2:Class2는 정의되어 있지 않았기 때문에 application domain에 등록되고 공유된다.

 

나중 로드될 때를 보자. module2.swf 에는 application1.swf에서 정의된 com1.Class1이 있으므로 무시된다. 또한 이미 module1.swf에 의해 application domain에 com2.Class2 등록되어 있으므로 module2.swf의 com2.Class2는 무시되어 버린다.


 

<그림 7> application domain 응용 예시 도면 2

 

<그림 7>의 경우 앞에서 언급한 것과 같은데 다른 점은 module이 그의 application domain안에서 다른 module을 불러들이고 있다.

 

메인 프로그램인 application1.swf에 이미 com1.Class1이 정의되어 있으므로 모듈에서 정의된 모든 com1.Class1은 무시된다. 두 모듈은 전부 application1.swf의 com1.Class1의 정의를 사용할 수 있다.

모두 로드가 완료된 경우에 application1.swf는 module4.swf의 com3.Class3를 사용할 수 없지만 module3.swf는 사용할 수 있다. 왜냐하면 application1.swf과 module4.swf의 application domain관계는 부모-자식 관계이기 때문이다.

 

<그림 8> application domain 응용 예시 도면 3

 

<그림 8>에서 Application domain은 앞에서 언급한 것과 동일하게 적용된다.

 

module5.swf에 다른 application domain을 가진 application2.swf가 로드되는 경우 메인 프로그램(application1.swf)과 module5.swf와 전혀 다른 영역을 가지므로 application2.swf안에 정의된 모든 클래스는 application1.swf와 module5.swf에서 정의된 클래스들과 별개가 된다.


이 글은 adobeflex.co.kr의 기술문서로 올린 글이다.

 

 

 

 

국내의 한 회사에서 Flex/Sliverlight관련 RIA 플랫폼 기반의 프레임워크를 오픈소스화 했다. 이름하야 OpenZET. Open 소스도 모자라 무려 한국어를 포함한 5개 국어(영어,프랑스어,스페인어,일본어)로 번역되었다. 회사자체에서 이렇게 오픈 소스를 한다는 것은 폐쇄적이인 국내 IT 시장에서는 매우 획기적이고 즐거운 소식이 아닐 수 없다. 더군다나 다국어 지원까지…

 

아주 많은 자료가 있는 것은 아니지만 현업에서 컴포넌트를 어떻게 만들고 응용하는가 알 수 있고 함께 정보를 공유할 수 있다는 점에서 매우 가치있게 평가한다.

 

다른 많은 RIA관련 회사들도 이렇게 오픈 마인드를 가지고 국내 RIA 시장에 활기를 넣었으면 하는 바람을 가진다.

 

공식홈페이지 : http://openzet.org/

 

Flex SDK 3.3 이 공식배포되었군요.

여전히 한글 문제는 해결된바 없습니다.

스타플(http://starpl.com)에서 제공하는 위젯중에 아래와 같은 “내별정보위젯”이 있다.

 


 

마우스를 올리면 내 별에 방문하거나 별받기를 할 수 있다.

 

스타플의 방문페이지와 별받기페이지에 접속시키기 위해 ExternalInterface.call()과 navigateToURL()을 아래와 같이 사용했다.

 

 

새창띄워 방문하기

ExternalInterface.call("window.open", 경로, "_blank", ""); 

 

방문하기

var request:URLRequest = new URLRequest( 경로 );
flash.net.navigateToURL( request, "_parent");

 

하지만 이러한 것들도 flash 컨텐츠를 웹페이지에 추가할때 쓰는 <object>태그와 <embed> 태그의 allowScriptAccess 속성과 allowNetworking 속성을 어떻게 지정하느냐에 따라 사용가능여부가 달라진다.

 

가령 이들 속성은 아래와 같은 방법으로 쓰여질 수 있다.

 

<object width="425" height="344">
<param name="allowScriptAccess" value="never" />
<param name="allowNetworking" value="internal" />
<param name="movie" value="swf경로"></param>
<param name="allowFullScreen" value="true"></param>
<embed src="swf경로"
    invokeURLs="false"
    autostart="false"
    allowScriptAccess="never"
    allowNetworking="internal"
    type="application/x-shockwave-flash"
    allowfullscreen="true"
    width="425"
    height="344"></embed>
</object>

 

각 속성에 대해서 자세히 설명하자면 다음과 같다.

 

 

allowScriptAccess 속성

 

이 속성은 SWF의 ExternalInterface을 통해 Javascript와 통신가능여부를 결정지어주는 속성이다. 이 속성은 다음 3가지 값중 하나가 된다.

  • never : SWF에서 ExternalInterface를 이용해 Javascript통신을 할 수 없다.
  • always : SWF에서 ExternalInterface를 이용해 Javascript통신이 언제든지 가능하다.
  • sameDomain : 같은 도메인에 있는 SWF일때 Javascript 통신이 가능하다.(기본)

 

allowNetworking 속성 

 

이 속성은 SWF에서 네트워크 기능 API 사용을 제한하는 옵션이다. 다음과 같은 3가지 값중 하나를 가진다.

  • all : 모든 네트워크 API 허용(기본)
  • internal : 브라우저 네비게시션이나 브라우저 상호작용 API 사용 제한
  • none : 모든 네크워크 API 제한

internal인 경우 제한 API

  • navigateToURL()
  • fscommand()
  • ExternalInterface.call()

none인 경우 제한 API (internal 포함)

  • sendToURL()
  • FileReference.download()
  • FileReference.upload()
  • Loader.load()
  • LocalConnection.connect()
  • LocalConnection.send()
  • NetConnection.connect()
  • NetStream.play()
  • Security.loadPolicyFile()
  • SharedObject.getLocal()
  • SharedObject.getRemote()
  • Socket.connect()
  • Sound.load()
  • URLLoader.load()
  • URLStream.load()
  • XMLSocket.connect()

 

이제 네이버에서 스타플 위젯을 통해 스타플에 방문할 수 없는 이유를 보자.

 

<EMBED
    invokeURLs="false"
    autostart="false"
    allowScriptAccess="never"
    allowNetworking="internal"
    src="경로"
    width="54"
    height="96"
    type="application/x-shockwave-flash"
    allowfullscreen="true">
</EMBED>

 

몇몇 분들이 이런 제한정책때문에 네이버 블로그에 있기를 꺼려하시는 분들이 계시다. 물론 보안상 어떻게 할 수 없다고는 할 수 있지만…


참고글

□ 개요

   o Adobe Flash Player에서 입력 검증 오류, 버퍼오버플로, 클릭재킹 등의 다수 취약점이
      발견되어 보안 업데이트가 발표됨[1]
   o 낮은 버전의 Adobe Flash Player 사용으로 악성코드 감염 등의 사고가 발생할 수 있음으로
      사용자의 주의 및 최신버전 설치가 권고됨


□ 해당프로그램

   o Adobe Flash Player 10.0.12.36 이하 버전이 설치된 Linux 제외 모든 운영체제
   o Adobe Flash Player 10.0.12.36 이하 버전 - 내부 네트워크 배포용
   o Adobe Flash Player 10.0.15.3 이하 버전이 설치된 Linux 운영체제
   o Adobe AIR 1.5 버전
   o Adobe Flash CS4 Professional에서 사용되는 Adobe Flash Player 10.0.22.87 이전버전
   o Adobe Flash CS3 Professional에서 사용되는 Adobe Flash Player / Debug Player 9.0.159.0
      이전버전
   o Adobe Flex 3에서 사용되는 Adobe Flash Debug Player 10.0.22.87 이전버전

 

□ 설명

   o Adobe Flash Player 취약점에 대한 보안 업데이트(총 5건)가 아래와 같이 발표됨
     ① Adobe Flash Player가 개체를 삭제하는 과정에서 개체에 할당된 메모리를 적절히 해제하지
         않아 발생하는 원격코드 실행 가능한 버퍼오버플로 취약점[2](CVE-2009-0520)
     ② Adobe Flash Player가 부적절하게 입력 검증하는 과정에서 발생하는 서비스 거부 취약점[3]
        (CVE-2009-0519)
     ③ Adobe Flash Player의 설정 관리자로 인한 클릭재킹 취약점[4](CVE-2009-0114)
     ④ Windows 운영체제에서 마우스 포인터를 출력할 때 발생하는 클릭재킹 취약점[5]
        (CVE-2009-0522)
     ⑤ Linux 운영체제에서 권한 상승으로 연계될 수 있는 정보 노출 취약점[6](CVE-2009-0521)
   o 상기 취약점을 이용하여 공격자는 SWF 파일을 조작하여 피해자의 PC에 악성 스크립트를
      실행시키거나 악성코드 감염 등과 같은 악성행위를 할 수 있음

 

□ 해결방안

   o 다음 표와 같은 버전의 Adobe 제품 사용자는 취약하지 않은 버전으로 업데이트 할 것을 권고함
     ※ 일부 Adobe 제품은 구글 툴바 추가 설치가 기본으로 설정되어 있으니 설치 전 확인 필요

 

   o Adobe Flash 컨텐츠를 사용하는 웹서버 관리자는 아래와 같이 웹페이지를 수정하여
      이용자들이 최신버전 Adobe Flash Player를 설치하도록 ActiveX 버전 수정 필요

< object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#
version=10,0,22,87" width="921" height="109" >

   o 향후에도 유사 취약점 노출로 인한 피해예방을 위해 아래와 같이 안전한 브라우징 습관을
     준수해야 함
      - 신뢰되지 않은 웹사이트의 플래시 파일 다운로드 주의
      - 의심되는 이메일에 포함된 플래시 파일 링크를 방문하지 않음
      - 개인방화벽과 백신제품 사용 등

 

□ 용어 정리

   o 클릭재킹 : 사용자가 웹 페이지를 클릭 할 때 자신도 모르게 의도하지 않은 기능을 실행
      하여 공격자가 컴퓨터에 대한 제어권 혹은 중요 정보를 획득하는 취약점[12]
   o Adobe Flash CS3/CS4 Professional : Adobe Flash 애니메이션 제작을 위한 도구[13]
   o Adobe AIR(Adobe Integrated Runtime) : 이미 입증된 웹 기술을 브라우저 외부 데스크톱
      에서 실행될 수 있도록 도와주는 프로그램 제작 도구[14]
   o Adobe Flex : Adobe Flash Player 또는 Adobe AIR에서 사용할 인터넷 어플리케이션
      개발 도구[15]

 

□ 기타 문의사항

   o 한국정보보호진흥원 인터넷침해사고대응지원센터 : 국번없이 118

 

□ 참고사이트

   [1] http://www.adobe.com/support/security/bulletins/apsb09-01.html
   [2] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0520
   [3] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0519
   [4] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0114
   [5] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0522
   [6] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0521
   [7] http://www.adobe.com/go/getflash
   [8] http://www.adobe.com/products/players/fpsh_distribution1.html
   [9] http://get.adobe.com/kr/air/
   [10] http://www.adobe.com/support/flashplayer/downloads.html#fp10
   [11] http://www.adobe.com/support/flashplayer/downloads.html#fp9
   [12] http://en.wikipedia.org/wiki/Clickjacking
   [13] http://www.adobe.com/kr/products/flash/
   [14] http://www.adobe.com/products/air/
   [15] http://www.adobeflex.co.kr/aboutflex/flex.html

 

출처 : http://www.krcert.net/secureNoticeView.do?seq=-1&num=316

오늘 반가운 소식이 있네요. 한국 Adobe RIA 공식사이트(http://adoberia.co.kr) 홈페이지가 개편되었습니다. 디자인도 예전보다 깔끔해지고 훨씬 공식 페이지 다운 면모를 갖추게 되었습니다.

 

Adobe RIA 공식 홈페이지는 Flex/Flash/AIR와 같은 다양한 Adobe RIA기술의 중심지라고 말할 수 있으며 Adobe RIA에 관심있는 분이라면 하루에 한번 이상은 접속할 필요가 있는 홈페이지라고 생각합니다.

 

 

 

홈페이지 겉모양만 개편되었다면 아무말 안하는데 Flex/AIR개발자들에게 매우 반가운 프로그램이 소개되었군요. 바로 Flex3 한글 매뉴얼입니다. 한글 매뉴얼은 AIR로 제작되어 배포하고 있으며 (주)가호컨설팅(대표 이동호, 현 ACC)에서 많은 수고가 있었다는 것으로 알고 있습니다.

 

 

지금까지 Flex/AIR 한글화 문서 사이트(http://flexdocs.kr)를 운영해왔는데 Flex 2를 넘어가지 못한 점이 아쉬었던 때에 늦게나마 이런 좋은 툴이 제공되었다는데 매우 감사할 따름입니다. Flex 3 한글 매뉴얼이 나왔으니 이제 이 사이트도 문닫을 때가 되었군요.

 

 

오 Adobe RIA 사이트에 직접 방문할 수 있고 각종 유명 커뮤니티 및 블로그 링크도 제공하네요.  엇 제 홈페이지도…

 

 

이 프로그램의 아쉬운 점은 너무 늦게 오픈했다는 점입니다. 그리고 Help가 제공되지 않았다는 것이네요. 올해쯤에는 Flex 4가 나올것이고 Flex 3와 많은 부분 차이가 있다는 점이 있다는 것을 볼 때… 넘 아쉽네요. 그래도 있는게 어디입니까? 다 읽어봐야겠습니다. 감사합니다. 이동호 대표님 ^^

 

Adobe RIA 공식사이트에서는 오픈이래로 지속적으로 기술문서가 올라오고 있습니다. RSS 피드도 제공하고 있으니 구독하시면 훨씬 좋겠습니다.

 

 

또한 BLOG도 따로 제공하고 있어서 RIA 종사자 인터뷰, 각종 최신정보들이 올라오고 있습니다. 블로그도 RSS피드를 제공하고 있으니 구독하시면 좋겠네요.

 

 

2009년 부터 기존 Flex Champion이 Adobe Community Champion(이하 ACC)으로 명칭이 바뀌면서 그 의미가 확대되었습니다. 기존에는 Flex만 다뤘다면 이제는 Flex, Flash, AIR 등 Adobe RIA 기술 전반을 다루고 있습니다. ACC는 단순히 실력이 있고 유명한 사람을 지칭하지 않습니다.  ACC가 아니더라도 충분히 좋은 실력자들이 많으니깐요.  정확히 말해 ACC는 Adobe RIA 기술의 전도사들입니다. 지속적으로 기술문서 작성등을 통해 현업에서 알아낸 노하우 및 새로운 기술에 대해서 소개하며 각종 오프라인 모임에 힘을 실어줄 사람들입니다.

 

저도 ACC 일원으로서 Adobe RIA 기술의 전도사 역할을 열심히 해내도록 노력하겠습니다.

 

 

 

 

Adobe Flex를 접할때 어려워하는 부분중에 하나가 Event입니다.
깊숙히 이해하려고 할때 잘 이해가 안되는 부분이 많은게 이벤트죠.

기본에 충실해야한다!


프로젝트(http://starpl.com)를 하면서 많이 느꼈습니다.


처음 Flex 접했을때 가장 어려움을 겪었던 것은 기본이 탄탄하지 못한 것으로부터 였습니다.


그래서 Flex와 함께 ActionScript 3.0의 기본 문법 및 대략적 내용을 이해해야한다고 생각합니다.


단순한 문법뿐 아니라 분석/설계에도 관심을 가지고 공부해서 명확하고 깨끗한 프로그램을 만들 수 있도록 노력해야겠구요.

 

아래는 Event에 대한 질문을 걸어놓고 스스로 답변까지 해두었습니다.
이것으로 Event 전부를 설명할 수 없지만 몇가지 궁금한 사항을 이해하는데는 도움이 될겁니다.


그냥 읽고 지식만 얻어가지 마시고 없는 질문이 있다면 스스로 질문/답변을 댓글로 달아주시면 더욱 고맙겠습니다. 잘못된 내용이나 추가해야한다는 것도 포함합니다.

 

1. ActionScript 3.0 이벤트(Event)

1.1 ActionScript 2.0과 3.0에서 이벤트가 바뀐점은?

  • ActionScript 2.0에서는 이벤트 리스너를 추가하는 경우 상황에 따라 addListener() 또는 addEventListener()를 사용하지만 ActionScript 3.0에서는 항상 addEventListener()를 사용합니다.
  • ActionScript 2.0에는 이벤트 흐름이 없으므로 이벤트를 브로드캐스팅하는 객체에서만 addListener() 메서드를 호출할 수 있지만 ActionScript 3.0에서는 이벤트 흐름에 포함된 모든 객체에서 addEventListener() 메서드를 호출할 수 있습니다.
  • ActionScript 2.0에서는 함수, 메서드 또는 객체를 이벤트 리스너로 사용할 수 있지만 ActionScript 3.0에서는 함수 또는 메서드만 이벤트 리스너로 사용할 수 있습니다.
  • on(event) 구문은 이제 ActionScript 3.0에서 지원되지 않으므로 ActionScript 이벤트 코드를 무비 클립에 첨부할 수 없습니다. 이벤트 리스너를 추가할 때는 addEventListener()만 사용할 수 있습니다.

1.2 Event를 사용해야하는 이유? 2가지 이상

  • 객체간 강한 결합 해결 : 애플리케이션 설계시 Callback함수를 사용하는 등의 강한결합을 이용하는 경우 유지보수 및 확장이 어려워지는 경우가 크다. 이벤트는 객체간 느슨한 결합을 해주는데 큰 힘을 발휘할 수 있다.
  • 작업에 대한 일관성 없는 응답 : 동기적 방식과 비동기적 방식에 대한 응답에 대한 처리를 이벤트를 사용하지 않는 경우 서로 다른 방법을 선택해서 사용해야한다. 이벤트는 1가지 방법으로 응답처리를 가능하게 해준다.


2. Event의 구성요소 3가지는 무엇이고 의미를 설명하라.

  • 이벤트 객체(Event Object)
    flash.net.Event의 객체나 이를 확장한 이벤트 객체를 의미한다. 이벤트 객체에는 이벤트에 대한 정보가 들어가있다. 이벤트의 종류(Type)과 이벤트를 발생한 객체(target)에 대한 정보가 포함된다.
  • 이벤트 발생자(Event Dispatcher)
    이벤트를 발생(broadcast)하는 객체이다. 이벤트 객체에 target으로 이벤트 발생자 정보가 담긴다. flash.net.EventDispatcher를 확장한 모든 객체는 이벤트 발생자가 될 수 있다. EventDispatcher의 dispatchEvent() 메소드에 이벤트 객체를 담아 실행하면 이벤트가 발생하게 된다.
  • 이벤트 청취자(Event Listener)
    일종의 메소드 또는 함수이다. 발생한 이벤트를 듣는 역할을 한다. EventDispatcher클래스를 확장한 클래스의 객체에서 addEventListener()을 이용해 이벤트 청취자를 등록할 수 있다. addEventListener()을 이용하면 청취할 이벤트의 종류(type)을 결정할 수 있으며 청취할 이벤트 흐름(capture,target,bubbling) 및 우선순위등을 선택할 수 있다.

3. Event Listener를 등록/삭제하는 방법을 기술하라.

  • 등록
    EventDispatcher의 addEventListener() 메소드를 이용한다.
    예)
    var button:Button = new Button();
    button.addEventListener( MouseEvent.CLICK, onClick, false );
    public function onClick( event:MouseEvent )
    {
      trace( event.type, event.target  );
    }
  • 삭제
    EventDispathcer의 removeEventListener() 메소드를 이용한다.
    예)
    button.removeEventListener( MouseEvent.CLICK, onClick, false );
    3개의 인자가 addEventListener()를 사용할때와 같아야 정상적으로 이벤트 청취자 등록이 삭제된다.
    참고로 맨 마지막 인자는 useCapture이다.

4. Event 전파단계의 종류 3가지가 무엇이며 각각에 대한 설명

  • 이벤트 전파단계의 종류 : 캡쳐(capture) 단계, 타겟(target) 단계, 버블(bubble)단계
  • 이벤트 전파 순서 : 캡쳐->타겟->버블
  • 캡쳐 단계
    루트(root) 디스플레이 객체서부터 이벤트를 발생한 객체(target)까지 디스플레이 객체 리스트(display Object List)를 따라 검색해나가는 단계이다. 즉, 부모->자식으로 이벤트를 전파한다. 이 캡쳐 단계에 이벤트를 청취하기 위해서는 addEventListener()의 3번째 인자인 useCapture가 반드시 true로 되어야 한다. false로 되어 있다면 캡쳐단계가 아닌 타겟과 버블 단계에서 이벤트 청취를 한다.
    캡쳐단계 청취하는 목적은 다양할 수 있다. 가령 중간에 이벤트 전파를 중지시킬때 필요할 수 있다. 이해가 안될 수 있지만 실제 프로젝트를 하면 이런 경우가 다분하다.
    사용 예 )
    button.addEventListener( MouseEvent.CLICK, onClick, true );
  • 타겟 단계
    갭쳐 단계 이후 타겟단계에 도달한다. 타겟 단계는 이벤트를 발생시킨 객체까지 도달했을때 이뤄진다.
  • 버블 단계
    타겟 단계 이후 다시 루트(root) 디스플레이 객체로 이벤트가 전파되는 단계이다. 버블버블 물망울이 수면위로 다시 올라가는 것을 연상하면 되겠다. 캡쳐와 반대로 자식->부모로 이벤트가 전파되는 단계이다.
    이벤트가 버블 단계로 전파되기 위한 조건은 이벤트 송출시 Event의 생성자 인자값중 bubble값을 true로 설정되어 있어야 한다. MouseEvent같은 경우 이 bubble값이 true로 설정되어 있다.
    타겟단계와 버블단계에 이벤트 청취자를 등록하기 위해 addEventListener()의 3번째 인자 useCapture가 false로 되어야 한다. useCapture를 설정하지 않으면 기본값이 false이다.
  • 이벤트 객체가 전파되는 기능이 굳이 필요한 이유
    부모-자식1-자식2….-자식100 의 형태로 디스플레이 객체 리스트가 형성되어있다고 가정하자 자식50에서 자식 100을 마우스 클릭했을때 이벤트를 받고 싶다고 하자. 자식50은 자식100의 존재유무를 모른다(알 수 있지만 알게되면 강한결합이 일어난다.) 이런 경우 이벤트 객체가 전파되는 기능은 매우 유용하게 쓰일 수 있다. 캡쳐나 버블 단계에서 자식50은 자식100에서 발생된 이벤트를 받을 수 있다.
  • flash.display.DisplayObject 클래스는 EventDispatcher를 확장해서 만들어졌다는 것에 주목하자.

5. 4번의 Event 전파는 어떤 환경에서 가능한가?

  • 반드시 디스플레이 객체 리스트(Display Object List) 상에 존재하는 디스플레이 객체(Display Object)여야 이벤트 전파가 가능하다. 그 외의 이벤트 전파는 타겟 단계밖에 없다. 가령, HTTPService나 URLLoader에서 이벤트가 발생하는 경우 이들은 디스플레이 객체가 아니다.
  • 이벤트 전파의 조건
    1. 디스플레이 객체여야 한다.(flash.display.DisplayObject를 확장한 클래스의 객체)
    2. 디스플레이 객체는 디스플레이 객체 리스트에 존재해야 한다. 즉, stage로 부터 addChild되어 있어야 한다.

6. 이벤트 전파시 target과 currentTarget의 차이점은 무엇인가? 예를 들어 설명하라.

  • target은 이벤트를 발생한 객체이고 currentTarget은 이벤트를 청취하는 객체이다.
  • 예시)
    var p:Sprite = new Sprite(); //parent
    var c:Sprite = new Sprite(); //child
    addChild( p );
    p.addChild( c ); //c가 p의 자식인 것에 주목
    p.addEventListener( MouseEvent.CLICK, onClick ); //이벤트 청취를 p가 하는 것에 주목!
    public function onClick( event:MouseEvent )
    {
    trace( event.target, event.currentTarget ); //c를 클릭했을 경우 c, p가 나오고 p를 클릭했을 경우 p,p가 나온다.
    }

7. Event 우선순위에 대해서 간단한 예제를 들어 설명하라.

  • 이벤트 우선순위는 구체적으로 이벤트 청취 우선순위라고 할 수 있다.
  • 이벤트 청취 우선순위는 addEventListener()의 4번째 인자인 priority를 이용한다. 이 값이 클 수록 청취 우선순위가 높아지며 addEventListener()의 등록 순서에 상관없이 우선순위에 따라 이벤트 리스너가 호출된다.
  • 이벤트 청취 우선순위가 유효한 조건
    1. 같은 객체에서 청취한다.
    2. 같은 이벤트 종류(type)을 청취한다.
    3. 같은 useCapure 값을 가진다.
  • ex)
    a.addEventListener(MouseEvent.CLICK, aHandler, false, 3);
    a.addEventListener(MouseEvent.CLICK, bHandler, false, 1);
    a.addEventListener(MouseEvent.CLICK, cHandler, false, 100);

    a객체에 MouseEvent.CLICK 이벤트가 발생시 등록순서에 상관없이 priority값이 가장큰 cHandler가 먼저 호출되고 다음으로 aHandler, bHandler가 호출된다.

8. Event 전파를 중단하는 메소드 2개가 무엇이며 간단한 예제를 들어 각각의 차이점을 설명하라.

  • Event 클래스의 stopPropogation()와 stopImmediatePropogation() 메소드이다.
  • 차이점
    - stopPropogation()
      이벤트 전파를 중단하되 같은 노드의 이벤트를 청취까지는 허락한다.
    - stopImmediatePropogation()
      이벤트 전파를 즉시 중단한다.
  • 예제
    back이 배경 canvas이고 button1이 back에 포함된 button일때
    back.addEventListener(MouseEvent.CLICK, event1Handler);
    back.button.addEventListener(MouseEvent.CLICK, event2Handler);
    back.button.addEventListener(MouseEvent.CLICK, event3Handler);
    back.button.addEventListener(MouseEvent.MOUSE_OUT, outHandler);private function event1Handler(e:MouseEvent):void
    {
           trace(’back’);
    }
    private function event2Handler(e:MouseEvent):void
    {
           trace(’button’);
           e.stopPropagation();
           e.stopImmediatePropagation();
    }
    private function event3Handler(e:MouseEvent):void
    {
           trace(’button2′);
    }
    private function event1Handler(e:MouseEvent):void
    {
           trace(’out’);
    }

    e.stopPropagation(); e.stopImmediatePropagation(); 두가지 다 없을경우
            button
            button2
            back
            out
    e.stopPropagation(); 만 실행
            button
            button2
            out
    e.stopImmediatePropagation(); 만 실행
            button
            out

9. addEventListener의  useCapture 인자는 무엇을 의미하는가? 간단한 예제를 들어 설명하라.

  • 이벤트 전파를 청취할때 캡처단계를 청취할지 타겟 및 버블 단계를 청취할지 결정하는 인자이다.
  • 모든 전파 단계에서 청취하기 위해서는 useCapture가 다른 2개의 addEventListener()를 사용해야한다.
  • 예제 생략

10. Sprite, Shape와 같은 클래스에서 어떻게 하면 이벤트를 송출할 수 있는가?

  • dispatchEvent() 메소드를 호출하면 된다.

11. Sprite, Shape와 같이 이벤트 송출할 수 있는 클래스는 근본적으로 어떤 클래스를 상속받았기 때문에 가능한가?

  • Sprite와 Shape는 궁극적으로 DisplayObject를 확장해서 만들어졌으며 DisplayObject는 EventDispatcher를 확장했다. 그러므로 Sprite와 Shape와 같은 클래스는 EventDispatcher의 메소드를 전부 사용할 수있다. 이중에 dispatchEvent()도 사용할 수 있다.

12. IEventDispatcher는 무엇이며 어떤 경우에 이것을 사용해야할까?

  • EventDispatcher의 인터페이스이다.
  • IEventDispatcher를 활용하는 것은 다중상속이 불가능한 AS3에서 유용하다.
    가령, 어떤 클래스가 ProxyObject를 확장해서 만들어져야한다. 그런데 이 클래스는 이벤트도 발생시켜야 하므로 EventDispatcher클래스도 확장해야한다. ActionScript 3.0은 다중상속을 지원하지 않는다.(다중상속은 이점도 있지만 단점도 있다.) 그러므로 이러한 경우에는 주가 되는 클래스인 ProxyObject를 is-a 확장을 하고 EventDispatcher를 has-a 확장을 감행한다음 만들어진 클래스가 EventDispatcher의 기능을 수행한다는 것을 명시적으로 외부에 알려주고 내부적으로 구현시켜주기 위해 IEventDispatcher 인터페이스를 implements 해준다. 이렇게 하면 원하는 기능인 ProxyObject와 EventDispatcher의 기능을 전부 수행하면서도 다중상속의 문제를 극복할 수 있다.
  • ex)
    public class MyProxyObject extends ProxyObject implements IEventDispatcher
    {
      var evnetDispatcher:EventDispatcher = new EventDispatcher();
     
      //내부적으로 IEventDispatcher에 선언한 메소드를 구현한다.  가령…
      public function dispatchEvent( event:Event ):Boolean
      {
         return  eventDispatcher.dispatchEvent( event );
      }
    }

13. preventDefault()와 stopPropagation(), stopImmediatePropagation()의 근본적 차이점은 무엇인가?


  • preventDefault()는 기본 동작(dafault behaviors)를 취소하는데 쓰이는 메소드인 반면 stopPropagation(), stopImmediatePropagation()는 이벤트 전파에만 영향을 준다.

 

  • preventDefault()가 사용 가능한지는 isDefaultPrevented() 메소드로 알 수 있다.

14. preventDefault()를 호출해서 실행을 가능하게 하기 위한 조건은 무엇인가?

  • 이벤트 객체를 생성시 cancelable속성이 true로 설정되어 있어야 한다.

15. 커스텀 이벤트를 만들어야하는 어떤 경우이며 어떻게 만드는가?

  • 커스텀 이벤트는 기본 이벤트 클래스(flash.events.Event) 또는 그 상위 이벤트 클래스를 확장하는 경우를 의미한다.
  • 이벤트를 확장해야하는 경우는 이벤트 송출시에 필요한 정보를 담아내기 위함이다.
  • 만드는 방법
    class MyEvent extends Event
    {
      public static const MY_EVENT:String = “myevent”;
      public var value:String;
      public function MyEvent( type:String, value:String ):void
      {
         super( type, false, false );
         this.value = value;
      }
      overide public function clone():Event
      {
        return new MyEvent( type, value );
      }
    }

16. 커스텀 이벤트가 bubbling이 가능하기 위해 어떤 조건이 필요한가?

  • Event의 생성자의 2번째 인자인 bubble값을 true로 설정한다.

17. 커스텀 컴포넌트 만들때 clone()함수를 override해야하는 이유는 무엇인가?

  • clone()메소드는 기존과 신규 properties들을 clone 내로 설정해 이벤트 객체의 복사본을 돌려준다.
    clone() 메소드를 Event 클래스 확장시 오버라이드 해야하는 이유는 이벤트를 다시 재전달해야하는 경우 묵시적으로 복사해서 재전달할 필요가 있을때 필요하기 때문이다.

 

  • 참고로 toString()도 확장해서 사용하는 것이 좋다. 이벤트에 대한 명세서가 될 수 있기 때문이다.

18. 약참조에 대해서 GC(가비지 콜렉션)과 관련되어 설명하라.

  • addEventListener()함수로 이벤트 청취자를 등록할때 5번째 인자로 useWeakReference 인자가 있다. 이것을 true로 설정하면 이벤트 청취자가 이벤트 청취를 약참조를 하겠다는 의미이다. 반대로 false(기본)이면 강참조이다.
  • 약참조, 강참조를 설명하기 전에 가비지 콜렉션(GC, Garbage Collection)에 대해서 먼저 알아야한다.
    만들어졌던 객체가 더이상 쓸모가 없어졌을때 GC라고 한다. 쓸모가 없어졌다는 것은 참조의 고리가 끊어졌다는 것을 의미한다. 메모리에 상주해 있지만 조만간 메모리에서 없애야할 대상인 것이다. Flash Player는 GC로 선정된 객체들을 메모리에서 삭제하는 시점을 내부적으로 구현해놓고 있다.(많은 분들이 Flash Player GC처리를 못한다고 하지만 실제로는 문제없이 잘하고 있다. 단지 효율적으로 GC처리를 하는지는 의문이지만…)
    Flash Player GC 대상을 찾아내기 위한 방법은 2가지가 있다. 그것은 레퍼런스 카운팅(reference counting)과 마크 앤 스윕(Mark and Sweep)이다. 레퍼런스 카운팅은 생성된 객체가 참조할때마다 참조 카운트를 1씩 더해준다. 또 참조에서 끊어지는 경우 1씩 감소시켜준다. 만약 레퍼런스 카운팅 값이 0이되는 경우 바로 GC 대상이 되는 것이다. 마크 앤 스윕은 레퍼런스 카운팅 만으로 찾을 수 없는 GC대상을 찾아준다. 가령, 두개 이상의 객체가 서로 다른 객체를 참조하고 있다고 가정한다.(순환참조) 그럼 이들 레퍼런스 카운팅 값은 1 이상이다. 그러므로 레퍼런스 카운팅 방법으로는 GC대상을 만들 수 없다. 마크 앤 스윕은  이들 객체가 최상위 부모와 연결되어 있는지 검사하는 방법이다. 부모로 부터 꼬리에 꼬리를 물고 객체가 참조되어 있는 것인지 조사해나가는 방법으로 GC대상을 검색한다.
    레퍼런스 카운팅과 마크앤 스윕 방법으로 찾아낸 GC대상은 메모리에 상주되지만 Flash Player의 GC 삭제방법 로직에 의해 어느 순간 삭제된다.(이 시점은 프로그래머가 어찌할 수 없다. AIR 또는 Flash Player Debug버전의 경우에만 System.gc()를 이용해 강제적으로 GC를 메모리에서 삭제할 수 있긴 하다.)
  • addEventListener()의 useWeakReference를 true로 설정하면 약참조가 되어 레퍼런스 카운팅을 증가시키지 않는다. 강참조의 경우 이벤트 대상(target)의 참조를 지웠다고 해도 addEventListener로 등록한 청취자 메소드가 GC대상이 아니라면 이벤트 대상의 레퍼런스 카운팅 값은 0이 될 수 없다. 결국 프로그래머의 뜻과 다르게 메모리를 낭비하는 일을 초래할 수 있다. 물론 이벤트 대상이 사라지는 시점에 removeEventListener() 사용하면 되겠지만 때로는 그 시점을 모르는 경우도 발생한다. 이러한 경우 useWeakReference를 true로 설정해서 메모리 낭비를 없앨 수 있겠다.
  • Flex에서 List 계열의 컴포넌트를 살펴보면 dataProvider()에 ArrayCollection 객체를 받을때 ArrayCollection의 변화를 살펴보기 위해 addEventListener()를 사용한다. 그런데 ArrayCollection 객체는 언제 어떻게 될지 List 계열 컴포넌트는 알 수 없을 것이다. 이때 유용하게 사용하는 것이 바로 useWeakReference=true로 설정해서 필요없어진 ArrayCollection이 메모리 남는 것을 방지할 수 있다. 참고 : http://blog.jidolstar.com/375


19. 이벤트가 전파되는 중간에 전파를 멈추게 하기 위해 stopPropagation()을 사용하지 않고removeEventListener() 를 사용하면 안되는 이유를 설명하라. (힌트, ActionScript 3.0의 Native 메커니즘에 입각한다. http://bixworld.egloos.com/2149717)

 

  • 이벤트 송출을 시작하면 flash player는 이벤트가 송출될 디스플레이 객체 리스트를 전부 조사해 미리 이벤트 객체를 복사해두기 때문이다. 이미 전파할 대상이 모두 정해져있고 전파가 시행중인 경우에는 중간에 removeEventLisener를 사용하더라도 일단 송출된 이벤트는 끝까지 전파된다. 다음 시점 이벤트 송출시에는 removeEventLisener()가 적용되어 있는 상태가 된다.
 

 

 

20. willTrigger()와 hasEventListener()의 의미를 설명하고와 두 메소드의 차이점을 보여줄 수 있는 대한 예를 들어보자.

 

 

 

  • 공통점
    hasEventListener()이나 willTrigger() 모두 이벤트 청취자가 있는지 검사한다.
  • 차이점
    hasEventListener() : 지정한 객체에(만) 이벤트 청취자가 있는지 조사한다. 이벤트 흐름과는 무관하다.
    willTrigger() : 지정한 객체를 포함하는 디스플레이 객체 리스트에 해당 이벤트가 청취자가 있는지 조사한다. 그러므로 이벤트 흐름과 관련 있다.
  • 예제
    var button:Sprite = new Sprite();
    addChild( button );
    this.addEventListener( MouseEvent.CLICK, onClick );
    trace( button.willTrigger( MouseEvent.CLICK ) ); //true
    trace( button.hasEventListener( MouseEvent.CLICK ) ); //false ->왜 false가 되는가 아는게 중요!
    trace( this.willTrigger( MouseEvent.CLICK ) ); //true
    trace( this.hasEventListener( MouseEvent.CLICK ) ); //true
  • 이 두개의 함수를 잘 활용하면 쓸데없이 이벤트를 송출하는 경우를 방지할 수 있다.

21. ActionScript 3.0 이벤트에 대해 참고할 만한 문헌 및 자료를 조사하자.

Adobe AIR로 만들어진 상용프로그램 중 하나인 EarthBrowser를 소개한다.

 

EarthBrawser는 이름 그대로 지구에서 일어나는 주요 환경 변화, 사건등을 3D로 묘사한 세어웨어 AIR 프로그램이다. AIR로 만들어졌기 때문에 Mac OSX  및 Linux와 MS Window에 설치가 가능하다.

 

Adobe AIR Marketplace에 가면 프로그램을 설치할 수 있다. 단 유료라서 때문에 7일간만 Demo버전으로 이용할 수 있다.

 

아래는 EarthBrowser 공식 홈페이지 화면이다. http://www.earthbrowser.com/로 방문하면 된다.


홈페이지에서는 Free버전도 다운로드 받을 수 있지만 기능이 제한되어 있다.

 

EarthBrowser는 Flash Player 기반인 AIR 설치형 프로그램이기 때문에 다른 여느 3D 프로그램처럼 빠르진 않다. 하지만 Flash Player인데도 사용할 만한 속도와 각종 여러 정보를 한눈에 볼 수 있게 한 것이 매력이다.

 

아래 그림은 EarthBrowser를 실행한 화면이다. Google Earth와 같이 마우스 스크롤로 확대 축소가 가능하며 드래그를 하면 지구가 돌아간다. 아마도 Papervision3DAway3D 같은 프로그램으로 3D를 구현했을 것 같다.

 

 

 

아래는 축소한 모습이다.

 

 

아래는 확대한 모습이다. 한국의 서울까지 확대해 봤다.

백두산으로 가봤다. 화산표시가 되어 있고 그 표시를 누르니 AIR의 웹브라우져인 Webkit이 뜨면서 백두산에 대한 정보가 나오고 있다. 중국명칭인 장백산이 아닌 백두산이라고 나와서 그나마 다행이다… 설명에 중국-한국 경계라고 해서 좀 그렇지만..

 

EarthBrowser는 현재 2년간 자유롭게 업그레이드 할 수 있다는 조건에 $29.95로 가격이 책정되어 있다. Domo버전은 7일밖에 사용하지 못한다. 구입후 Code를 입력하면 사용할 수 있게 된다. Demo버전을 사용하면 주기적으로 이 창이 뜬다.

 

EarthBrowser는 주요위성의 위치, 각국 지역 날씨(온도,강수량,대기압,습도,풍향,바다온도,오존), 화산정보, 구름의 시각화, 오로라, 태풍정보, 나라별 국기, 지진정보,

아래는 대전의 1주일 날씨를 보여준다.

 

 

 

아래 그림은 나라별 국기 모습이다.

 

 

한국 국기를 누르니 아래처럼 한국 소개 브라우져가 나온다. 헉!! 일본해?!

 

각 지역별 온도분포를 보여주고 있다. 놀랍다…

 

 

아래는 강수량을 보여준다. 오늘 비왔는데… 잘 보여주는 듯 싶다.

 

각지역 웹캠으로 그 지역의 모습을 보여주기도 한다. 와우~~

 

우리나라 최초 우주인 이소연씨가 타고 왔던 국제우주정거장(ISS)의 위치도 잡힌다. 옆에 GALEX 위성도 보인다. 정말 멋지다!

 

정리하며…

예전에 Adobe Flex로 만들어진 각종 애플리케이션을 보면서 감탄을 했는데… 오늘 Adobe AIR로 만들어진 EarthBrowser를 보면서 놀라움을 금치 못했다. 그래픽 가속을 이용하지 않기 때문에 실행시 약간 버벅거림이 있는 것은 사실이지만 EarthBrowser는 Adobe RIA기술이 어디까지 확장할 수 있는지 잘 보여줄 수 있는 프로그램이라 생각한다. 단순히 Flash, AIR는 3D 가속이 안되기 때문에 느리다는 편견을 버리고 Cross Browse, Cross OS의 강점을 잘 살려서 그만의 독특한 프로그램을 만들어 내어 상용화까지 이르게 하는 창의적 생각을 하는 것이 중요하다고 생각했다.

 

개인적으로 천문프로그래밍에 관심이 많다. 스타플(http://starpl.com), 천문노트(http://astronote.org)를 개발하고 운영하면서 지금도 천문프로그래밍에 대한 좋은 아이디어를 찾고 있다.  Adobe AIR로 개발해서 Adobe AIR Marketplace 에 프로그램을 올리는 것도 나쁠 것 같지 않다.

 

Adobe AIR Marketplace가 나와서 말인데, 난 오늘 처음 이런게 있는지 알았다. App Store만 있다고 생각했는데 Adobe에서도 이런게 있었다니… 비록 App Store에 올라온 애플리케이션과 비교할 때, 40:1 수준으로 많이 부족하다. 정책 및 상용화에 대한 근본적인 차이가 있었을 것이라 생각한다. Adobe가 AIR를 개발하면서 어떻게 하면 상용화로 이끌 수 있을지 많이 고민했다면 App Store 수준까지는 아니더라도 지금 엄청나게 많은 AIR 애플리케이션이 쏟아졌을텐데… 안타깝다.

 

한국에서도 EarthBrowser와 같은 훌륭한 AIR 애플리케이션이 많이 나오길 희망한다.

Adobe AIR?

Adobe AIR는 여러 운영체제(Mac OSX, Windows, Linux)에서도 설치가 가능한 설치형 프로그램이다.  Cross Browser의 대명사인 Flash Player 기술이 브라우져에 종속의 한계를 넘어 Cross OS 설치형 프로그램을 가능하게한 것이 바로 Adobe AIR이다. AIR는  HTML/Javascript, Flash, Flex등으로 개발할 수 있다. 이러한 개발 방법의 다양화는 요즘 대세인 듯 싶다.  Adobe Alchemy로 C/C++코드를 Adobe Flash/Flex/AIR에서 사용할 수 있게 했듯이 말이다. ^^

참고사이트

웹서핑중에 재미있는 사이트가 있어서 소개한다.

 

사이트 이름은 wonderfl이다. 부제로 build flash online 로 되어 있다.


부제만 보면 알 수 있듯이 사이트는 ActionScript 3.0 코드를 온라인상에 입력하면 이 코드를 서버에서 컴파일해주고 바로 그 결과를 보여주는 사이트이다.

 

이름도 참 독특하다. wonderful을 보방해서 wonderfl로 했다.  뒤에 fl은 Adobe Flash의 Fl인듯 싶다.

 

이런 컨셉과 비슷한 사이트는 몇번 본 것 같은데 ActionScript 3.0을 컴파일해서 서비스하는 것은 처음본다.


서버에서는 Flex의 mxmlc를 이용해 컴파일하는 것으로 보인다.

각종 filterbitmap 처리를 통한 Effect, pixel manipulation, pixel bender 그리고 papervision3D등의 예제가 여러개 올라와 있다.

 

본인도 가입해서 한번 만들어보았다. Papervision3D가 지원되기 때문에 그에 알맞은 코드를 찾아다가 아래 그림과 같이 코딩해보았다. 훌륭하다. ^^ 또한 Flash Player 10 기반으로도 만들 수 있기 때문에 Matrix3D, Vector3D, Vector와 같은 클래스나 연산자를 사용하여 코드를 테스트 해볼 수 있다.

 

샘플코드는 Clockmaker 블로그에서 얻어왔다.

 

아쉬운 점은 블로그에 결과물을 붙힐 수 없다는 것이다(적어도 내 블로그에는…). 하지만 링크를 통해 만든 결과물을 보여줄 수 있다. 아래 링크로 확인하기 바란다.

이 사이트가 어떤 사업성을 가질지는 의문(그냥 개인이 심심해서 만든 것 같음)이지만 매우 재미있는 있고 독특한 사이트라고 느꼈다.

우야꼬의 “Flash CS4로 만드는 Adobe AIR 1.5″ 책 저자인 우야꼬 님이 Stratus에 대한 번역을 해주셨네요.

번역글 : http://wooyaggo.tistory.com/search/stratus

 

 

Stratus는 Flash Player간 RTMFP(Real-Time Media Flow Protocal)을 이용해 Peer-to-Peer 통신을 가능하게 해주는 신기술입니다. 최소한의 서버 통신을 이용해 Flash Player간에 직접적 통신이 가능하도록 하여 서버부하를 줄여줄 수 있다는데 큰 매력을 느끼게 합니다. TCP가 아닌 UDP 기반으로 통신하기 때문에 훨씬 속도가 빠르겠군요.

 

이제 Flash Player 간 인터넷전화, 메신저, 화상채팅등이 구현이 되겠군요.
지속적으로 관심을 가지고 지켜봐야할 기술임에 틀림없겠습니다.

빵집 개발자 양병규님께서 이 글의 이전 글인 Adobe Alchemy 속도 테스트에 대해 이런 답변을 달아주셨다.


컴파일러에 대해서 조금 관심을 가져보시면 알 수 있을텐데요…
음… 뭐랄까…
그 최적화라는 것이 불필요한 일은 안하고 같은 기능이라면 더 빠른 기능으로 대치하고.. 그런일을 하는 것이 컴파일러의 최적화인데요..
제가 보기에는
for( i=0; i < 1000000; i++ )
{
result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}
이 부분이.. 최적화를 하면 루프를 1000000번 돌지 않고 한방에 끝낼것같습니다. 최적화알고리즘중에서 루프문안에서의 내용이 반복을 해도 변화가 없는 경우에는 루프를 한방에 끝내도록 하는 알고리즘이 있습니다. 이 예가 그런 예일것 같은데요..

암튼.. ActionScript에는 그런 최적화가 없는데 이미 오랜 역사동안 만들어진 C/C++언어의 최적화 기술이 적용되면 분명히 나은 결과를 보일 것 으로 예상됩니다. ^^;(물론 코딩하는 사람의 실력이 좋을 수록 최적화의 효과가 떨어지겠지만)

그리고..
저는 아무리 봐도 C/C++로 코딩한 것이 결국 몽땅 ActionScript로 만들어지는 것이 맞는 것 같습니다. SWF에는 ActionScript 이 외에 다른 실행코드는 존재하지 않습니다. 최종적으로 만들어진 결과가 *.SWF(*.SWC)이지 않습니까? SWF File Format Specification Version 10 문서 278페이지를 다 훑어봐도 ActionScript 이외에는 실행코드가 없습니다. 머.. DLL을 임포트한다거나 별도의 실행코드용 외부 라이브러리가 있다거나… 그런건 전혀 없습니다.

malloc 함수 같은 경우의 예를 들어보면 ActionScript에는 malloc과 동일한 역할을 하는 어셈블명령은 없습니다. 하지만 ActionInitArray와 같이 긴 변수를 사용할 수 있는 명령으로 ‘구현’은 할 수 있을겁니다. 아마도 그런 방법으로 구현되지 않았나싶습니다.

어쨋든 Alchemy는 C/C++을 100% ActionScript로 변환해 주는게 맞고.. 함수사용, 변수사용, 최적화, 수학함수의 구현, 각종 기초 알고리즘등 기초적인 부분에서 훨씬 우월하기 때문에 좋은 효과를 나타내고 있는 것으로 생각됩니다.

시간이 나면 Alchemy에서 만들어진 SWF 파일을 분석해 보면 아주 좋은 공부가 될것같습니다. ^^;

양병규님의 의견중 최적화에 대한 것을 다시 살펴보자면 -O3 옵션으로 인해 최적화 알고리즘이 적용되어 for문이 없어지고 result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3); 만 남는다는 것을 지적하고 ActionScript 3.0에는 이런 최적화 기능이 없기 때문에 실제로 돌려보면 C코드는 1번만 수행하는 반면 ActionScript 3.0에서는 원래대로 1백만번 루프를 돈다는 것이다.

 

또한 여러 상황에 비추어볼때 Alchemy는 C/C++ 코드를 100% ActionScript 3.0으로 변경하는게 맞다고 말씀해 주었다.

 

이 의견을 읽고 맞는 말인 것 같다고 생각해서 코드를 다음과 같이 수정했다.

 

1. C 코드 최적화를 방지해서 다시 속도 테스트

speedtest.c

#include <math.h>
#include <stdlib.h>
#include "AS3.h"

static AS3_Val c_speed_test(void* self, AS3_Val args)
{
    int i;
    double result = 0;
    for( i=0; i < 10000000; i++ )
    {
        result += sin( i ) * cos( i );
    }
    return AS3_Number(result);
}

int main()
{
    AS3_Val method = AS3_Function( NULL, c_speed_test );
    AS3_Val result = AS3_Object( "c_speed_test: AS3ValType", method );
    AS3_Release( method );
    AS3_LibInit( result );
    return 0;
}

 

AlchemySpeedTest.as

package {
    import cmodule.speedtest.CLibInit;

    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    public class AlchemySpeedTest extends Sprite
    {
        public function AlchemySpeedTest()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            var loader:CLibInit = new CLibInit();
            var lib:Object = loader.init();
            var startTime:Number;

            startTime = getTimer();
            var cResult:String = "c returned value : " + lib.c_speed_test(null) + " delay time(ms) : " + (getTimer()-startTime);

            startTime = getTimer();
            var asResult:String = "as3 returned value : " + as3_speed_test() + " delay time(ms) : " + (getTimer()-startTime);

            var textField:TextField = new TextField();
            textField.text = cResult + "\n" + asResult;
            textField.width = 500;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.defaultTextFormat = new TextFormat( null, 12 );
            addChild(textField);

        }
        public function as3_speed_test():Number
        {
            var i:int;
            var result:Number = 0;
            for (i = 0; i < 10000000; i++)
            {
                result += Math.sin( i ) + Math.cos( i );
            }
            return result;
        }
    }
}

 

이 코드에서 달라진 것은 gcc로 c를 컴파일 할 때 -O3와 같은 최적화 알고리즘 적용을 하더라도 전과 같이 for문이 삭제되는 것을 방지하도록 했다.

 

이전 코드

for( i=0; i < 1000000; i++ )
{
        result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}

 

새로 바꾼 코드

for( i=0; i < 10000000; i++ )
{
        result += sin( i ) * cos( i );
}

새로 바꾼 코드는 sin(), cos()함수에 계속 변화되는 값을 넣어주었고 그 결과가 누적되도록 했다. 이로써 최적화를 하더라도 for문이 적용되지 않는 경우는 없을 것이다.

컴파일은 아래와 같이 한다.

gcc speedtest.c –03 –Wall –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

결과는 다음과 같다.

c returned value : 0.24755567269650028 delay time(ms) : 3425
as3 returned value : 2.873882594355213 delay time(ms) : 3184

예상대로 gcc –O3 로 인해 최적화가 적용되었던 것이다. 양병규 님의 말에 힘이 실리는 듯하다.

 

2. ActionScript 3.0 Native Vector3D, Matrix3D와 C++의 Vector3D, Matrix3D 속도테스트

 

Flash Player 10부터 추가된 ActionScript 3.0 API중에 Vector3D, Matrix3D가 있다. 이 클래스들은 3D 표현을 위한 다양한 기능을 제공하고 있다. Native 코드이므로 소스코드는 직접 볼 수 없다.

 

ActionScript 3.0의 Vector3D와 Matrix3D 대신 C++로 Vector3D, Matrix3D를 만들어서 서로 수행속도를 비교해보도록 하겠다.

 

아래는 Vector3D와 Matrix3D C++코드, 그리고 이들을 속도 테스트 할 수 있는 C++코드, ActionScript 3.0과 C++과 속도 테스트 할 수 있도록 제작한 ActionScript 3.0 코드가 나열되어 있다.

 

Vector3D.h

#ifndef VECTOR3D_H_
#define VECTOR3D_H_

#include "AS3.h"

class Vector3D {

public :
  Vector3D();
  Vector3D(double x, double y, double z);
  Vector3D(const AS3_Val& as3Vector);
  virtual ~Vector3D();

  double dot(const Vector3D& v) const;
  Vector3D cross(const Vector3D& v) const;
  double modulus() const;
  Vector3D normalise() const;

  void setX(double x);
  void setY(double y);
  void setZ(double z);
  double getX() const;
  double getY() const;
  double getZ() const;

private :
  double _x;
  double _y;
  double _z;

};

#endif /*VECTOR3D_H_*/

 

Matrix3D.h

#ifndef MATRIX3D_H_
#define MATRIX3D_H_

#include "Vector3D.h"

class Matrix3D {

public :
  Matrix3D();
  virtual ~Matrix3D();

  void setRotationX(double degrees);
  void setRotationY(double degrees);
  void setRotationZ(double degrees);

  void setIdentity();

  Vector3D transformVector(const Vector3D& vector) const;

private :
  double _M00;
  double _M01;
  double _M02;
  double _M10;
  double _M11;
  double _M12;
  double _M20;
  double _M21;
  double _M22;

};

#endif /*MATRIX3D_H_*/

 

Vector3D.cpp

#include "Vector3D.h"
#include <cmath>

Vector3D::Vector3D() :
  _x(0),
  _y(0),
  _z(0) {
}

Vector3D::Vector3D(const AS3_Val& as3Vector) {
  AS3_ObjectValue(as3Vector, "x:DoubleType, y:DoubleType, z:DoubleType", &_x, &_y, &_z);
}

Vector3D::Vector3D(double x, double y, double z) :
  _x(x),
  _y(y),
  _z(z) {
}

Vector3D::~Vector3D() {
}

double Vector3D::dot(const Vector3D& v) const {
  return v._x*_x + v._y*_y + v._z*_z;
}

Vector3D Vector3D::cross(const Vector3D& v) const {

  Vector3D result;
  result._x = _y*v._z - _z*v._y;
  result._y = _z*v._x - _x*v._z;
  result._z = _x*v._y - _y*v._x;
  return result;
}

double Vector3D::modulus() const {
  return std::sqrt(_x*_x + _y*_y + _z*_z);
}

Vector3D Vector3D::normalise() const {
  double mod = modulus();
  return Vector3D(_x/mod, _y/mod, _z/mod);
}

void Vector3D::setX(double x) {
  _x = x;
}

void Vector3D::setY(double y) {
  _y = y;
}

void Vector3D::setZ(double z) {
  _z = z;
}

double Vector3D::getX() const {
  return _x;
}

double Vector3D::getY() const {
  return _y;
}

double Vector3D::getZ() const {
  return _z;
}

Matrix3D.cpp

#include "Matrix3D.h"
#include <cmath>

Matrix3D::Matrix3D() :
  _M00(1),
  _M01(0),
  _M02(0),
  _M10(0),
  _M11(1),
  _M12(0),
  _M20(0),
  _M21(0),
  _M22(1) {
}

Matrix3D::~Matrix3D() {
}

void Matrix3D::setIdentity() {
  _M00 = 1;
  _M01 = 0;
  _M02 = 0;
  _M10 = 0;
  _M11 = 1;
  _M12 = 0;
  _M20 = 0;
  _M21 = 0;
  _M22 = 1;
}

void Matrix3D::setRotationX(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M11 = cos(radians);
  _M12 = -sin(radians);
  _M21 = sin(radians);
  _M22 = cos(radians);
}

void Matrix3D::setRotationY(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M00 = cos(radians);
  _M02 = sin(radians);
  _M20 = -sin(radians);
  _M22 = cos(radians);
}

void Matrix3D::setRotationZ(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M00 = cos(radians);
  _M01 = -sin(radians);
  _M10 = sin(radians);
  _M11 = cos(radians);
}

Vector3D Matrix3D::transformVector(const Vector3D& vector) const {
  Vector3D result;
  result.setX(_M00*vector.getX() + _M01*vector.getY() + _M02*vector.getZ());
  result.setY(_M10*vector.getX() + _M11*vector.getY() + _M12*vector.getZ());
  result.setZ(_M20*vector.getX() + _M21*vector.getY() + _M22*vector.getZ());
  return result;
}

alchemy_speed_test.cpp

#include "AS3.h"
#include "Vector3D.h"
#include "Matrix3D.h"

AS3_Val speedTest1(void* self, AS3_Val args) {

  // Declare AS3 variables
  AS3_Val as3Vector1;
  AS3_Val as3Vector2;
  // Extract variables from arguments array
  AS3_ArrayValue(args, "AS3ValType, AS3ValType", &as3Vector1, &as3Vector2);
  // Create native C++ objects with AS3 parameters
  Vector3D vector1(as3Vector1);
  Vector3D vector2(as3Vector2);
  Vector3D vector3;
  // Speed test : calculate cross products and normalise
  for (int i = 0; i < 1000000; i++) {
    vector3 = vector1.cross(vector2);
    vector3 = vector3.normalise();
    vector1 = vector2;
    vector2 = vector3;
  }

  // Obtain a class descriptor for the AS3 Vector3D class
  AS3_Val vector3DClass = AS3_NSGet(AS3_String("flash.geom"), AS3_String("Vector3D"));
  AS3_Val params = AS3_Array("");
  // Construct a new AS3 Vector3D object with empty parameters
  AS3_Val result = AS3_New(vector3DClass, params);
  // Set the x, y and z properties of the AS3 Vector3D object, casting as appropriate
  AS3_Set(result, AS3_String("x"), AS3_Number(vector3.getX()));
  AS3_Set(result, AS3_String("y"), AS3_Number(vector3.getY()));
  AS3_Set(result, AS3_String("z"), AS3_Number(vector3.getZ()));

  // Release what’s no longer needed
  AS3_Release(params);
  AS3_Release(vector3DClass);
  // return the AS3 Vector
  return result;
}

AS3_Val speedTest2(void* self, AS3_Val args) {

  // Declare AS3 variable
  AS3_Val as3Vector;
  // Extract variables from arguments array
  AS3_ArrayValue(args, "AS3ValType", &as3Vector);

  // Create native C++ object with AS3 parameters
  Vector3D vector(as3Vector);
  Vector3D copy = vector;
  Matrix3D rotationX;
  Matrix3D rotationY;
  Matrix3D rotationZ;
  // Speed test : calculate rotation matrices and transform vector
  for (int i = 0; i < 1000; i++) {
    vector = copy;
    for (double ang = 0; ang < 180; ang++) {
      rotationX.setRotationX(ang);
      rotationY.setRotationY(ang);
      rotationZ.setRotationZ(ang);
      vector = rotationX.transformVector(vector);
      vector = rotationY.transformVector(vector);
      vector = rotationZ.transformVector(vector);
    }
  }

  // Obtain a class descriptor for the AS3 Vector3D class
  AS3_Val vector3DClass = AS3_NSGet(AS3_String("flash.geom"), AS3_String("Vector3D"));
  AS3_Val params = AS3_Array("");
  // Construct a new AS3 Vector3D object with empty parameters
  AS3_Val result = AS3_New(vector3DClass, params);
  // Set the x, y and z properties of the AS3 Vector3D object, casting as appropriate
  AS3_Set(result, AS3_String("x"), AS3_Number(vector.getX()));
  AS3_Set(result, AS3_String("y"), AS3_Number(vector.getY()));
  AS3_Set(result, AS3_String("z"), AS3_Number(vector.getZ()));

  // Release what’s no longer needed
  AS3_Release(params);
  AS3_Release(vector3DClass);
  // return the AS3 Vector
  return result;
}

/**
* Main entry point for Alchemy compiler. Declares all functions available
* through the Alchemy bridge.
*/
int main() {
  // Declare all methods exposed to AS3 typed as Function instances
  AS3_Val speedTest1Method = AS3_Function(NULL, speedTest1);
  AS3_Val speedTest2Method = AS3_Function(NULL, speedTest2);

  // Construct an object that contains references to all the functions
  AS3_Val result = AS3_Object("speedTest1:AS3ValType, speedTest2:AS3ValType", speedTest1Method, speedTest2Method);

  // Release what’s no longer needed
  AS3_Release(speedTest1Method);
  AS3_Release(speedTest2Method);

  // Notify the bridge of what has been created — THIS DOES NOT RETURN!
  AS3_LibInit(result);

  // Should never get here!
  return 0;
}

AlchemySpeedTest.as

package {

  import cmodule.alchemy_speed_test.CLibInit;
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.geom.Matrix3D;
  import flash.geom.Vector3D;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.utils.getTimer;

  public class AlchemySpeedTest extends Sprite {

    private var vectorUtils:Object;

    public function AlchemySpeedTest() {

      // Set up the stage
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;

      // Create the Alchemy bridge to C++ methods
      var loader:CLibInit = new CLibInit;
      vectorUtils = loader.init();

      // Create a text field          
      var timerText1:TextField = new TextField();
      var timerText2:TextField = new TextField();
      var timerText3:TextField = new TextField();
      var timerText4:TextField = new TextField();
      timerText1.autoSize = TextFieldAutoSize.LEFT;
      timerText2.autoSize = TextFieldAutoSize.LEFT;
      timerText3.autoSize = TextFieldAutoSize.LEFT;
      timerText4.autoSize = TextFieldAutoSize.LEFT;
      timerText1.y = 0;
      timerText2.y = 30;
      timerText3.y = 60;
      timerText4.y = 90;
      addChild(timerText1);
      addChild(timerText2);
      addChild(timerText3);
      addChild(timerText4);
      var time0:int;
      var totalTime1:int;
      var totalTime2:int;
      var totalTime3:int;
      var totalTime4:int;

      // Perform the speed test
      time0 = getTimer();
      var vector1:Vector3D = speedTest1();
      totalTime1 = getTimer() - time0;

      time0 = getTimer();
      var vector2:Vector3D = speedTest2();
      totalTime2 = getTimer() - time0;

      time0 = getTimer();
      var vector3:Vector3D = nativeSpeedTest1();
      totalTime3 = getTimer() - time0;

      time0 = getTimer();
      var vector4:Vector3D = nativeSpeedTest2();
      totalTime4 = getTimer() - time0;
      // Display elapsed time and final vector
      timerText1.text = "1. Alchemy Time taken = " + totalTime1 + " vector = (" + vector1.x + ", " + vector1.y + ", " + vector1.z + ")";
      timerText2.text = "1. Native  Time taken = " + totalTime3 + " vector = (" + vector3.x + ", " + vector3.y + ", " + vector3.z + ")";
      timerText3.text = "2. Alchemy Time taken = " + totalTime2 + " vector = (" + vector2.x + ", " + vector2.y + ", " + vector2.z + ")";
      timerText4.text = "2. Native  Time taken = " + totalTime4 + " vector = (" + vector4.x + ", " + vector4.y + ", " + vector4.z + ")";
    }

    /**
     * Speed test using C++ to iteratively calculate the cross products of two vectors
     */
    private function speedTest1():Vector3D {
      var vector1:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      var vector2:Vector3D = new Vector3D(0.987, 0.654, 0.321);

      return vectorUtils.speedTest1(vector1, vector2);
    }

    /**
     * Speed test using C++ to iteratively calculate rotation matrices and apply these to a vector
     */
    private function speedTest2():Vector3D {
      var vector:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      return vectorUtils.speedTest2(vector);    
    }

    /**
     * Speed test using AS3 to iteratively calculate the cross products of two vectors
     */
    private function nativeSpeedTest1():Vector3D {
      var vector1:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      var vector2:Vector3D = new Vector3D(0.987, 0.654, 0.321);
      var vector3:Vector3D;
      var time0:int = getTimer()
      for (var i:int = 0; i < 1000000; i++) {
        vector3 = vector1.crossProduct(vector2);
        vector3.normalize();
        vector1 = vector2;
        vector2 = vector3;
      }
      return vector3;
    }

    /**
     * Speed test using AS3 to iteratively calculate rotation matrices and apply these to a vector
     */
    private function nativeSpeedTest2():Vector3D {
      var vector:Vector3D = new Vector3D(0.123, 0.456, 0.789);

      var copy:Vector3D = vector.clone();

      var rotationX:Matrix3D = new Matrix3D();
      var rotationY:Matrix3D = new Matrix3D();
      var rotationZ:Matrix3D = new Matrix3D();

      for (var i:int = 0; i < 1000; i++) {
        vector = copy.clone();
        for (var ang:Number = 0; ang < 180; ang++) {
          rotationX.identity();
          rotationX.appendRotation(ang, Vector3D.X_AXIS);
          rotationY.identity();
          rotationY.appendRotation(ang, Vector3D.Y_AXIS);
          rotationZ.identity();
          rotationZ.appendRotation(ang, Vector3D.Z_AXIS);
          vector = rotationX.transformVector(vector);
          vector = rotationY.transformVector(vector);
          vector = rotationZ.transformVector(vector);
        }
      }
      return vector;
    }

  }
}

위 코드에서 alchemy_speed_test.cpp에 speedTest1(), speedTest2()가 만들어져 있고 AlchemySpeedTest.as 안에는 C++ 코드를 호출할 수 있는 함수와 ActionScript 3.0 Native Vector3D와 Matrix3D 속도를 테스트해보는 nativeSpeedTest1(), nativeSpeedTest2() 함수가 정의되어 있다.

 

speedTest1()과 nativeSpeedTest1()은 2개의 Vector간에 dot product 연산과 Normalisation을 1백만번 반복해서 결과를 반환하다. 반면 speedTest2()와 nativeSpeedTest2()는 x,y,z축 회전에 대한 Matrix를 3개 만들어 Vector를 회전을 반복한 다음 결과를 반환한다.

 

C++파일들을 컴파일 하기 위해 Makefile을 만들었다.

.SUFFIXES: .cpp .c .o

NAME=alchemy_speed_test
PATH:=${ALCHEMY_HOME}/achacks:${PATH}
CC = gcc
CCPP = g++
FLAGS = -O3 -Wall
OBJS =  $(NAME).o Matrix3D.o Vector3D.o

.cpp.o:
    $(CCPP) $(FLAGS) -c $<
.c.o:
    $(CC) $(FLAGS) -c $<

$(NAME) : $(OBJS)
    $(CC) $(FLAGS) -swc -o $(NAME).swc $(OBJS)

clean:
    rm $(OBJS) $(NAME).swc
    rm swfbridge.log
    rm *achacks*
    rm -r _sb_*

Window사용자중 Cygwin을 사용하시는 분은 make 패키지를 설치해야 이 Makefile을 구동할 수 있다.

 

프롬프트 상에서 make 명령을 하면 Makefile을 구동하여 C++파일을 컴파일 할 수 있다.  

 


 

만약 make를 사용하지 않으면 아래와 같이 컴파일해도 된다.

g++ –O3 –Wall –c alchemy_speed_test.cpp
g++ –O3 –Wall –c Matrix3D.cpp
g++ –O3 –Wall –c Vector3D.cpp
gcc –O3 –Wall –swc –o alchemy_speed_test.swc alchemy_speed_test.o Matrix3D.o Vector3D.o

 

마지막으로 컴파일된 alchemy_speed_test.swc 을 AlchemySpeedTest.as에 추가해서 컴파일 한다.

mxmlc.exe –library-path+=alchemy_speed_test.swc –target-player=10.0.0 AlchemySpeedTest.as

 

생성된 AlchemySpeedTest.swf 를 Flash Player 10에서 구동하면 다음과 같은 결과가 나온다.

1. Alchemy Time taken = 200 vector = (-0.40824829046386324, 0.8164965809277259, -0.4082482904638632)

1. Native  Time taken = 1065 vector = (-0.4082482904638631, 0.816496580927726, -0.4082482904638629)

2. Alchemy Time taken = 290 vector = (-0.6365049502756422, 0.662044570582514, -0.04630804289556738)

2. Native  Time taken = 893 vector = (-0.6365604400634766, 0.6619919538497925, -0.04631434381008148)

 

결국 결과는 다음과 같다.

Vector dot Product 연산과 Normalisation의 경우
- Alchemy : 201 ms
- Native : 1090 ms

회전행렬 생성과 Vector 변환의 경우
- Alchemy : 301 ms
- Native : 908 ms

이전과 다르게 수행속도의 차이를 보이고 있다. 이 결과만 놓고 볼 때 필요할 때는 C/C++로 만들어 최적화 할 수 있도록 만들 수 있겠다는 생각을 가진다. 하지만 많은 차이를 보이지 않아 실제 속도해결에 있어서 필요성에 대해서는 의문을 가질 수 있겠다. 아래서부터 Alchemy를 더욱 이해해 보도록 하겠다.

 

Alchemy를 깊게 이해하자.

 

Adobe labs에서 Alchemy에 대한 소개를 보면 ActionScript 3.0 코드보다 더욱 빠를 것이다라고 언급하고 있다. 하지만 지금까지의 결과를 볼 때 실제로는 기대만큼 효과적으로 빠른 것 같지는 않다. Alchemy를 사용해서 수행속도에 대한 이점을 이끌어내기 위해서는 Alchemy 본질을 어느 정도 이해할 필요가 있다고 생각했다.

 

아래 Alchemy에 대한 내용은 나름대로 자료를 찾아서 안되는 영어로 번역해보고 분석해봐서 정리한 내용이다. 혹시 잘못된 내용이 있을 수 있다. 따끔한 지적도 마다하지 않겠다. ^^

 

Alchemy를 이용해 C/C++ 코드를 SWF로 컴파일 하는 과정은 다음과 같다.

  1. C/C++ 코드를 LLVM(Low Level Virtual Machine) 바이트 코드로 변환한다.(확장자 .bc)
  2. LLVM 코드를 ActionScript 3.0 코드로 변환한다. (확장자 .as)
  3. ActionScript 3.0 코드를 ActionScript Byte Code로 변환한다.(확장자 .abc)
  4. 마지막으로 swc를 만든다. (확장자 .swc)

Alchemy의 중요 요소중 하나는 LLVM(Low Level Virtual Machine)이다. LLVM은 컴파일러 컴포넌트로 여러 언어를 중간코드로 변경하여 언어와 플랫폼에 독립적이고 컴파일, 링크시 런타임등 각 시점에서 최적화를 시켜준다. 자체적인 중간코드(intermediate representation)을 바탕으로 프로그래밍 언어와 CPU에 독립적인 최적화기, GCC기반의 C/C++ 프론트엔드(front-end), 그리고 X86, X86-64, PowerPC, ARM, IA-64, SPARC, Mips, Alpha, c 소스 백엔드(back-end), 또한 메모리에 코드를 생성해 실행할 수 있는 JIT 백엔드 등을 포함하고 있다.

 

Alchemy 설치 디렉토리를 $ALCHEMY_HOME 이라고 한다면 $ALCHEMY_HOME/achacks 내에 gcc가 있다. gcc는 sh코드로 만들어져 있고 이 안에서 C/C++코드를 SWC로 컴파일하기 위한 모든 과정이 들어간다. gcc 파일에서 첫번째 하는 과정은 해당 C/C++코드를 LLVM 바이트 코드(.bc)로 바꾸는 일이다. 이때 사용되는 도구가 $ALCHEMY_HOME/bin/llvm-gcc와 llvm-ld이다. 예를 들어 아래와 같은 코드가 실행된다.

$ llvm-gcc -v -emit-llvm -nostdinc -I$ALCHEMY_HOME/avm2-libc/include -I/usr/local/include –include $ALCHEMY_HOME/avm2-libc/avm2/AVM2Env.h echotest.c -c -o echo.o

$ llvm-ld -o=echotest -O5 -internalize-public-api-list=_start,malloc,free,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3, __fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf, __iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__qdivrem, __adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__qdivrem,__fixdfdi, __fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3, __lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__subdi3, __ucmpdi2,__udivdi3,__umoddi3,__xordi3,__subdi3,__ucmpdi2,__udivdi3, __umoddi3,__xordi3,__error $ALCHEMY_HOME/avm2-libc/lib/avm2-libc.l.bc echotest.o

위 과정은 llvm-gcc로 echotest.c를 echotest.o로 만들고 llvm-ld로 echotest.o를 avm2-libc.l.bc와 링크해서 echotest와 echotest.bc를 만들어준다. echotest는 echotest.bc를 실행하는 sh파일이다.

 

참고로 LLVM 바이트 코드인 echotest.bc는 $ALCHEMY_HOME/bin/llvm-dis를 이용해 이 도구는 disassemble하는 역할을 한다.  LLVM 홈페이지에 online demo를 보면 쉽게 이런 과정을 볼 수 있다. online demo에서 LLVM disassemble를 보면 다음 c코드가 어떻게 LLVM 바이트 코드로 바뀌는지 확인해볼 수 있다.

#include <stdio.h>
int main(int argc, char **argv) {
    printf("%d\n", 1);
}

다음은 위 코드를 LLVM 바이트 코드로 바꾼 것은 disassemble 한 것이다.

; ModuleID = ‘/tmp/webcompile/_5616_0.bc’
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"
@.str = internal constant [4 x i8] c"%d\0A\00"        ; <[4 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** %argv) nounwind {
entry:
    %0 = tail call i32 (i8*, …)* @printf(i8* noalias getelementptr ([4 x i8]* @.str, i32 0, i32 0), i32 1) nounwind        ; <i32> [#uses=0]
    ret i32 undef
}

declare i32 @printf(i8*, …) nounwind

 

다음으로 $ALCHEMY_HOME/bin에 있는 llc 도구를 이용해 ActionScript 3.0 코드를 만든다.

$ llc -march=avm2 -avm2-use-memuser -o=echotest.as -avm2-package-name=cmodule.echotest echotest.bc

다음 코드가 만들어진 ActionScript 3.0 코드이다.

package cmodule.speedtest {
// Start of file scope inline assembly
import flash.utils.*
import flash.display.*
import flash.text.*
import flash.events.*
import flash.net.*
import flash.system.*

public var gdomainClass:Class;
public var gshell:Boolean = false;

public function establishEnv():void
{
  try
  {
    var ns:Namespace = new Namespace("avmplus");
    gdomainClass = ns::["Domain"];
    gshell = true;
  }
  catch(e:*) {}
  if(!gdomainClass)
  {
    var ns:Namespace = new Namespace("flash.system");
    gdomainClass = ns::["ApplicationDomain"];
  }
}

establishEnv();

……(생략)

public const _c_speed_test:int = regFunc(FSM_c_speed_test.start)

public final class FSM_c_speed_test extends Machine {

    public static function start():void {
            var result:FSM_c_speed_test = new FSM_c_speed_test
        gstate.gworker = result
    }

    public var i0:int

    public static const intRegCount:int = 1
    public var f0:Number, f1:Number, f2:Number, f3:Number

    public static const NumberRegCount:int = 4
    public final override function work():void {
        Alchemy::SetjmpAbuse { freezeCache = 0; }
        __asm(label, lbl("_c_speed_test_entry"))
        __asm(push(state), switchjump(
            "_c_speed_test_errState",
            "_c_speed_test_state0",
            "_c_speed_test_state1"))
    __asm(lbl("_c_speed_test_state0"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_0_F"))
        mstate.esp -= 4; __asm(push(mstate.ebp), push(mstate.esp), op(0×3c))
        mstate.ebp = mstate.esp
        mstate.esp -= 0
        f0 =  (0)
        i0 =  (0)
    __asm(jump, target("_c_speed_test__XprivateX__BB75_1_F"), lbl("_c_speed_test__XprivateX__BB75_1_B"), label, lbl("_c_speed_test__XprivateX__BB75_1_F"));
        f1 = f0
        f2 =  (Number(i0))
        f0 = f2
        //InlineAsmStart
    f0 =  Math.sin(f0);

    //InlineAsmEnd
        f3 = f0
        f0 = f2
        //InlineAsmStart
    f0 =  Math.cos(f0);

    //InlineAsmEnd
        f0 =  (f3 * f0)
        i0 =  (i0 + 1)
        f0 =  (f0 + f1)
        __asm(push(i0==10000000), iftrue, target("_c_speed_test__XprivateX__BB75_3_F"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_2_F"))
        __asm(jump, target("_c_speed_test__XprivateX__BB75_1_B"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_3_F"))
        mstate.esp -= 8
        __asm(push(f0), push(mstate.esp), op(0×3e))
        state = 1
        mstate.esp -= 4;(mstate.funcs[_AS3_Number])()
        return
    __asm(lbl("_c_speed_test_state1"))
        i0 = mstate.eax
        mstate.esp += 8
        mstate.eax = i0
        mstate.esp = mstate.ebp
        mstate.ebp = __xasm<int>(push(mstate.esp), op(0×37)); mstate.esp += 4
        //RETL
        mstate.esp += 4
        mstate.gworker = caller
        return
    __asm(lbl("_c_speed_test_errState"))
        throw("Invalid state in _c_speed_test")
    }
}

……(생략)

 

 

Alchemy로 C 또는 C++ 코드의 컴파일 결과는 기본적으로 ActionScript 와 AVM2 바이트 코드로 만들어진 하나의 클래스가 된다. 이 과정에서 결과물인 SWC는 C와 C++이 C 라이브러리와 POSIX를 지원에 필요할 수 있는 것도 함께 컴파일 되므로 크기가 커진다. 가령 54줄의 C코드를 Alchemy를 통해 컴파일 하면 27415줄의 ActionScript 코드가 만들어진다. 방금 위에서 이러한 현상을 확인할 수 있었다.

 

다음으로 asc.jar를 이용해 만들어진 ActionScript 파일을 swf로 만든다. 이 과정에서 playerglobal.abc와 global.abc를 포함한다.

$ java -Xms16M -Xmx1024M -jar $ALCHEMY_HOME/bin/asc.jar -AS3 -strict -import $ALCHEMY_HOME/flashlibs/global.abc -import $ALCHEMY_HOME/playerglobal.abc -d -config Alchemy::Shell=false -config Alchemy::NoShell=true -config Alchemy::LogLevel=0 -config Alchemy::Vector=true -config Alchemy::NoVector=false -config Alchemy::SetjmpAbuse=false -swf cmodule.echotest.EchoTest,800,600,60 echotest.as

다음으로 만들어진 echotest.swf를 ActionScript 바이트 코드로 만들어주고 라이브러리와

$ GetABC2.pl echotest echotest.swf.abc

$ALCHEMY_HOME/achacks에 있는 swctmpl.swf를 위에서 만든 abc로 대체해 새로운 swf를 만들고 SWF 버전 태그를 10으로 한다.

$ PutABC2.pl $ALCHEMY_HOME/swctmpl.swf temp.swf echotest.swf.abc cmodule/echotest/CLibInit 
$ V10SWF.pl temp.swf library.swf

마지막으로 다음과 같은 catalog.xml과 함께 최종적으로 SWC를 만든다.

<? xml version = "1.0"encoding = "utf-8"?>
<swc xmlns = "http://www.adobe.com/flash/swccatalog/9">
  <versions>
    <swc version = "1.0" />
    <flex version = "2.0"    build = "143452"/>
  </ versions>
  <features>
    <feature-script-deps />
    <feature-files />
  </ features>
  <libraries>
    <library path = "library.swf">
      <script name = "cmodule/echotest/ CLibInit" mod = "1177909560000">
        <def id = "cmodule.hello:CLibInit" /> 
        <dep id = "Date"    type = "e"/> 
        <dep id = "Date"    type = "s"/> 
        <dep id = "flash.utils : Dictionary" type = "e"/> 
        <dep id = "flash.utils : Dictionary" type = "s"/> 
:
(생략)
:
        <dep id = "AS3" type = "n"/> 
        <dep id = "Object" type = "i"/> 
      </ script>
    </ library>
  </ libraries>
  <Files>
  </ files>
</ swc>

$ zip echotest.swc catalog.xml library.swf

 

이처럼 Alchemy를 통해 얻는 결과물은 일반적으로 SWC이다(SWF의 경우 사용가치가 떨어진다.). 이 SWC는 Flex 3(Flash Player 10을 겨냥해서 만들어져야 함), Flex 4(아직 개발중인 “Gumbo”) 그리고 Flash CS4에서 사용할 수 있다.

 

C와 C++로 만들어졌기 때문에 Alchemy로 컴파일 된 SWF가 일반 C/C++의 규칙과 제한을 따른다고 생각하면 곤란하다. C/C++는 일반 SWF와 같이 Flash Player의 보안샌드박스안에 포함되고 모든 화면출력결과물은 display list를 통과하도록 만들어진다. 그리고 네트워크 및 파일엑세스도 이미 존재하는 클래스를 사용하게 된다. Alchemy로부터 컴파일된 라이브러리에서 만들어진 코드는 적절한 메모리 덩어리를 ByteArray로 할당한다. 이것은 Alchemy가 어떻게 포인터, malloc, free등이 ActionScript 에서 어떻게 작업되는가 알려주는 개념이 된다. 이말은 즉 C/C++언어가 Alchemy를 통해 100% ActionScript 3.0으로 변경되어 원천적으로 가지고 있는 SWF의 제약사항을 넘어갈 수는 없다는 것을 의미한다.

 

그럼에도 불구하고 Alchemy의 AVM2 코드가 Flash 와 Flex에서 만들어진 AVM2 코드보다 어떻게 수행속도가 빠를 수 있는지 궁금할 거다. 실제로는 더 빠르지 않다. 그럼 Adobe 에서 주장하는 “수행속도가 빠를 것이다”라는 것은 거짓말인가? 100% ActionScript 3.0으로 바뀌는데 수행속도가 빠를 수 없다고 생각할 수 있다. 하지만 이 부분에서 수행속도의 개선을 찾으면 안된다. 실제로는 Alchemy의 LLVM은 많은 최적화를 이루는 반면 Flash와 Flex의 컴파일러는 최적화 과정이 없다. 또한 Alchemy는 ActionScript 3.0 자체가 가지는 오버헤드의 문제로 인한 수행속도 저하를 극복하는데 쓰일 수 있다.

 

그럼 Alchemy의 수행속도에 대해 기대할 수 있는 부분은 구체적으로 무엇일가? 바로 메모리 엑세스와 함수 호출이다. Alchemy를 통해 컴파일된 코드는 ByteArray를 적용하기 위해 Flash Player 10에 적용된 새로운 ByteArray를 사용한다. 마치 이것은 RAM처럼 이용된다. ActionScript 3.0의 함수를 호출할 때 그들의 인자가 박싱(boxed) 와 언박싱(unboxed)를 하게 된다. 하지만 Alchemy로 만들어진 코드는 그러한 과정이 없다. 이 두가지 이유로 Alchemy로 만들어진 결과물이 수행속도 향상에 도움이 될 수 있다는 것이다.

 

ActionScript 코드와 Alchemy 코드간에 통신과정에서 수행속도가 떨어질 수 있다. 이 과정은 마샬링(marshaling)이 된다. 마샬링은 어떤 한 언어에서 작성된 출력 데이터를 다른 언어의 입력단으로 전달해야하는 경우 데이터 변환과정이 생기는 것을 말하는데 ActionScript 코드와 Alchemy 코드간에 빈번한 상대방의 함수 호출은 이런 과정으로 인해 전체적인 수행속도를 죽이는 일을 발생시킬 수 있다. 그러므로 어떤 처리를 위해 C/C++코드에서 한꺼번에 처리하는 과정이 들어가도록 코딩하고 상대방 함수로 넘겨주는 파라미터의 수를 줄여주는 것이 속도향상에 도움이 될 것이다.

 

Alchemy는 매우 강력하지만 마법사는 아니다. Alchemy의 이점이 무엇인지 잘 파악해서 사용하는 것이 현명하다. 본인이 생각할 때 Alchemy를 사용해야 하는 경우는 다음과 같다.

1. Alchemy로 포퍼먼스 향상에 어느 정도 이득이 있는 경우
2. 기존 C/C++ 라이브러리를 ActionScript 3.0으로 바꾸기 힘들거나 별도의 변경 없이 그래로 Flash/Flex/AIR 프로젝트에서 사용하고 싶은 경우

위 2가지 경우 중 1가지라도 있다고 판단하면 Alchemy를 활용해서 자신의 Adobe RIA 프로젝트에 접목시킬 수 있다고 생각한다. 2번째의 경우 C/C++ 개발자와 Adobe RIA 개발자간에 협업도 기대할 수 있는 부분이다.

 

한가지 더 언급할 것은 Alchemy는 아직 정식 배포상태가 아니라는 점이다. 그래서 약간 개발하기도 불편하고 버그도 간혹 보인다. 하지만 정식 배로가 된다면 그러한 문제점은 해결될 것이라 생각하고 지금까지 보여줬던 것 보다 더욱 큰 포퍼먼스 향상을 기대할 수 있다. 추후에는 Flash Catalyst에 포함되어 개발에 큰 도움이 될 것이다.

 

정리하며

 

Alchemy의 장점은 메모리 엑세스와 함수호출에 따른 수행속도의 개선과 기존 C/C++ 라이브러리를 그대로 활용할 수 있다는 점에 있다. 이 장점을 잘 살려서 현업에 적용할 수 있도록 적절한 예제를 찾아보려한다.

 

이 글을 적으면서도 Alchemy의 컴파일 과정 및 내용에 대해서 확실히 이해할 수 없었다. 하지만 실망하지 않는다. 어쨌던 이러한 노력은 나중에 큰 도움이 될 것이다라고 생각하기 때문이다.

 

이 글을 보신 분 중 공유할 수 있는 내용이나 지적사항이 있다면 언제든지 댓글 환영한다. ^^


한국에도 Alchemy를 활용한 많은 예제와 현업에서 적용 예가 많이 나왔으면 한다.

 

참고내용

 

아래 테스트 코드는 문제가 있었습니다. 왜냐하면 GCC 최적화 옵션에 대해서 명확히 이해를 못하고 사용했기 때문입니다. 그래서 잘못된 지식을 전하는 것을 우려해 그냥 내용만 훑어보시되 “Adobe Alchemy 수행속도 테스트와 깊이 이해하기”글을 덧붙여 읽어주시길 바랍니다. 제가 잘못된 내용을 적었다면 지적해주셨으면 합니다. 그리고 실무에 썼거나 테스트를 해보신 분이라면 그에 대해 소개도 부탁드릴께요.

Alchemy는 C/C++ 코드를 AVM2(ActionScript 3 Virtual Machine) 환경에서 동작하도록 해주는 도구이다. Alchemy를 이용해 기존에 있는 C/C++ 코드를 Adobe AIR, Adobe Flex, Adobe Flash 프로젝트에서 직접 활용할 수 있는 SWF 또는 SWC를 만들어낼 수 있다. 이에 대한 더욱 자세한 사항은 본인이 게재한 “C/C++와 Flash 플랫폼과의 만남. Alchemy 1부” 를 참고하기 바란다.

 

이 글은 Alchemy를 이용해 만들어진 SWC를 이용했을 때와 순수 ActionScript 3.0으로만 코딩했을 때 수행속도 차이를 비교해보는 것을 목적으로 한다.

 

Alchemy 속도 테스트

 

과연 Alchemy를 이용해서 C언어를 SWC로 변환한 코드의 수행속도에 진전이 있을까? 아래 c코드와 ActionScript 3.0 코드는 이를 테스트할 수 있도록 한다. c언어와 ActionScript 3.0에서 동일하게 ”sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3)” 식을 1백만번 반복한다.

 

컴파일하고 수행하는 방법은 “C/C++와 Flash 플랫폼과의 만남. Alchemy 1부”를 참고한다.

1. speedtest.c

#include <math.h>
#include "AS3.h"

static AS3_Val c_speed_test(void* self, AS3_Val args)
{
    int i;
    double result;
    for( i=0; i < 1000000; i++ )
    {
        result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
    }
    return AS3_Number(result);
}

int main()
{
    AS3_Val method = AS3_Function( NULL, c_speed_test );
    AS3_Val result = AS3_Object( "c_speed_test: AS3ValType", method );
    AS3_Release( method );
    AS3_LibInit( result );
    return 0;
}

 

2. AlchemySpeedTest.as

package {
    import cmodule.speedtest.CLibInit;

    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    public class AlchemySpeedTest extends Sprite
    {
        public function AlchemySpeedTest()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            var loader:CLibInit = new CLibInit();
            var lib:Object = loader.init();
            var startTime:Number;

            startTime = getTimer();
            var cResult:String = "c returned value : " + lib.c_speed_test(null) + " delay time(ms) : " + (getTimer()-startTime);

            startTime = getTimer();
            var asResult:String = "as3 returned value : " + as3_speed_test() + " delay time(ms) : " + (getTimer()-startTime);

            var textField:TextField = new TextField();
            textField.text = cResult + "\n" + asResult;
            textField.width = 500;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.defaultTextFormat = new TextFormat( null, 12 );
            addChild(textField);

        }
        public function as3_speed_test():Number
        {
            var i:int;
            var result:Number;
            //var sin:Function = Math.sin;
            //var cos:Function = Math.cos;
            for (i = 0; i < 1000000; i++)
            {
            result = Math.sin( 0.332 ) + Math.cos( 0.123 ) + Math.tan( 0.333 ) * Math.log(3);
            }
            return result;
        }
    }
}

 

speedtest.c를 아래처럼 Alchemy로 컴파일하고 만들어진 speedtest.swc를 AlchemySpeedTest.as와 함께 mxmlc로 컴파일 한다.

gcc speedtest.c –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

아래는 위 프로그램을 시행한 결과이다.

c returned value : 1.6983678388082433 delay time(ms) : 559
as3 returned value : 1.6983678388082433 delay time(ms) : 694

이 결과는 의외의 결과이다. C와 ActionScript 3.0과 같은 수행속도를 보였기 때문이다. 하지만 이 결과는 C코드를 컴파일 할 때 최적화하지 않았기 때문이다. 아래처럼 –03 –Wall 옵션을 넣고 다시 수행해보자. -O3는 최적화 옵션이고 –Wall(또는 –W)는 모든 경고를 출력하도록 한다.

gcc speedtest.c –03 –Wall –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

컴파일한 SWF파일을 실행한 결과는 다음과 같다.

c returned value : 1.6983678388082433 delay time(ms) : 1
as3 returned value : 1.6983678388082433 delay time(ms) : 601

 

결론

 

위 실험 결과를 통해 Alchemy를 이용해 C/C++의 수행속도를 잘 활용하면 좋다는 것을 알 수 있었다. 가령, ActionScript 3.0으로 구동 시에 많은 부하가 있을 가능성이 있는 부분을 C로 만들어 Alchemy를 통해 최적화된 SWC로 라이브러리화 해서 사용한다면 여러분의 Flash/Flex/AIR 애플리케이션의 속도를 크게 향상시킬 수 있다. 구체적으로 Audio/Video, 데이터 형식 변환, 데이터 조작, XML 분석 및 암호화 기능, 물리 시뮬레이션등이 들어가는 코드에서 포퍼먼스 향상을 기대할 수 있겠다.

아래는 Alchemy가 C/C++코드를 ActionScript 3.0 코드로 완벽하게 변경하는가에 대한 의견이다.

(글쓴이 의견)

어떤 분은 Alchemy로 SWF를 만들어준다고 해서 C/C++코드가 전부 ActionScript 3.0으로 변환될 것이다라는 생각을 가질 수 있다. 하지만 실험 결과만 가지고 볼 때 틀리다는 것을 유추할 수 있다. Alchemy는 C/C++ 코드를 AVM2 환경에서 동작하도록 만들되 ActionScript 3.0과 함께 사용할 수 있도록 만들어준다. 즉 C/C++를 적절하게 변경하고 ActionScript 3.0과 통신할 수 있는 통로구를 마련해주는 것이 Alchemy의 목적이다. 만약 ActionScript 3.0으로만 변경되는 것이라면 빠른 수행속도를 기대하기 어려울 것이다.

(양병규님 의견)

컴파일러에 대해서 조금 관심을 가져보시면 알 수 있을텐데요…
음… 뭐랄까…
그 최적화라는 것이 불필요한 일은 안하고 같은 기능이라면 더 빠른 기능으로 대치하고.. 그런일을 하는 것이 컴파일러의 최적화인데요..
제가 보기에는
for( i=0; i < 1000000; i++ )
{
result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}
이 부분이.. 최적화를 하면 루프를 1000000번 돌지 않고 한방에 끝낼것같습니다. 최적화알고리즘중에서 루프문안에서의 내용이 반복을 해도 변화가 없는 경우에는 루프를 한방에 끝내도록 하는 알고리즘이 있습니다. 이 예가 그런 예일것 같은데요..

암튼.. ActionScript에는 그런 최적화가 없는데 이미 오랜 역사동안 만들어진 C/C++언어의 최적화 기술이 적용되면 분명히 나은 결과를 보일 것 으로 예상됩니다. ^^;(물론 코딩하는 사람의 실력이 좋을 수록 최적화의 효과가 떨어지겠지만)

그리고..
저는 아무리 봐도 C/C++로 코딩한 것이 결국 몽땅 ActionScript로 만들어지는 것이 맞는 것 같습니다. SWF에는 ActionScript 이 외에 다른 실행코드는 존재하지 않습니다. 최종적으로 만들어진 결과가 *.SWF(*.SWC)이지 않습니까? SWF File Format Specification Version 10 문서 278페이지를 다 훑어봐도 ActionScript 이외에는 실행코드가 없습니다. 머.. DLL을 임포트한다거나 별도의 실행코드용 외부 라이브러리가 있다거나… 그런건 전혀 없습니다.

malloc 함수 같은 경우의 예를 들어보면 ActionScript에는 malloc과 동일한 역할을 하는 어셈블명령은 없습니다. 하지만 ActionInitArray와 같이 긴 변수를 사용할 수 있는 명령으로 ‘구현’은 할 수 있을겁니다. 아마도 그런 방법으로 구현되지 않았나싶습니다.

어쨋든 Alchemy는 C/C++을 100% ActionScript로 변환해 주는게 맞고.. 함수사용, 변수사용, 최적화, 수학함수의 구현, 각종 기초 알고리즘등 기초적인 부분에서 훨씬 우월하기 때문에 좋은 효과를 나타내고 있는 것으로 생각됩니다.

시간이 나면 Alchemy에서 만들어진 SWF 파일을 분석해 보면 아주 좋은 공부가 될것같습니다. ^^;

Alchemy가 유용한 것은 C/C++ 코드를 ActionScript 3.0와 함께 쉽게 사용할 수 있게끔 해주는 것과 C/C++자체가 가지는 빠른 수행속도를 Flash 컨텐츠에서 이용할 수 있는 것으로 생각한다. Alchemy가 앞으로 사용 편의성 향상과 여러 환경에서 문제없이 개발할 수 있도록 개선된다면 Adobe RIA기술이 한 단계 업그레이드 될 것이라 생각한다.

 

한가지 주의할 사항은 전부 C/C++로 만드는 것은 좋지 못하다. Alchemy를 적용하기에 앞서 본문에서 실험한 것과 같은 방법으로 여러가지 수행 테스트를 해보고 순수하게 ActionScript 3.0으로 한 것보다 큰 포퍼먼스를 기대할 수 있다고 판단했을 때 적용하는 것이 좋다. 때로는 어떤 코드냐에 따라 별로 큰 차이가 없을 수 있기 때문이다.

국내에 Alchemy를 응용한 많은 예제와 애플리케이션들이 나오길 바란다.

 

참고할 사이트

 

이 문서는 Adobe 연구 프로젝트 코드명중 하나인 “Alchemy”에 대해서 소개한다.

 

Alchemy는 2008년 미국에서 열린 Adobe MAX 컨퍼런스 행사에서 최초로 소개되었고 C/C++ 코드를 오픈 소스 ActionScript Virtual Machine(AVM2)용으로 컴파일 목적으로 연구 중인 프로젝트이다.

 

Alchemy는 운영체제(OS)에 의존성이 없는 C/C++ 라이브러리를 Flash Player 10 또는 Adobe AIR 1.5에서 실행되는 SWF 또는 SWC 형태로 컴파일할 수 있도록 해준다. 컴파일은 GCC(GNU Compiler Collection)를 이용해 LLVM(Low Level Virtual Machine)으로 변환한 뒤 이것을 AVM 바이트 코드로 변경시켜주는 과정이 들어간다.

주로 Audio/Video, 데이터 형식 변환, 데이터 조작, XML 분석 및 암호화 기능, 물리 시뮬레이션 등 부하가 일어나는 계산에 응용하면 적합하다. 이러한 계산을 할 때, ActionScript 3.0보다 훨씬 빠르지만 순수한 C/C++ 코드보다 2~10배 정도 느릴 수 있다.

 

C와 C++로 만들어진 코드는 Alchemy를 통해 ActionScript 3.0 환경에서 사용할 수 있도록 SWF 또는 SWC로 컴파일 되므로 이를 가져다가 Adobe Flex, Flash, AIR 환경에서 개발할 수 있게 된다.

 

Alchemy로 제작된 결과물은 Flash Player 10이나 AIR 1.5 이상에서 구동되며, Windows, Mac OS X, Linux등의 다양한 운영체제에서 사용할 수 있다.

현재 Alchemy는 아직 미완성된 버전이다. 정식 배포가 되기 전까지는 개발의 불편함과 발생할 오류 등에 대해 감수할 수 밖에 없다.

 

이 문서는 총 2부로 나뉘어 쓰여진다. 1부에서는 개발환경 구축 및 간단한 예제 소개를 통해 Alchemy에 대해서 이해하는 것을 목적으로 한다. 2부에서는 간단한 응용을 통해 실무에 어떻게 적용될 수 있는지 알아보도록 하겠다.

Alchemy은 사전적으로 연금술(鍊金術)이라 불리운다. 비금속을 인공적 수단으로 귀금속으로 전환하는 것을 목표로 삼는 연금술은 그 의미로 봐서는 과학적으로 가능하지 않다. 하지만 인간의 이러한 노력은 과학적 접근을 통해 물질에 대한 명확한 분석을 가능하게 하는데 큰 역할을 했다고 생각한다. Alchemy는 한국어로 알크미 정도로 발음하면 될 것 같다.

Adobe에서 연구 프로젝트 이름으로 Alchemy라는 용어를 왜 썼을까? C/C++과 Flash를 섞어 금과 같이 가치 있는 것을 만들겠다는 의미가 내포되어 있는 듯 하다.

 

Alchemy 소개 동영상

 

Alchemy에 대해서 소개하는 동영상을 보도록 하자. 영어듣기가 어려우신 분들은 뛰어넘어가도 좋겠다.

 

아래는 Adobe 이벤젤리스트인 라이언 스튜어트(Ryan Stewart)가 Alchemy에 대해서 소개한 동영상을 담고 있다.

http://tv.adobe.com/#vi+f1472v1033

 

아래는 Adobe MAX 컨퍼런스 행사에서 Alchemy에 대해서 소개하는 동영상이다.

 

http://kr.youtube.com/watch?v=0hX-Uh3oTcE

 

아래는 Automate Studios의 최고기술경영자이자 최고 소프트웨어 설계 책임자인 Brandel Hall이 Alchemy를 소개하는 동영상이다.

 

http://labs.adobe.com/technologies/alchemy/videos/brandenhall/

 

Alchemy를 이용하여 C코드를 Flash로 변환한 게임 : Doom 1

 

Alchemy에 대한 흥미를 돋구기 위한 것으로 이것만 한 것이 없는 것 같다. 93년 작품인 DOS에서 돌아갔던 Doom 1이다. 아래 그림은 C코드로 만들어진 Doom 게임을 Alchemy를 이용해 Flash로 변환하여 실행한 모습을 스크린샷 한 것이다.

 

이처럼 Alchemy를 이용하면 기존 C/C++코드를 다양하게 활용할 수 있다.
아래 링크를 통해 Doom 1을 실행해자.

이 코드는 제작자가 Alchemy 개발환경에서 직접 컴파일할 수 있도록 소스를 공개해두었다.

Doom 게임을 직접 Flash 기반으로 포팅하는 소스도 공개가 되어 있다 참고하길 바란다. 이것은 Alchemy와 상관이 없다.

 

Alchemy 활용을 위한 지침

 

1. 기존 C/C++ 코드 이용

이미 C/C++로 만들어진 코드가 많이 있다. 암호화 알고리즘이나 압축 알고리즘 등은 이미 C/C++로 만들어져 있는데 이러한 코드를 일일히 ActionScript 3.0으로 바꾼다는 것은 비능률적이다. Alchemy는 이용하면 기존 C/C++ 코드를 쉽게 ActionScript 3.0에서 활용할 수 있는데 큰 도움을 줄 수 있다.

2. C/C++로 최적화

C/C++은 ActionScript 3.0보다 더 최적화된 코드를 만들어낼 수 있다. 가령 ByteArray를 다루는 일이나 많은 연산을 처리하는 작업에서 큰 포퍼먼스 향상을 기대할 수 있다. 게다가 C/C++ 특성상 직접 메모리 관리가 된다. 이러한 이유로 복잡한 Audio/Video 데이터 형식 변경 및 데이터 조작, 속도를 요하는 과학 시뮬레이션 핵심 코드 등에 적절하게 활용하면 매우 유용하게 쓸 수 있다. 구체적으로 복잡한 연산은 C/C++이 담당하고 렌더링은 ActionScript 3.0이 담당하게 한다면 전체 ActionScript 3.0으로 구현하는 것보다 몇 배의 포퍼먼스 향상도 기대할 수 있다. C/C++로 만들어진 코드와 ActionScript 3.0 간에 빈번한 통신은 오히려 속도를 저해시키는 요인이 될 수 있으며 제작만 어려워지는 상황이 발생할 수도 있으니 잘 구분해서 활용해야겠다.

3. C/C++로 만들었기 때문에 다 할 수 있는 것은 아니다.

C/C++을 이용하지만 AVM 환경에서 돌아가는 것을 목적으로 하기 때문에 몇가지 제약이 따른다. 가령 Win32 API 기반의 C코드나 DirectX 코드를 변환해 사용할 수 없다. 또한 Flash Player의 보안샌드박스에 위반하는 작업도 할 수 없다.

 

Alchemy 입문을 위한 참고 사항

 

Alchemy를 사용하기 위해 다음 항목을 참고하면 되겠다.

  1. Alchemy는 Flash Player 10 환경 및 Adobe AIR 1.5 Runtime 환경에서 동작하므로 해당 프로그램을 다운로드 받는다.
  2. 자신의 운영체제(Windows/Mac OSX/Linux 지원)에 맞는 Alchemy 툴킷(toolkit)을 다운로드 받는다.
  3. Getting Stated 문서를 통해 Alchemy 설치 및 사용방법을 익힌다. 이 문서에는 Alchemy 설치방법과 C/C++코드를 SWC로 변환하는 방법, 그리고 SWC를 이용한 간단한 ActionScript 3.0 예제를 보여준다.
  4. 질문이나 정보공유를 위해 Alchemy 포럼 페이지를 이용한다.
  5. Alchemy에 대한 간단한 라이브러리를 다운로드 받을 수 있고 또한 라이브러리 공유를 할 수 있다.

 

Alchemy 시스템 요구사항

 

Alchemy 개발환경을 구축하기 위해 운영체제 별로 다음과 같은 사양을 요구한다.

1. Windows

Window XP SP2(본인은 SP3)에서만 테스트 되었다. Vista의 경우는 테스트 되지 않았다. 다음 패키지들이 요구된다.

  • Cygwin 개발 도구
  • Java 1.4 이상
  • Perl(또한 Compress::Raw::Zlib 패키지를 설치해야 한다.)
  • Flash Player 10
  • 기타추가사항 : Adobe AIR 1.5 이상 런타임, Adobe AIR 1.5 SDK 이상 또는 Adobe Flex SDK 3.2 이상

2. Mac OSX

Mac OSX 환경에서는 다음 패키지들이 필요하다.

  • Perl 5.8 이상(Compress::Raw::Zlib.pm 포함)
  • Java 1.5 이상
  • Xcode 3.0 이상
  • Flash Player 10 이상
  • 기타추가사항 : Adobe AIR 1.5 이상 런타임, Adobe AIR 1.5 SDK 이상 또는 Adobe Flex SDK 3.2 이상

3. Linux

Ubuntu 8.0.4 버전에서만 테스트 되었다. 다음 패키지가 필요하다.

  • Java 1.4 이상
  • Perl(또한 Compress::Raw::Zlib 패키지를 설치해야 한다.)
  • Flash Player 10 이상
  • 기타추가사항 : Adobe AIR 1.1 beta 런타임, Adobe AIR 1.1 beta SDK와 Flex SDK 3.2 이상

 

Microsoft Windows에서 Alchemy 설치

 

MS Windows XP 환경에서 설치 방법을 설명하겠다. 물론 Mac OS X나 Linux환경에서도 가능하다. Mac OS X와 Linux에서는 gcc 개발이 가능하므로 Windows와 달리 Cygwin과 같은 프로그램을 설치할 필요가 없다는 점이 편리하다.

 

1. 설치 요구사항(MS Window 환경인 경우)

2. Cygwin 설치

Cygwin을 다운로드 받아 설치한다. Cygwin은 Window 상에서 Linux와 비슷한 환경을 제공해주는 프로그램이라고 할 수 있다. 그러므로 Linux환경에 어느 정도 경험있는 사람이라면 Cygwin도 쉽게 적응할 수 있을 것이다. Window환경에서 Alchemy를 동작시키기 위해서는 Cygwin이 필요하지만 Linux, Mac OS X에서는 필요없다.

 

 

인터넷을 이용해 설치하도록 한다.

 

 

설치할 폴더를 지정한다.

 

 

설치를 위한 패키지 파일을 다운로드로 하기 위한 임시 폴더를 지정한다.

 

 

인터넷 연결을 선택한다. Direct Connection을 선택해 사용하면 되겠다.

 

 

패키지를 다운로드 할 미러 사이트를 선정한다. daum.net에서 지원하므로 이용하자.

 

아래와 같이 Select Packages가 뜨면 필요한 패키지를 선택해 설치할 수 있도록 한다.(앞서 Perl, zip, gcc/g++, vi 에디터를 설치해야 한다고 언급했다.)

 

먼저 Devel 카테고리에 [+]기호를 눌러 펼친 다음 아래에 살펴보면 Package이름으로 gcc-core: C Compiler와 gcc-g++: C++ Compiler가 있다. 해당 패키지에 Skip으로 된 문자를 클릭해서 3.4.4-3과 같이 버전이 표시되도록 한다. Skip은 설치에서 제외한다는 의미이다.

 

 

다음으로 Perl 카테고리의 Default를 클릭하면 Install로 변한다. 이렇게 해서 하위에 있는 Perl관련 패키지들이 전부 설치될 것이다.

 

 

zip 패키지를 설치하기 위해 Archive 카테고리 [+] 버튼을 눌러 펼친 다음 zip: A file compression and packaging utility compatible with PKZIP 을 찾아 Skip을 클릭하여 2.32-2로 바꾸도록 한다.

 

 

vi 에디터를 사용하기 위해 Editors 카테고리의 [+] 버튼을 눌러 아래에 vim: Vi IMproved를 찾은 뒤 Skip을 눌러 7.2-3로 바꿔 설치할 수 있도록 한다.

 

 

 

다음 버튼을 눌러 설치를 한다.

 

 

Cygwin 설치가 완료되었다.

 

 

설치가 완료되면 바탕화면에 아래와 같은 실행 아이콘이 생길 것이다.

 

 

설치는 C드라이브에 설치되었다. 설치된 cygwin 내부를 살펴보면 디렉토리 구조가 Linux와 비슷하다는 점이 흥미롭다.

 

 

Cygwin에 다른 패키지를 설치하고 싶을 때는 Cygwin 홈페이지로 가서 설치 실행파일 다운로드 받아 앞서 했던 것을 반복하면 되겠다. 물론 이전 설치한 것은 굳이 재설치할 필요 없다.

 

Cygwin의 자세한 사용법은 Java를 설치 후 해보도록 하자.

 

3. Java 설치

Windows 환경에서 Alchemy는 Java 환경이 구축되어 있어야 한다. 참고로 MAX OS X의 경우에는 Xcode 2.4+ 버전이 필요하다.

http://java.sun.com/javase/downloads/index.jsp

상단 페이지로 가서 Java Standard Edition Develop Kit(JDK)를 Windows용으로 다운로드 받아 설치한다.

 

설치 후 환경 변수를 설정해야 한다. 환경 변수를 설정하는 이유는 운영체제에 Java가 설치된 경로를 등록함으로써 DB나 다른 프로그램과의 연동에 대한 편의성을 제공할 수 있기 때문이다. Linux나 Max OC X에도 이러한 환경 변수 설정이 있으니 인터넷 검색을 통해 찾아보길 바란다.

 

내컴퓨터->(마우스 오른쪽 클릭)속성->고급으로 들어간 다음 환경변수 버튼을 클릭한다.

 

 

아래처럼 환경변수 설정 창이 나온다.

 

 

위 화면에 사용자 변수와 시스템 변수가 있다. 사용자 변수는 로그인한 사람에게만 적용하기 위한 것익 시스템 변수는 전체 사용자에게 적용하는 변수라고 생각하면 되겠다. 시스템 변수에서 새로 만들기 버튼을 누른 다음 다음처럼 변수값을 등록한다.

  • 변수 이름 :  JAVA_HOME
  • 변수 값 :   JDK가 설치된 경로를 넣으면 되겠다.

 

다음으로 시스템 변수 중에 Path를 선택하여 편집 버튼을 누르면 다음과 같은 창이 뜬다.  아래처럼 변수 값으로 %JAVA_HOME%\bin; 를 앞부분에 추가한다.

 

CLASSPATH 시스템 변수를 등록한다. 시스템 변수에서 새로 만들기 버튼을 눌러 아래와 같이 입력한다.

  • 변수 이름 : CLASSPATH
  • 변수 값 : %classpath%;

 

모두 입력했으면 확인 버튼을 눌러 변수 설정을 종료한다.

 

 

위에서 설정한 환경 변수가 제대로 설정되어 있는지 확인하기 위해 콘솔창을 띄운다. 시작->실행->cmd입력->확인하면 되겠다.

 

 

cmd 창에서 아래와 같이 “java –version”, “javac –version”을 입력했을 때 에러 없이 잘 보여준다면 제대로 환경변수가 설정된 것이다.

 

이로써 Java 설치를 마친다.

4. Java를 설치한 뒤에 Cygwin 터미널 실행.

Cygwin은 Linux환경과 비슷하다. 윈도우에서 돌아가는 Linux라고 생각하고 접근하면 되겠다. 아래처럼 “ls –al”, “pwd”등으로 자신의 계정에 있는 파일 정보를 볼 수 있다.

 

각각의 드라이브에 접근 할 수 있다. 가령, C드라이브에 접근하기 위해 “cd /cygdrive/c”을 입력하면 된다.

이처럼 Cygwin을 이용하면 Window에서 Linux와 비슷한 환경이 제공된다. 그러므로 Linux 환경을 접해본 사람이라면 Cygwin을 다루는데 그리 어렵지 않을 것이다.

 

5. Flex SDK 설치

Flex SDK를 다운로드 받아서 C 드라이브에 설치하자. Flex SDK 3.2 이상 버전이어야 한다. Flex Builder 3를 이미 설치한 사람은 C:\Program Files\Adobe\Flex Builder 3\sdks 안에 SDK 3.2 가 있을 수 있겠다. 편의성을 위해 아래처럼 C 드라이브안에 flexsdk 이름으로 만들어 두자.  이 폴더는 Cygwin에서 접근하면 “/cygdrive/c/flexsdk”이 된다. 자유롭게 다른 경로에 설치할 수 있으므로 앞으로 /cygdrive/c/flexsdk를  $FLEX_HOME 별칭을 사용하겠다.

 

Cygwin에서는 Linux 명령어인 ls로 C 드라이브 내용을 볼 수 있다. 가령 “ls /cygdrive/c/flexsdk/”를 입력하면 금방 C드라이브에 설치한 flexsdk 내용을 확인할 수 있겠다.

 

 

6. Alchemy 다운로드 및 설치

http://labs.adobe.com/downloads/alchemy.html

위 링크로 들어가서 Windows Alchemy Toolkit을 다운로드 받는다. 다운로드 받은 다음 압축을 푼다. 본인은 편의성을 위해 cygwin 설치폴더 내에 alchemy폴더를 만들어 다운로드 받은 Alchemy Toolkit 파일을 복사했다. C:\cygwin\alchemy 를 Cygwin에서는 /alchemy/로 접근할 수 있으므로 앞으로 /alchemy/를 $ALCHEMY_HOME 으로 별칭을 두어 사용하겠다.

 

7. Alchemy 환경설정

Alchemy 개발환경 설정은 비교적 간단하다. 몇가지 환경설정만 하면 Cygwin에서 Alchemy 개발환경이 만들어진다. 아래 과정을 진행하면서 주의할 사항은 왜 그렇게 하는가를 따져보면서 읽어보는게 좋겠다. 왜냐하면 이러한 과정은 앞으로 Alchemy 개발환경 적응에 도움이 되기 때문이다.

 

$ALCHEMY_HOME으로 찾아간다. cd 명령어로 이동하면 되겠다. c:\cygwin\alchemy에 설치했었으므로 “cd /alchemy”로 이동하면 된다. 만약 d 드라이브에 설치했다면 “cd \cygdrive\d\alchemy”로 이동할 수 있다.

 

$ALCHEMY_HOME 안에는 config 파일이 있다. ls로 확인해보길 바란다.
아래와 같이 config를 실행하면 된다.

$ cd /alchemy
$ ./config

실행하면 몇 가지 에러 메시지를 보이면서 종료한다.

 

걱정하지 말자. config를 실행한 이유는 Alchemy설치를 위한 파일을 생성하기 위함이다. 아래처럼 ls로 $ALCHEMY_HOME 폴더 안에 파일목록을 살펴보면 전에 없었던 alchemy-setup 파일이 생성된 것을 확인할 수 있다.

 

Cygwin 설치할 때 vi 에디터를 설치했다. 그러므로 다음 명령어로 alchemy-setup 파일을 수정할 수 있다. 혹시 vi 에디터를 설치하지 않았다면 다른 에디터를 이용하길 바란다. 단, 외부에서 수정시 수정할 파일은 chmod를 이용해 권한 설정을 해주어야 편집이 가능하다.

$ cd /alchemy
$ vi alchemy-setup

아래와 같이 alchemy-setup내에 Flex SDK에 포함된 ADL(AIR Debug Launcher)을 추가하도록 한다.

export FLEX_HOME=/cygdrive/c/flexsdk
export ADL=$FLEX_HOME/bin/adl.exe

 

alchemy-setup 파일 설정을 마쳤다. 이번에는 자신의 계정으로 돌아가서 숨김 파일인 .bashrc 파일을 연다. 본인의 경우 /home/jidolstar 가 개인 계정이므로 해당 계정으로 이동한 다음 vi 에디터로 .bashrc를 열도록 하겠다.

$ cd /home/jidolstar
$ ls –al
$vi .bashrc

.bashrc 맨 아래에 아래와 같이 입력한다. 단 alchemy 설치 경로는 c:\cygwin\alchemy 일 때를 가정한다.

source /alchemy/alchemy-setup
PATH=$ALCHEMY_HOME/achacks:$FLEX_HOME/bin:$PATH
export PATH

 

환경설정 PATH로 $ALCHEMY_HOME/achacks 와 $FLEX_HOME/bin을 등록했다.

$ALCHEMY_HOME/achacks은 Alchemy에 사용하는 gcc 또는 g++ 툴이 존재한다. 즉 Cygwin에서 제공하는 gcc대신 Alchemy용 전용 gcc를 활용하기 위한 환경설정인 것이다. .bashrc에 설정을 했으므로 본인 계정으로 접속한 경우에는 항상 Alchemy 전용 gcc를 이용하게 되는 것이다. 만약 때에 따라서 Cygwin에서 제공하는 gcc를 이용할 경우가 있다면 $ALCHEMY_HOME/achacks는 빼고 alc-on와 alc-off 명령을 이용해 필요할 때마다 Alchemy 전용 gcc와 Cygwin gcc를 번갈아 가면서 이용할 수 있다.

 

alc-on, alc-off를 좀더 이해하기 위해 설명을 덧붙이겠다. 앞서 설정한 alchemy-setup을 유심히 봤다면 그 안에 아래와 같은 코드를 볼 수 있었을 것이다.

alias alc-home=’cd $ALCHEMY_HOME’
alias alc-on=’export PRE_ALCHEMY_PATH=$PATH; export PATH=$ALCHEMY_HOME/achacks:$PATH’
alias alc-off=’export PATH=$PRE_ALCHEMY_PATH’

별칭(Alias)으로 alc-home, alc-on, alc-off를 지정한 것을 볼 수 있다. cygwin 콘솔창 안에서 이들 명령을 시행하면 해당 명령시 실행한다. alc-home의 경우 Alchemy가 설치된 홈으로 이동한다. alc-on은 Alchemy 전용 gcc를 이용하도록 경로를 수정해준다. 그리고 alc-off는 Cygwin gcc를 이용하도록 한다. 단 alc-on 전에 alc-off를 사용해버리면 모든 경로정보가 사라지게 된다. 이런 경우에는 Cygwin을 종료했다가 다시 실행하면 되겠다.

 

$FLEX_HOME/bin은 /cygdrive/flexsdk/bin에 있는 flex 컴파일러인 mxmlc.exe를 어느 경로에서든 실행할 수 있게 하기 위함이다. 이 환경설정은 사용하는 것이 좋겠다. 그렇지 않으면 Flex 컴파일을 위해 mxmlc.exe가 있는 경로를 찾아가야 할 것이다.

Alchemy 설정을 모두 마쳤다. 설정이 적용될 수 있도록 Cygwin을 종료했다가 다시 실행하면 설정이 적용된다.

 

Alchemy 예제 테스트

Alchemy를 테스트 해보기 위해 Alchemy 설치시 이미 포함되어 있는 예제 프로그램을 이용해 보겠다. C, ActionScript 코드에 대한 자세한 설명은 배제하고 Alchemy를 이용하는 방법만 소개한다. 필요하다면 예제 프로그램을 스스로 분석하는 것이 도움이 될 것이다. 본 예제는 Adobe 이벤젤리스트인 마이크 챔버스(Mike Chambers)에 의해 작성되었다.

 

1.  C언어로 작성된 코드를 Alchemy를 이용해 SWC로 컴파일하기

먼저 $ALCHEMY_HOME/samples/stringecho 로 이동한 뒤 gcc를 이용해 해당 코드를 SWC 파일로 컴파일 해보자.

 

$ALCHEMY_HOME/samples/stringecho 안에는 아래 코드와 같은 stringecho.c가 존재한다. 이 코드의 역할은 매우 단순하다. 코드를 대충봐도 알겠지만 결국 사용할 함수는 echo()이다. AS3_로 붙은 함수들은 모두 Alchemy C 라이브러리에서 제공하는 함수들임을 예상할 수 있겠다.

//Simple String Echo example
//mike chambers
//mchamber@adobe.com

#include <stdlib.h>
#include <stdio.h>

//Header file for AS3 interop APIs
//this is linked in by the compiler (when using flaccon)
#include <AS3.h>

//Method exposed to ActionScript
//Takes a String and echos it
static AS3_Val echo(void* self, AS3_Val args)
{
    //initialize string to null
    char* val = NULL;
    //parse the arguments. Expect 1.
    //pass in val to hold the first argument, which
    //should be a string
    AS3_ArrayValue( args, "StrType", &val );
    //if no argument is specified
    if(val == NULL)
    {
        char* nullString = "null";
        //return the string "null"
        return AS3_String(nullString);
    }
    //otherwise, return the string that was passed in
    return AS3_String(val);
}

//entry point for code
int main()
{
    //define the methods exposed to ActionScript
    //typed as an ActionScript Function instance
    AS3_Val echoMethod = AS3_Function( NULL, echo );

// construct an object that holds references to the functions
    AS3_Val result = AS3_Object( "echo: AS3ValType", echoMethod );

// Release
    AS3_Release( echoMethod );

// notify that we initialized — THIS DOES NOT RETURN!
    AS3_LibInit( result );

// should never get here!
    return 0;
}

위 코드를 SWC로 컴파일하기 위해 다음 명령어를 실행한다. –O3 옵션은 최적화를 위한 것이고 -Wall은 모든 경고 메시지를 보여주기 위함이다. -swc를 붙였으므로 SWC로 만들어질 것이다. –o는 출력파일이름을 지정한다. -O3는 꼭 붙여주는 습관을 가지는게 좋다. 그렇지 않으면 수행속도가 떨어질 수 있다. 최소한 –O2 이상 주도록 하자.

$ cd /alchemy/samples/stringecho
$ gcc stringecho.c –O3 –Wall –swc –o stringecho.swc

위처럼 나오면 컴파일이 완료된 것이고 결과적으로 stringecho.swc가 생성된다. Alchemy에서 컴파일은 다음과 같은 과정을 거친다. 참고만 하도록 한다.

  1. C언어를 LLVM(Low Level Vitual Machine) Byte Code로 변환한다. (확장자 .bc)
  2. LLVM 코드를 ActionScript 3.0 코드로 변환한다.(확장자 .as)
  3. ActionScript 코드를 ActionScript Byte Code로 변환한다.(확장자 .abc)
  4. 마지막으로 swc을 만든다. (확장자 .swc)

2. 컴파일된 SWC를  ActionScript 3.0 프로젝트에 적용해보기

$ALCHEMY_HOME/samples/stringecho 안에 컴파일 결과물인 stringecho.swc를 이용해 간단한 ActionSciript에 포함하여 컴파일 해보자.

 

$ALCHEMY_HOME/samples/stringecho/as3 안에 가보면 EchoTest.as 가 있다. 이 ActionScript 파일은 방금 만든 stringecho.swc에 정의된 echo()함수를 사용한다.

package
{
    import flash.display.Sprite;
    import cmodule.stringecho.CLibInit;
    public class EchoTest extends Sprite
    {
        public function EchoTest()
        {
            var loader:CLibInit = new CLibInit;
            var lib:Object = loader.init();
            throw new Error(lib.echo("foo"));
        }
    }
}

아래처럼 실행하면 되겠다. 참고로 mxmlc.exe를 바로 실행할 수 있는 이유는 환경설정을 위해 개인 계정에 존재하는 .bashrc 파일에 환경설정시 경로로 $FLEX_HOME/bin 를 설정했기 때문이다.

$ cd /alchemy/samples/stringecho/as3
$ mxmlc.exe -library-path+=../stringecho.swc –target-player=10.0.0 EchoTest.as

–target-player=10.0.0 옵션을 넣은 이유는 Alchemy 결과물은 반드시 Flash Player 10 이상 버전에서 동작할 수 있기 때문이다. 컴파일이 성공하면 아래와 같이 EchoTest.swf가 만들어진다.

 

 

만들어진 EchoTest.swf를 실행해보면 throw new Error(lib.echo("foo")); 부분 때문에 에러창이 나온다. 아래 창과 같이 “foo”가 나왔다면 제대로 실행된 것이다. 다른 에러가 나왔다면 Flash Player 10인지 확인해보자.

 

3. 컴파일된 SWC를 Flex 프로젝트에 적용해보기

이번에는 stringecho.swc를 Flex Builder 3 Profession에서 Flex 프로젝트를 만들어 적용해보도록 하겠다.

 

아래와 같이 Flex Builder 3 Professional을 실행한 다음 stringecho_test라는 이름으로 Flex 프로젝트를 만들고 libs 폴더에 stringecho.swc를 복사한다.

그런 다음 아래와 같이 MXML를 작성한다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    creationComplete="init();">
    <mx:Script>
        <![CDATA[
            import cmodule.stringecho.CLibInit;
            private function init():void
            {
                var loader:CLibInit = new CLibInit;
                var lib:Object = loader.init();
                throw new Error(lib.echo("foo"));
            }
        ]]>
    </mx:Script>
</mx:Application>

이때 아래 2가지 조건이 맞아야 한다.

첫째. Flex SDK의 버전은 3.2 이상이어야 한다.
둘째. Flash Player version은 10.0.0 이상이어야 한다.

이 조건들이 맞지 않으면 에러를 낼 것이다. 에러가 발생한다면 아래와 같이 메뉴에서 Project->Properties로 들어가 창이 뜨면 좌측메뉴에 Flex Compiler를 선택한 뒤 아래처럼 설정한다.

  • Flex SDK version을 Flex SDK 3.2로 선택한다.
  • Compiler Options에 –-target-player=10.0.0 을 삽입한다.
  • HTML wrapper의 Require Flash Player version을 10.0.0으로 맞춘다.

만약 Flex SDK 3.2가 없다면 다운로드 받아 C:\Program Files\Adobe\Flex Builder 3\sdks 내부에 복사해두고 메뉴에서 Windows->Preferances로 들어가 아래와 같은 새창이 뜨면 좌측 카테고리에서 Flex->Installed Flex SDKs를 선택한다. 우측에 Add버튼을 눌러 sdk경로를 맞춰준 다음 Flex 3.2를 기본으로 선택해준다.

 

 

위 환경 설정을 모두 맞추면 에러가 발생하지 않을 것이다.

실행후 아래와 같이 Error: foo 메시지가 나왔다면 프로그램이 제대로 실행된 것이다.

 

 

Alchemy Tools에 대해

Alchemy SDK에는 많은 명령 실행을 위한 도구가 포함되어 있다. 여기에는 C/C++파일을 Flash와 호환하기 위한 디버깅 도구 등도 포함한다. 더욱 자세한 내용은 Developing with Alchemy:Tools를 참고하도록 한다.

 

1. 별칭(Alias)

$ALCHEMY_HOME 디렉토리 하위에 존재하는 alchemy-setup 파일을 열어 아래 부분에 살펴보면 다음 설정이 존재한다. 이 부분은 별칭(alias)을 설정하는 것으로 alc-home, alc-on, alc-off 을 등록하도록 한다.

alias alc-home=’cd $ALCHEMY_HOME’
alias alc-on=’export PRE_ALCHEMY_PATH=$PATH; export PATH=$ALCHEMY_HOME/achacks:$PATH’
alias alc-off=’export PATH=$PRE_ALCHEMY_PATH’

alc-home : Alchemy를 설치한 디렉토리로 이동한다. 본 문서에 설치하는 방법대로 설치하면 “cd /alchemy” 한 것과 같은 동작이다.

 

alc-on/alc-off : Alchemy 동작 On/Off 기능이다. alc-on을 실행하면 $ALCHEMY_HOME/achacks 내에 있는 gcc 빌드 관련 도구를 이용하게 되며 alc-off를 실행하면 원래 gcc를 이용하게 한다.

 

아래는 alc-home, alc-on, alc-off 명령어를 이용할 때 상태를 보여준다. alc-home 명령어 실행시 Alchemy 설치 디렉토리로 이동한 것을 확인하자. 그리고 alc-on/alc-off 명령어를 실행시에 어떤 gcc를 이용하게 되는지 확인한다.

 

 

하지만 이 문서대로 착실하게 따라온 사람은 아래처럼 나올 것이다. 그 이유는 .bashrc 파일 설정과 관련 있다. “PATH=$ALCHEMY_HOME/achacks:$FLEX_HOME/bin” 는 강제적으로 $ALCHEMY_HOME/achacks로 경로로 잡아주기 때문에 alc-on, alc-off 명령어가 적용되지 않는 것이다. 만약 alc-on, alc-off 명령을 적용하길 원한다면 $ALCHEMY_HOME/achacks을 빼고 Cygwin을 다시 실행하면 되겠다.

 

 

환경변수에 경로가 잘 설정되었는지 확인하기 위해 echo $PATH를 아래와 같이 이용해봐도 되겠다.

2. $ALCHEMY_HOME/achacks 에 포함된 도구

이 경로에 포함된 도구는 앞서 설명했듯이 gcc 빌드 관련 도구들이다. alc-on 명령어를 실행할 때 사용할 수 있다.

gcc : 매우 중요한 명령으로 다음과 같은 기능을 수행한다.

  • 라이브러리 경로를 /usr/local을 $ALCHEMY_HOME/usr/local 지정한다.
  • LLVM 바이트코드를 생성하는 llvm-gcc와 llvm-g++를 호출한다.
  • ActionScript를 생성하는 적절한 매개변수를 가지는 llvm 도구를 호출한다.
  • ABC(ActionScript Byte Code) 파일을 생성하기 위해 Alchemy ActionScript 컴파일러를 호출한다.
  • “-swc” 옵션이 있는가 없는가에 따라 ABC파일을 SWF 또는 SWC로 패키지화 한다.
    -swc는 매우 중요한 옵션으로 SWC로 패키지화 한다는 것을 의미한다. 기본값은 실행가능한 SWF를 생성한다. 결과가 exe(실행파일)이지만 실행파일 헤더에 swfbridge와 연결하기 위한 shabang이 있다.

    모든 다른 옵션은 llvm-gcc로 전달된다.

    g++ : 전달된 인수를 그대로 gcc에 전달한다.
    uname : 운영체제가 FreeBSD 6.2에 있는 것처럼 작동한다.
    pkg.pl, tmpl.pl, swctmpl.swf : SWF와 SWC를 생성을 도와주는 역할을 한다.
    기타 다른 스크립트 : 기본적으로 $ALCHEMY_HOME에 대한 경로를 /usr에 맵핑하는 스크립트이다.

 

3. $ALCHEMY_HOME/bin 에 포함된 도구

Alchemy 설정을 수행하면 이 경로가 포함이 된다(alchemy-setup과 .bashrc 파일 참고). 사용중인 다른 도구에 간섭받지 않도록 설정되어 있다.

 

alc-util : Alchemy에 영향을 미치는 환경 변수들을 보여준다. 아래는 실행한 모습이다.

 

swfbridge : Alchemy에서 생성된 실행 파일을 실행하는데 사용한다. 기본 목적은 ADL(AIR Debug Launcher)를 사용하여 SWF를 시작할 수 있도록 임시 AIR 애플리케이션을 만드는 것이다. 두 번째 목적은 로컬 소켓 연결을 위해 특정 API를 호출하는 것이지만 향후 배포에서 제외될 가능성이 있다.

 

Alchemy 샘플 디렉토리($ALCHEMY_HOME/samples/HelloFlash)로 가면 아래와 같이 swfbridge를 간단하게 테스트할 수 있는 코드(HelloFlash.c)가 있다. 일반 c코드와 동일하다.

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello Flash!\n");
}

이 코드를 실행하기 위해 다음과 같이 실행한다.

cd $ALCHEMY_HOME/samples/HelloFlash
gcc HelloFlash.c
./a.exe

 

이처럼 Alchemy를 통해 생성된 exe파일은 swfbridge에서 실행된다. swfbridge는 exe파일의 head에 있는 shabang에 의해 실행된다.

 

gluegen : 적은 노력으로 C/C++ 라이브러리를 ActionScript로 만들기 위한 컴파일 도구이다. 확장자가 .gg를 가지며 이를 응용한 예제는 2부에서 소개하도록 하겠다.

zpipe : 이 도구는 Zlib 압축 알고리즘을 사용하여 표준 입력(STDIN)을 통해 압축 또는 압축 풀기를 하여 표준 출력(STDOUT)으로 내보내는 역할은 한다. 2개의 옵션이 있다. zpipe -h는 도움말을 보여주고 zpipe -d는 압축풀기(decompress)를 하도록 한다.

예제 : zpipe <uncompressed> compressed

zpipe.pl : zpipe와 같은 역할을 하지만 Perl과 Compress::Raw::Zlib에서 구현된다. 사용법은 동일하다.

ExplSWF.pl : 전달된 SWF를 확장한다. SWF의 태그가 현재 디렉토리에 파일로 내보내진다.

예제 : ExplSWF.pl src.swf

ImplSWF.pl : 전달된 SWF를 압축한다. 현재 디렉토리 내에 태그 파일이 SWF에 포함된다.

예제 : ImplSWF.pl dst.swf

GetABC2.pl : SWF에서 ABC를 추출한다.

예제 : GetABC2.pl src.swf dst.abc

PutABC2.pl :원본이 되는 SWF를 주어진 ABC로 대체된 새로운 SWF를 만든다.

예제 : PutABC2.pl src.swf dst.swf newabc.abc

PutBIN.pl : 원본이 되는 SWF를 주어진 바이너리 부분으로 대체된 새로운 SWF를 만든다.

예제 : PutBIN.pl src.swf dst.swf data.bin symname

ShrSWF.pl : SWF 파일을 압축한다.

예제 : ShrSWF.pl src.swf dst.swf

V10.pl : SWF 버전 태그를 10으로 한다.

예제 : V10.pl src.swf dst.swf

 

 

1부를 정리하며

 

지금까지 Adobe Alchemy 개발환경 구축과 함께 간단한 예제 및 Alchemy 도구들에 대해 소개했다. 2부에서는 Adobe Alchemy 공식페이지에서 제공하는 라이브러리를 활용하고 간단한 응용을 통해 Alchemy에 대해 심층적으로 학습해보는 시간을 가지도록 하겠다.

 

관련 페이지

추가사항 1 : Alchemy 속도 테스트(잘못된 예제)

 

이 글을 통해 많은 분들이 Alchemy 수행속도 및 내부구조에 대해서 관심을 보였습니다. 기대에 부응코자 Alchemy가 Flash 애플리케이션에 어느 정도 속도향상을 줄 수 있는가 알아보기 위해 간단한 테스트를 했습니다.

 

    추가사항 2 : Alchemy 속도 테스트 및 깊이 이해하기

    제가 GCC에 대해서 별로 접근해본 적이 없어서 위 Alchemy 속도 테스트에서 잘못된 예제를 보여드렸네요. 최적화에 대한 잘못된 지식으로 인해 잘못된 테스트로 글을 적은 점 사과하며 아래글도 함께 보시면 이해에 도움이 되겠습니다.
  • Adobe Alchemy 수행속도 테스트와 깊이 이해하기

Adobe Flex에서 Visual Component에 스타일을 입히기 위해 MXML형태의 <mx:Style></mx:Style>을 자주 사용할 것이다.

 

<mx:Style>을 이해하기 앞서 먼저 Flex의 컴파일 과정에 대해서 언급하겠다. 알다시피 Flex의 결과물은 Flash 컨텐츠인 SWF파일이다. Flex로 만들어진 결과물은 컴파일 단계에서 모두 ActionScript 3.0으로 변환(Generate)되고 변환된 ActionScript 3.0 파일을 가지고 SWF가 만들어진다. 즉, Flex에서 유용하게 사용되는 MXML은 UI 디자인의 편의성을 제공하기 위해 사용되는 것이지 실제 컴파일 단계에서는 직접적으로 사용되지 않는 언어이다. 

 

<mx:Style> 변환에 대한 이해

 

MXML에서 <mx:Style>로 싸여진 것도 ActionScript 3.0 으로 변환한다는 것을 이제 짐작할 수 있을 것이다. 실제로 어떻게 ActionScript 3.0으로 변환되는지 확인하기 위해 Flex Builder에서 Flex Project를 만들고 생성된 프로젝트의 컴파일러 옵션에 –keep-generated-actionscript를 넣어주자. 이 옵션은 Flex 컴파일시 사용되는 mxmlc에 지정하는 옵션이다. 더 많은 옵션은 “About the application compiler options”를 참고하면 되겠다.

 

 

 

위 옵션대로 넣어주면 프로젝트의 src폴더에 generated 폴더가 생성된다.

 

 

 

generated 폴더를 살펴보면 아래 그림처럼 ActionScript 3.0 파일이 생성된 것을 확인할 수 있다.

 

 

 

아래 코드 처럼 Application에 어떤 visual component도 삽입하지 않은 상태에서 GenerateCSSTest-generated.as 파일을 살펴보자. (프로젝트명을 GenerateCSSTest로 했기 대문에 GenerateCSSTest-generated.as 가 된 것이다.)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
</mx:Application>

 

아래 코드는 MXML로 만들어진 Application을 ActionScript 3.0으로 변환된 GenerateCSSTest 클래스이다.(GenerateCSSTest-generated.as). 우리가 집중적으로 살펴보아야 할 부분은 <mx:Style>이 어떻게 사용되는가 알아내는 것이므로 코드상에서 _GenerateCSSTest_StylesInit() 부분에서 관련성을 찾아낼 수 있을 것이다. 이 애플리케이션이 실행하게 되면 GenerateCSSTest 생성자에서 _GenerateCSSTest_StylesInit()를 호출하므로 <mx:Style>에서 적용한 CSS가 여기서 적용될 것이다라는 것을 예측할 수 있다.

public class GenerateCSSTest
    extends mx.core.Application
{

    //    instance variables

    //    type-import dummies

    //    Container document descriptor
private var _documentDescriptor_ : mx.core.UIComponentDescriptor =
new mx.core.UIComponentDescriptor({
  type: mx.core.Application
})

    //    constructor (Flex display object)
    /**
     * @private
     **/
    public function GenerateCSSTest()
    {
        super();

        mx_internal::_document = this;

        //    our style settings

        //    ambient styles
        mx_internal::_GenerateCSSTest_StylesInit();

        //    properties
        this.layout = "vertical";

        //    events

    }

    //    initialize()
    /**
     * @private
     **/
    override public function initialize():void
    {
         mx_internal::setDocumentDescriptor(_documentDescriptor_);

        super.initialize();
    }

    //    scripts
    //    end scripts

    //    supporting function definitions for properties, events, styles, effects

    //    initialize style defs for GenerateCSSTest

    mx_internal static var _GenerateCSSTest_StylesInit_done:Boolean = false;

    mx_internal function _GenerateCSSTest_StylesInit():void
    {
        //    only add our style defs to the StyleManager once
        if (mx_internal::_GenerateCSSTest_StylesInit_done)
            return;
        else
            mx_internal::_GenerateCSSTest_StylesInit_done = true;

        var style:CSSStyleDeclaration;
        var effects:Array;

        StyleManager.mx_internal::initProtoChainRoots();
    }

    //    embed carrier vars
    //    end embed carrier vars

//    end class def
}

 

이제 아래 코드처럼 Application안에 Button과 TextArea 컴포넌트를 삽입하고 각각에 대해서 Type Selector와 Class Selector로 CSS를 <mx:Style> 태그안에 적용해보자.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Button label="버튼1"/>
    <mx:TextArea text="Flex가 쪼아~" styleName="myTextArea"/>
    <mx:Style>
        Button
        {
            fontSize:12pt;
        }
        .myTextArea
        {
            fontSize:14pt;
            color:red;
        }
    </mx:Style>
</mx:Application>

 

아래코드는 위처럼 Application에 2가지 컴포넌트와 스타일을 적용했을 때 만들어진 GenerateCSSText 클래스 ActionScript 3.0 코드이다. 파란색 부분은 아무것도 안넣을 때와 비교할 때 추가된 부분이다. MXML로 컴포넌트를 삽입하면 상단 파란색 부분처럼 UIComponentDescriptor의 인스턴스인 _documentDescriptor_에 자동으로 등록시켜준다. 각 컴포넌트에 설정된 속성인 label, text, styleName도 추가되어 있는 것을 볼 수 있다. _documentDescriptor_는 initialize()함수 실행시 적용된다.

 

또한 그 아래로 가보면 우리가 관심을 가지고 있는 <mx:Style>에 작성된 CSS가 어떻게 적용되었나 알 수 있다. 

 

여기 있는 코드는 2개의 CSS Selector를 등록하고 있다.

 

첫번째로 Class Selector인 “.myTextArea” 이다. StyleManager.getStyleDeclaration()을 통해 기존에 “.myTextArea”가 등록되어 있는지 조사한다. 만약 등록이 안되어 있다면 StyleManager.setStyleDeclaration()을 통해 CSSStyleDeclaration의 인스턴스형으로 “.myTextArea”를 등록한다. 그런 다음 <mx:Style>에 지정한 fontSize와 color를 적용하기 위해 CSSStyleDeclaration의 factory함수를 정의한다. 이렇게 해두면 자동적으로 StyleManager에 등록된 Class Selector인 “.myTextArea”를 생성하게 되며 StyleManager는 styleName이 “.myTextArea”로 적용된 컴포넌트는 전부 찾아 적용해준다.

 

같은 방법으로 Type Selector인 “Button”을 등록한다. StyleManager은 Button 컴포넌트로 만들어진 인스턴스를 찾아 fontSize:12pt를 적용해준다.

이처럼 <mx:Style></mx:Style>안에 적용된 CSS는 ActionScript 3.0으로 만들어져 StyleManager 클래스가 담당하도록 Flex 컴파일러가 변경시켜준다. 이것이 핵심이다.

public class GenerateCSSTest
    extends mx.core.Application
{

    //    instance variables

    //    type-import dummies

    //    Container document descriptor
private var _documentDescriptor_ : mx.core.UIComponentDescriptor =
new mx.core.UIComponentDescriptor({
  type: mx.core.Application
  ,
  propertiesFactory: function():Object { return {
    childDescriptors: [
      new mx.core.UIComponentDescriptor({
        type: mx.controls.Button
        ,
        propertiesFactory: function():Object { return {
          label: "버튼1"
        }}
      })
    ,
      new mx.core.UIComponentDescriptor({
        type: mx.controls.TextArea
        ,
        propertiesFactory: function():Object { return {
          text: "Flex가 쪼아~",
          styleName: "myTextArea"
        }}
      })
    ]
  }}
})

    //    constructor (Flex display object)
    /**
     * @private
     **/
    public function GenerateCSSTest()
    {
        super();

        mx_internal::_document = this;

        //    our style settings

        //    ambient styles
        mx_internal::_GenerateCSSTest_StylesInit();

        //    properties
        this.layout = "vertical";

        //    events

    }

    //    initialize()
    /**
     * @private
     **/
    override public function initialize():void
    {
         mx_internal::setDocumentDescriptor(_documentDescriptor_);

        super.initialize();
    }

    //    scripts
    //    end scripts

    //    supporting function definitions for properties, events, styles, effects

    //    initialize style defs for GenerateCSSTest

    mx_internal static var _GenerateCSSTest_StylesInit_done:Boolean = false;

    mx_internal function _GenerateCSSTest_StylesInit():void
    {
        //    only add our style defs to the StyleManager once
        if (mx_internal::_GenerateCSSTest_StylesInit_done)
            return;
        else
            mx_internal::_GenerateCSSTest_StylesInit_done = true;

        var style:CSSStyleDeclaration;
        var effects:Array;

        // myTextArea
        style = StyleManager.getStyleDeclaration(".myTextArea");
        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration(".myTextArea", style, false);
        }
        if (style.factory == null)
        {
            style.factory = function():void
            {
                this.color = 0xFF0000;
                this.fontSize = 14;
            };
        }
        // Button
        style = StyleManager.getStyleDeclaration("Button");
        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration("Button", style, false);
        }
        if (style.factory == null)
        {
            style.factory = function():void
            {
                this.fontSize = 12;
            };
        }

        StyleManager.mx_internal::initProtoChainRoots();
    }

    //    embed carrier vars
    //    end embed carrier vars

//    end class def
}

 

본문에서는 StyleManager 자체의 구조를 설명하는 것은 너무 방대할 것이므로 생략한다. 개인적으로 Flex의 스타일 적용 방식은 매우 유연하고 사용하기 편하다고 생각한다. 그만큼 Flex SDK에 있는 StyleManager가 잘 만들어져 있다는 것을 의미하기도 한다. 시간이 허락된다면 StyleManager를 분석해서 배치된 Visual Component에 어떻게 스타일이 적용되는지 알아보는 것도 흥미롭고 유익한 일일 것이다.

 

컴파일러 옵션인 –keep-generated-actionscript은 MXML을 ActionScript 3.0으로 변환한 코드를 가시적으로 볼 수 있게 함으로써 Flex가 어떻게 동작하는가 아는데 중요한 가이드 역할을 하고 있는 셈이다.

 

컴포넌트의 기본 스타일은 어떻게 적용되는가?

 

이쯤되면 궁금한 점이 또 생긴다.

 

<mx:Style>로 적용하지 않은 컴포넌트들은 어떻게 CSS가 먹히는 것일까? 본인은 처음에 해당 컴포넌트 클래스에 StyleManager를 이용하여 CSS를 만들었을 것이라 생각했다. 하지만 그게 아니였다. Flex SDK에 있는 Button.as를 살펴보길 바란다.(Window XP에 Flex Builder 3 Professonal을 설치한 경우에는 C:\Program Files\Adobe\Flex Builder 3\sdks\3.2.0\frameworks\projects\framework\src\mx\controls에 Button.as 가 있다.) 직접 보면 알겠지만 Button.as안에는 StyleManager에 관련된 내용이 전혀 존재하지 않는다. 그럼 Flex 컴포넌트들의 기본 스타일은 도대체 어떻게 적용되는 것인가?

답은 generated 폴더 안에 있다.

 

generated 폴더 내부에는 Button에 기본 스타일을 적용을 정의한 _ButtonStyle.as 뿐 아니라 _gloabalStyle.as, _ApplicationStyle.as, _dateGridStylesStyle.as 등이 있다.

아래는 _ButtonStyle.as이다. Button의 스킨인 mx.skins.halo.ButtonSkin이 여기서 적용된다. 또한 textAlign도 여기서 적용되는 것을 볼 수 있다. 즉 사용한 컴포넌트에 CSS를 적용하지 않더라도 기본 스타일이 이들 코드를 통해 적용됨을 알 수 있다.

 

앞선 예제에서 Application에서 <mx:Style>에 “Button” Type Selector로 fontSize=12pt가 적용되었으므로 이 로직이 실행이 되고 난 다음에 fontSize=12pt가 적용됨을 감각적으로 알 수 있을 것이다.

public class _ButtonStyle
{

    public static function init(fbs:IFlexModuleFactory):void
    {
        var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration("Button");
        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration("Button", style, false);
        }
        if (style.defaultFactory == null)
        {
            style.defaultFactory = function():void
            {
                this.paddingTop = 2;
                this.textAlign = "center";
                this.skin = mx.skins.halo.ButtonSkin;
                this.paddingLeft = 10;
                this.fontWeight = "bold";
                this.cornerRadius = 4;
                this.paddingRight = 10;
                this.verticalGap = 2;
                this.horizontalGap = 2;
                this.paddingBottom = 2;
            };
        }
    }
}

 

방금 언급한 Style관련 ActionScript 3.0 코드들(_ButtonStyle.as, _gloabalStyle.as, _ApplicationStyle.as, _dateGridStylesStyle.as 등)은 Flex SDK의 SystemManager 클래스에 등록되어 사용된다. generated 폴더를 살펴보면 SystemManager 클래스를 확장한 _GenerateCSSTest_mx_managers_SystemManager 클래스의 ActionScript 3.0 코드가 존재한다. 아래 코드를 살펴보자.

public class _GenerateCSSTest_mx_managers_SystemManager
    extends mx.managers.SystemManager
    implements IFlexModuleFactory
{
    public function _GenerateCSSTest_mx_managers_SystemManager()
    {

        super();
    }

    override     public function create(… params):Object
    {
        if (params.length > 0 && !(params[0] is String))
            return super.create.apply(this, params);

        var mainClassName:String = params.length == 0 ? "GenerateCSSTest" : String(params[0]);
        var mainClass:Class = Class(getDefinitionByName(mainClassName));
        if (!mainClass)
            return null;

        var instance:Object = new mainClass();
        if (instance is IFlexModule)
            (IFlexModule(instance)).moduleFactory = this;
        return instance;
    }

    override    public function info():Object
    {
        return {
            compiledLocales: [ "en_US" ],
            compiledResourceBundleNames: [ "containers", "core", "effects", "skins", "styles" ],
            currentDomain: ApplicationDomain.currentDomain,
            layout: "vertical",
            mainClassName: "GenerateCSSTest",
            mixins: [ "_GenerateCSSTest_FlexInit", "_alertButtonStyleStyle", "_ScrollBarStyle", "_activeTabStyleStyle", "_textAreaHScrollBarStyleStyle", "_ToolTipStyle", "_advancedDataGridStylesStyle", "_comboDropdownStyle", "_ContainerStyle", "_textAreaVScrollBarStyleStyle", "_linkButtonStyleStyle", "_globalStyle", "_windowStatusStyle", "_windowStylesStyle", "_activeButtonStyleStyle", "_errorTipStyle", "_richTextEditorTextAreaStyleStyle", "_CursorManagerStyle", "_todayStyleStyle", "_dateFieldPopupStyle", "_plainStyle", "_dataGridStylesStyle", "_ApplicationStyle", "_headerDateTextStyle", "_ButtonStyle", "_popUpMenuStyle", "_swatchPanelTextFieldStyle", "_opaquePanelStyle", "_weekDayStyleStyle", "_headerDragProxyStyleStyle" ]

        }
    }
}

info() 메소드를 override해서 mixins에 CSS를 적용한 클래스의 이름들을 전부 Array형태로 담고 있다. SystemManger는 이 mixins에 등록된 클래스들을 전부 스캔하며 CSS를 적용한다.

 

아래코드는 StyleManager 내부의 일부 코드이다. 앞서 설명한 SystemManager를 확장한_GenerateCSSTest_mx_managers_SystemManager 클래스는 info()를 override했기 때문에 해당 mixins값을 전부 얻어올 수 있다. 이 값의 길이만큼 loop를 돌면서 getDefinitionByName()메소드를 통해 해당 클래스 정의되어 있는지 알아내어 그 클래스의 init()함수를 호출한다. _ButtonStyle.as에 init()메소드에 StyleManager를 가지고 Button의 Type Selector를 등록했다는 것을 확인할 수 있었다. 결국 StyleManager로 Style을 적용한 클래스들의 init()함수를 모두 호출함으로써 기본 CSS가 애플리케이션에 적용되는 것이다.

        var mixinList:Array = info()["mixins"];
        if (mixinList && mixinList.length > 0)
        {
            var n:int = mixinList.length;
            for (var i:int = 0; i < n; ++i)
            {
                // trace("initializing mixin " + mixinList[i]);
                var c:Class = Class(getDefinitionByName(mixinList[i]));
                c["init"](this);
            }
        }

 

 

상속되는 CSS 속성은 어떻게 적용될까?

 

CSS 속성중에는 상속되는 것과 상속되지 않는 것이 있다. 가령 fontSize, color, fontFamily 등은 상속이 된다. 한 예로 아래코드처럼 Application에 color를 blue로 지정해보자. color는 상속되는 CSS속성이기 때문에 Button에 color가 지정되어 있지 않음에도 불구하고 파란색 글자색이 적용된다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Button label="버튼1"/>
    <mx:TextArea text="Flex가 쪼아~" styleName="myTextArea"/>
    <mx:Style>
        Application
        {
            color:blue;
        }

        Button
        {
            fontSize:12pt;
        }
        .myTextArea
        {
            fontSize:14pt;
            color:red;
        }
    </mx:Style>
</mx:Application>

 

 

Button에는 파란색이 적용된 반면 TextArea에는 그대로 빨간색이 적용된 이유는 CSS적용 우선순위 때문이다. 부모(Application)에 적용한 CSS속성값에 자식(TextArea)에서 다시 적용하면 부모에서 적용한 것은 무시된다. 다른 이야기지만 이러한 일을 전부 StyleManager에서 하고 있다는게 대단해보인다. Flex SDK의 강력함이 엿보인다. 만약 Flex SDK를 사용하지 않고 순수하게 ActionScript 3.0으로 코딩해야하는 경우에 CSS를 적용해야한다면… 앞이 깜깜할 뿐이다. 다 만들어야하니 말이다. ^^;

 

이처럼 Flex의 CSS는 상속이 되는 속성들이 있는데 이들 속성도 generated 폴더에 살펴보면 어떻게 등록되어 사용되는지 살펴볼 수 있다. _GenerateCSSTest_FlexInit.as를 살펴보자.

public class _GenerateCSSTest_FlexInit
{
   public function _GenerateCSSTest_FlexInit()
   {
       super();
   }
   public static function init(fbs:IFlexModuleFactory):void
   {
      EffectManager.mx_internal::registerEffectTrigger("addedEffect", "added");
      EffectManager.mx_internal::registerEffectTrigger("creationCompleteEffect", "creationComplete");
      EffectManager.mx_internal::registerEffectTrigger("focusInEffect", "focusIn");
      EffectManager.mx_internal::registerEffectTrigger("focusOutEffect", "focusOut");
      EffectManager.mx_internal::registerEffectTrigger("hideEffect", "hide");
      EffectManager.mx_internal::registerEffectTrigger("mouseDownEffect", "mouseDown");
      EffectManager.mx_internal::registerEffectTrigger("mouseUpEffect", "mouseUp");
      EffectManager.mx_internal::registerEffectTrigger("moveEffect", "move");
      EffectManager.mx_internal::registerEffectTrigger("removedEffect", "removed");
      EffectManager.mx_internal::registerEffectTrigger("resizeEffect", "resize");
      EffectManager.mx_internal::registerEffectTrigger("rollOutEffect", "rollOut");
      EffectManager.mx_internal::registerEffectTrigger("rollOverEffect", "rollOver");
      EffectManager.mx_internal::registerEffectTrigger("showEffect", "show");
      var styleNames:Array = ["fontWeight", "modalTransparencyBlur", "textRollOverColor", "backgroundDisabledColor", "textIndent", "barColor", "fontSize", "kerning", "textAlign", "fontStyle", "modalTransparencyDuration", "textSelectedColor", "modalTransparency", "fontGridFitType", "disabledColor", "fontAntiAliasType", "modalTransparencyColor", "leading", "dropShadowColor", "themeColor", "letterSpacing", "fontFamily", "color", "fontThickness", "errorColor", "fontSharpness", "textDecoration"];

      import mx.styles.StyleManager;

      for (var i:int = 0; i < styleNames.length; i++)
      {
         StyleManager.registerInheritingStyle(styleNames[i]);
      }
   }
}  // FlexInit

앞의 코드에서 볼 수 있듯이 상속되는 Style속성들은 StyleManager의 registerInheritingStyle() 메소드를 통해 등록된다. 아하! 라고 외쳤다면 여러분도 Flex SDK의 StyleManager의 강력함에 찬사를 보내고 있는 것일지도 모르겠다. ^^

참고로 _GenerateCSSTest_FlexInit 클래스는 _GenerateCSSTest_mx_managers_SystemManager의 info() 메소드의 mixins에 등록되어 있어서 SystemManager에서 _GenerateCSSTest_FlexInit 클래스의 info() 메소드를 자동으로 호출하게 된다.

 

 

나만의 Visual Component에 기본 CSS는 어떻게 적용할까?

 

Flex SDK에서 제공하는 Button이나 CheckBox와 같은 컴포넌트는 컴파일시 기본 CSS를 만들어주는 것을 앞서 확인할 수 있었다. 그럼 UIComponent를 확장해서 만든 Visual Component는 어떻게 기본 CSS를 적용할 수 있을까? 생각보다 어렵지 않다. 다음 코드를 보자.

package
{
import com.jidolstar.ui.frames.skins.FrameSkin;
import mx.core.UIComponent;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;

[Style(name="frameSkin", type="Class", inherit="no")]
[Style(name="backgroundColor",type="Number",format="Color",inherit="no")]
[Style(name="backgroundAlpha",type="Number",inherit="no")]
[Style(name="borderColor",type="Number",format="Color",inherit="no")]
[Style(name="borderAlpha",type="Number",inherit="no")]
[Style(name="borderThickness",type="Number",nherit="no")]
[Style(name="paddingLeft",type="Number",nherit="no")]
[Style(name="paddingRight",type="Number",nherit="no")]
[Style(name="paddingTop",type="Number",nherit="no")]
[Style(name="paddingBottom",type="Number",nherit="no")]
[Style(name="cornerRadius",type="Number",format="Color",inherit="no")]

public class MyComponent extends UIComponent
{
    private static var classConstructed:Boolean = classConstruct();
    private static function classConstruct():Boolean
    {
        // 기존에 TagBar이름의 type selector
        // 스타일 선언에 대한 인스턴스를 가져온다.
        var styleDeclaration:CSSStyleDeclaration
            = StyleManager.getStyleDeclaration("MyComponent ");

        // 이전에 스타일 선언이 되어있지 않은 경우라면
        // 스타일선언 인스턴스를 새로 하나 만든다.
        if (!styleDeclaration)
            styleDeclaration = new CSSStyleDeclaration();

        // defaultFactory를 이용해 스타일을 정의한다.
        styleDeclaration.defaultFactory = function ():void
        {
            this.frameSkin = FrameSkin;
            this.backgroundColor = 0xcccccc;
            this.backgroundAlpha = 1;
            this.borderColor = 0×009999;
            this.borderAlpha = 1;
            this.borderThickness = 4;
            this.paddingLeft = 10;
            this.paddingRight = 10;
            this.paddingTop = 10;
            this.paddingBottom = 10;
            this.cornerRadius = 10;
        }

        //선언된 스타일을 적용시킨다.
        StyleManager.setStyleDeclaration("MyComponent",
                  styleDeclaration, false);
        return true;
    }

    override public function styleChanged(styleProp:String):void
    {
        super.styleChanged( styleProp );
        invalidateProperties();
        invalidateDisplayList();
    }
}
}

 

위 코드는 구동되지 않는다. 하지만 어떻게 기본 CSS를 줄 수 있을지 해답을 제공하고 있다. 앞서 설명한 _ButtonStyle.as 에서 적용한 방법대로 private static 코드를 위처럼 만들면 된다. UIComponent를 확장한 MyComponent의 static 값인 classConstructed:Boolean = classConstruct() 부분이 처음 MyComponent가 엑세스 될 때 실행되므로 딱 한번만 CSS적용하는 static으로 정의된 classConstruct() 메소드가 실행된다. 이런 방법으로 기본 CSS를 컴포넌트에 적용시킬 수 있다. 이보다 더 좋은 방법이나 다른 방법이 있다면 소개해주길 바란다.

 

정리하며

 

지금까지 <mx:Style>에 적용한 CSS가 어떻게 ActionScript 3.0으로 변환되어 적용되는가 알아보았다. 여기서는 <mx:Style>만 짚고 넘어갔지만 Flex가 전체적으로 어떻게 돌아가는가 알기 위해서 generated 폴더에 변환된 ActionScript 3.0 파일을 전부 살펴보면서 분석해 봐야 한다. 중요하게 이해하고 넘어가야 하는 것은 Flex의 MXML은 쉽고 빠른 설계를 위해 사용되는 언어라는 점과 MXML은 모두 ActionScript 3.0으로 변환되어 컴파일 된다는 점이다. 그러므로 Flex는 MXML을 통해 ActionScript 3.0의 불편한 하드 코딩을 줄여주고 쉽게 스타일링, 데이터바인딩, 구조설계 등을 할 수 있게 해준 일종의 프레임워크로 이해해야 한다. C나 C++, Java등을 하고 Flex를 처음 접하는 사람들은 MXML과 ActionScript 3.0의 차이점을 알고 어떤 방식으로 Flex 애플리케이션이 돌아가는지 이해하는 과정이 필요한 시점이 반드시 올 것이다. Visual Component들이 어떻게 CSS가 적용되고 어떤 방식으로 Application이 구동되는지 어느 정도 이해해야 한 단계 높은 Flex 프로그래밍을 할 수 있다. 이것이 제대로 된 Flex Component를 설계하기 위한 필요조건이 아닐까?

 

참고글

 

Adobe Flex를 접할때 어려워하는 부분중에 하나가 Event입니다.
깊숙히 이해하려고 할때 잘 이해가 안되는 부분이 많은게 이벤트죠.

기본에 충실해야한다!
프로젝트(http://starpl.com)를 하면서 많이 느꼈습니다.
처음 Flex 접했을때 가장 어려움을 겪었던 것은 기본이 탄탄하지 못한 것으로부터 였습니다.
그래서 Flex와 함께 ActionScript 3.0의 기본 문법 및 대략적 내용을 이해해야한다고 생각합니다.
단순한 문법뿐 아니라 분석/설계에도 관심을 가지고 공부해서 명확하고 깨끗한 프로그램을 만들 수 있도록 노력해야겠구요.

아래는 Event에 대한 질문을 걸어놓고 스스로 답변까지 해두었습니다.
이것으로 Event 전부를 설명할 수 없지만 몇가지 궁금한 사항을 이해하는데는 도움이 될겁니다.
그냥 읽고 지식만 얻어가지 마시고 없는 질문이 있다면 스스로 질문/답변을 댓글로 달아주시면 더욱 고맙겠습니다. 잘못된 내용이나 추가해야한다는 것도 포함합니다.


1. ActionScript 3.0 이벤트(Event)

1.1 ActionScript 2.0과 3.0에서 이벤트가 바뀐점은?

  • ActionScript 2.0에서는 이벤트 리스너를 추가하는 경우 상황에 따라 addListener() 또는 addEventListener()를 사용하지만 ActionScript 3.0에서는 항상 addEventListener()를 사용합니다.
  • ActionScript 2.0에는 이벤트 흐름이 없으므로 이벤트를 브로드캐스팅하는 객체에서만 addListener() 메서드를 호출할 수 있지만 ActionScript 3.0에서는 이벤트 흐름에 포함된 모든 객체에서 addEventListener() 메서드를 호출할 수 있습니다.
  • ActionScript 2.0에서는 함수, 메서드 또는 객체를 이벤트 리스너로 사용할 수 있지만 ActionScript 3.0에서는 함수 또는 메서드만 이벤트 리스너로 사용할 수 있습니다.
  • on(event) 구문은 이제 ActionScript 3.0에서 지원되지 않으므로 ActionScript 이벤트 코드를 무비 클립에 첨부할 수 없습니다. 이벤트 리스너를 추가할 때는 addEventListener()만 사용할 수 있습니다.


1.2 Event를 사용해야하는 이유? 2가지 이상

  • 객체간 강한 결합 해결 : 애플리케이션 설계시 Callback함수를 사용하는 등의 강한결합을 이용하는 경우 유지보수 및 확장이 어려워지는 경우가 크다. 이벤트는 객체간 느슨한 결합을 해주는데 큰 힘을 발휘할 수 있다.
  • 작업에 대한 일관성 없는 응답 : 동기적 방식과 비동기적 방식에 대한 응답에 대한 처리를 이벤트를 사용하지 않는 경우 서로 다른 방법을 선택해서 사용해야한다. 이벤트는 1가지 방법으로 응답처리를 가능하게 해준다.


2. Event의 구성요소 3가지는 무엇이고 의미를 설명하라.

  • 이벤트 객체(Event Object)
    flash.net.Event의 객체나 이를 확장한 이벤트 객체를 의미한다. 이벤트 객체에는 이벤트에 대한 정보가 들어가있다. 이벤트의 종류(Type)과 이벤트를 발생한 객체(target)에 대한 정보가 포함된다.
  • 이벤트 발생자(Event Dispatcher)
    이벤트를 발생(broadcast)하는 객체이다. 이벤트 객체에 target으로 이벤트 발생자 정보가 담긴다. flash.net.EventDispatcher를 확장한 모든 객체는 이벤트 발생자가 될 수 있다. EventDispatcher의 dispatchEvent() 메소드에 이벤트 객체를 담아 실행하면 이벤트가 발생하게 된다.
  • 이벤트 청취자(Event Listener)
    일종의 메소드 또는 함수이다. 발생한 이벤트를 듣는 역할을 한다. EventDispatcher클래스를 확장한 클래스의 객체에서 addEventListener()을 이용해 이벤트 청취자를 등록할 수 있다. addEventListener()을 이용하면 청취할 이벤트의 종류(type)을 결정할 수 있으며 청취할 이벤트 흐름(capture,target,bubbling) 및 우선순위등을 선택할 수 있다.


3. Event Listener를 등록/삭제하는 방법을 기술하라.

  • 등록
    EventDispatcher의 addEventListener() 메소드를 이용한다.
    예)
    var button:Button = new Button();
    button.addEventListener( MouseEvent.CLICK, onClick, false );
    public function onClick( event:MouseEvent )
    {
      trace( event.type, event.target  );
    }
  • 삭제
    EventDispathcer의 removeEventListener() 메소드를 이용한다.
    예)
    button.removeEventListener( MouseEvent.CLICK, onClick, false );
    3개의 인자가 addEventListener()를 사용할때와 같아야 정상적으로 이벤트 청취자 등록이 삭제된다.
    참고로 맨 마지막 인자는 useCapture이다.


4. Event 전파단계의 종류 3가지가 무엇이며 각각에 대한 설명

  • 이벤트 전파단계의 종류 : 캡쳐(capture) 단계, 타겟(target) 단계, 버블(bubble)단계
  • 이벤트 전파 순서 : 캡쳐->타겟->버블
  • 캡쳐 단계
    루트(root) 디스플레이 객체서부터 이벤트를 발생한 객체(target)까지 디스플레이 객체 리스트(display Object List)를 따라 검색해나가는 단계이다. 즉, 부모->자식으로 이벤트를 전파한다. 이 캡쳐 단계에 이벤트를 청취하기 위해서는 addEventListener()의 3번째 인자인 useCapture가 반드시 true로 되어야 한다. false로 되어 있다면 캡쳐단계가 아닌 타겟과 버블 단계에서 이벤트 청취를 한다.
    캡쳐단계 청취하는 목적은 다양할 수 있다. 가령 중간에 이벤트 전파를 중지시킬때 필요할 수 있다. 이해가 안될 수 있지만 실제 프로젝트를 하면 이런 경우가 다분하다.
    사용 예 )
    button.addEventListener( MouseEvent.CLICK, onClick, true );
  • 타겟 단계
    갭쳐 단계 이후 타겟단계에 도달한다. 타겟 단계는 이벤트를 발생시킨 객체까지 도달했을때 이뤄진다.
  • 버블 단계
    타겟 단계 이후 다시 루트(root) 디스플레이 객체로 이벤트가 전파되는 단계이다. 버블버블 물망울이 수면위로 다시 올라가는 것을 연상하면 되겠다. 캡쳐와 반대로 자식->부모로 이벤트가 전파되는 단계이다.
    이벤트가 버블 단계로 전파되기 위한 조건은 이벤트 송출시 Event의 생성자 인자값중 bubble값을 true로 설정되어 있어야 한다. MouseEvent같은 경우 이 bubble값이 true로 설정되어 있다.
    타겟단계와 버블단계에 이벤트 청취자를 등록하기 위해 addEventListener()의 3번째 인자 useCapture가 false로 되어야 한다. useCapture를 설정하지 않으면 기본값이 false이다.
  • 이벤트 객체가 전파되는 기능이 굳이 필요한 이유
    부모-자식1-자식2....-자식100 의 형태로 디스플레이 객체 리스트가 형성되어있다고 가정하자 자식50에서 자식 100을 마우스 클릭했을때 이벤트를 받고 싶다고 하자. 자식50은 자식100의 존재유무를 모른다(알 수 있지만 알게되면 강한결합이 일어난다.) 이런 경우 이벤트 객체가 전파되는 기능은 매우 유용하게 쓰일 수 있다. 캡쳐나 버블 단계에서 자식50은 자식100에서 발생된 이벤트를 받을 수 있다.
  • flash.display.DisplayObject 클래스는 EventDispatcher를 확장해서 만들어졌다는 것에 주목하자.


5. 4번의 Event 전파는 어떤 환경에서 가능한가?

  • 반드시 디스플레이 객체 리스트(Display Object List) 상에 존재하는 디스플레이 객체(Display Object)여야 이벤트 전파가 가능하다. 그 외의 이벤트 전파는 타겟 단계밖에 없다. 가령, HTTPService나 URLLoader에서 이벤트가 발생하는 경우 이들은 디스플레이 객체가 아니다.
  • 이벤트 전파의 조건
    1. 디스플레이 객체여야 한다.(flash.display.DisplayObject를 확장한 클래스의 객체)
    2. 디스플레이 객체는 디스플레이 객체 리스트에 존재해야 한다. 즉, stage로 부터 addChild되어 있어야 한다.

6. 이벤트 전파시 target과 currentTarget의 차이점은 무엇인가? 예를 들어 설명하라.

  • target은 이벤트를 발생한 객체이고 currentTarget은 이벤트를 청취하는 객체이다.
  • 예시)
    var p:Sprite = new Sprite(); //parent
    var c:Sprite = new Sprite(); //child
    addChild( p );
    p.addChild( c ); //c가 p의 자식인 것에 주목
    p.addEventListener( MouseEvent.CLICK, onClick ); //이벤트 청취를 p가 하는 것에 주목!
    public function onClick( event:MouseEvent )
    {
    trace( event.target, event.currentTarget ); //c를 클릭했을 경우 c, p가 나오고 p를 클릭했을 경우 p,p가 나온다.
    }


7. Event 우선순위에 대해서 간단한 예제를 들어 설명하라.

  • 이벤트 우선순위는 구체적으로 이벤트 청취 우선순위라고 할 수 있다.
  • 이벤트 청취 우선순위는 addEventListener()의 4번째 인자인 priority를 이용한다. 이 값이 클 수록 청취 우선순위가 높아지며 addEventListener()의 등록 순서에 상관없이 우선순위에 따라 이벤트 리스너가 호출된다.
  • 이벤트 청취 우선순위가 유효한 조건
    1. 같은 객체에서 청취한다.
    2. 같은 이벤트 종류(type)을 청취한다.
    3. 같은 useCapure 값을 가진다.
  • ex)
    a.addEventListener(MouseEvent.CLICK, aHandler, false, 3);
    a.addEventListener(MouseEvent.CLICK, bHandler, false, 1);
    a.addEventListener(MouseEvent.CLICK, cHandler, false, 100);

    a객체에 MouseEvent.CLICK 이벤트가 발생시 등록순서에 상관없이 priority값이 가장큰 cHandler가 먼저 호출되고 다음으로 aHandler, bHandler가 호출된다.

8. Event 전파를 중단하는 메소드 2개가 무엇이며 간단한 예제를 들어 각각의 차이점을 설명하라.

  • Event 클래스의 stopPropogation()와 stopImmediatePropogation() 메소드이다.
  • 차이점
    - stopPropogation()
      이벤트 전파를 중단하되 같은 노드의 이벤트를 청취까지는 허락한다.
    - stopImmediatePropogation()
      이벤트 전파를 즉시 중단한다.
  • 예제
    back이 배경 canvas이고 button1이 back에 포함된 button일때
    back.addEventListener(MouseEvent.CLICK, event1Handler);
    back.button.addEventListener(MouseEvent.CLICK, event2Handler);
    back.button.addEventListener(MouseEvent.CLICK, event3Handler);
    back.button.addEventListener(MouseEvent.MOUSE_OUT, outHandler);

    private function event1Handler(e:MouseEvent):void
    {
           trace('back');
    }
    private function event2Handler(e:MouseEvent):void
    {
           trace('button');
           e.stopPropagation();
           e.stopImmediatePropagation();
    }
    private function event3Handler(e:MouseEvent):void
    {
           trace('button2');
    }
    private function event1Handler(e:MouseEvent):void
    {
           trace('out');
    }

    e.stopPropagation(); e.stopImmediatePropagation(); 두가지 다 없을경우
            button
            button2
            back
            out
    e.stopPropagation(); 만 실행
            button
            button2
            out
    e.stopImmediatePropagation(); 만 실행
            button
            out
     


9. addEventListener의  useCapture 인자는 무엇을 의미하는가? 간단한 예제를 들어 설명하라.

  • 이벤트 전파를 청취할때 캡처단계를 청취할지 타겟 및 버블 단계를 청취할지 결정하는 인자이다.
  • 모든 전파 단계에서 청취하기 위해서는 useCapture가 다른 2개의 addEventListener()를 사용해야한다.
  • 예제 생략


10. Sprite, Shape와 같은 클래스에서 어떻게 하면 이벤트를 송출할 수 있는가?

  • dispatchEvent() 메소드를 호출하면 된다.


11. Sprite, Shape와 같이 이벤트 송출할 수 있는 클래스는 근본적으로 어떤 클래스를 상속받았기 때문에 가능한가?

  • Sprite와 Shape는 궁극적으로 DisplayObject를 확장해서 만들어졌으며 DisplayObject는 EventDispatcher를 확장했다. 그러므로 Sprite와 Shape와 같은 클래스는 EventDispatcher의 메소드를 전부 사용할 수있다. 이중에 dispatchEvent()도 사용할 수 있다.


12. IEventDispatcher는 무엇이며 어떤 경우에 이것을 사용해야할까?

  • EventDispatcher의 인터페이스이다.
  • IEventDispatcher를 활용하는 것은 다중상속이 불가능한 AS3에서 유용하다.
    가령, 어떤 클래스가 ProxyObject를 확장해서 만들어져야한다. 그런데 이 클래스는 이벤트도 발생시켜야 하므로 EventDispatcher클래스도 확장해야한다. ActionScript 3.0은 다중상속을 지원하지 않는다.(다중상속은 이점도 있지만 단점도 있다.) 그러므로 이러한 경우에는 주가 되는 클래스인 ProxyObject를 is-a 확장을 하고 EventDispatcher를 has-a 확장을 감행한다음 만들어진 클래스가 EventDispatcher의 기능을 수행한다는 것을 명시적으로 외부에 알려주고 내부적으로 구현시켜주기 위해 IEventDispatcher 인터페이스를 implements 해준다. 이렇게 하면 원하는 기능인 ProxyObject와 EventDispatcher의 기능을 전부 수행하면서도 다중상속의 문제를 극복할 수 있다.
  • ex)
    public class MyProxyObject extends ProxyObject implements IEventDispatcher
    {
      var evnetDispatcher:EventDispatcher = new EventDispatcher();
     
      //내부적으로 IEventDispatcher에 선언한 메소드를 구현한다.  가령...
      public function dispatchEvent( event:Event ):Boolean
      {
         return  eventDispatcher.dispatchEvent( event );
      }
    }


13. preventDefault()와 stopPropagation(), stopImmediatePropagation()의 근본적 차이점은 무엇인가?

  • preventDefault()는 기본 동작(dafault behaviors)를 취소하는데 쓰이는 메소드인 반면 stopPropagation(), stopImmediatePropagation()는 이벤트 전파에만 영향을 준다.
  • preventDefault()가 사용 가능한지는 isDefaultPrevented() 메소드로 알 수 있다.

14. preventDefault()를 호출해서 실행을 가능하게 하기 위한 조건은 무엇인가?

  • 이벤트 객체를 생성시 cancelable속성이 true로 설정되어 있어야 한다.


15. 커스텀 이벤트를 만들어야하는 어떤 경우이며 어떻게 만드는가?

  • 커스텀 이벤트는 기본 이벤트 클래스(flash.events.Event) 또는 그 상위 이벤트 클래스를 확장하는 경우를 의미한다.
  • 이벤트를 확장해야하는 경우는 이벤트 송출시에 필요한 정보를 담아내기 위함이다.
  • 만드는 방법
    class MyEvent extends Event
    {
      public static const MY_EVENT:String = "myevent";
      public var value:String;
      public function MyEvent( type:String, value:String ):void
      {
         super( type, false, false );
         this.value = value;
      }
      overide public function clone():Event
      {
        return new MyEvent( type, value );
      }
    }



16. 커스텀 이벤트가 bubbling이 가능하기 위해 어떤 조건이 필요한가?

  • Event의 생성자의 2번째 인자인 bubble값을 true로 설정한다.

17. 커스텀 컴포넌트 만들때 clone()함수를 override해야하는 이유는 무엇인가?

  • clone()메소드는 기존과 신규 properties들을 clone 내로 설정해 이벤트 객체의 복사본을 돌려준다.
    clone() 메소드를 Event 클래스 확장시 오버라이드 해야하는 이유는 이벤트를 다시 재전달해야하는 경우 묵시적으로 복사해서 재전달할 필요가 있을때 필요하기 때문이다.
  • 참고로 toString()도 확장해서 사용하는 것이 좋다. 이벤트에 대한 명세서가 될 수 있기 때문이다.

18. 약참조에 대해서 GC(가비지 콜렉션)과 관련되어 설명하라.

  • addEventListener()함수로 이벤트 청취자를 등록할때 5번째 인자로 useWeakReference 인자가 있다. 이것을 true로 설정하면 이벤트 청취자가 이벤트 청취를 약참조를 하겠다는 의미이다. 반대로 false(기본)이면 강참조이다.
  • 약참조, 강참조를 설명하기 전에 가비지 콜렉션(GC, Garbage Collection)에 대해서 먼저 알아야한다.
    만들어졌던 객체가 더이상 쓸모가 없어졌을때 GC라고 한다. 쓸모가 없어졌다는 것은 참조의 고리가 끊어졌다는 것을 의미한다. 메모리에 상주해 있지만 조만간 메모리에서 없애야할 대상인 것이다. Flash Player는 GC로 선정된 객체들을 메모리에서 삭제하는 시점을 내부적으로 구현해놓고 있다.(많은 분들이 Flash Player GC처리를 못한다고 하지만 실제로는 문제없이 잘하고 있다. 단지 효율적으로 GC처리를 하는지는 의문이지만...)
    Flash Player GC 대상을 찾아내기 위한 방법은 2가지가 있다. 그것은 레퍼런스 카운팅(reference counting)과 마크 앤 스윕(Mark and Sweep)이다. 레퍼런스 카운팅은 생성된 객체가 참조할때마다 참조 카운트를 1씩 더해준다. 또 참조에서 끊어지는 경우 1씩 감소시켜준다. 만약 레퍼런스 카운팅 값이 0이되는 경우 바로 GC 대상이 되는 것이다. 마크 앤 스윕은 레퍼런스 카운팅 만으로 찾을 수 없는 GC대상을 찾아준다. 가령, 두개 이상의 객체가 서로 다른 객체를 참조하고 있다고 가정한다.(순환참조) 그럼 이들 레퍼런스 카운팅 값은 1 이상이다. 그러므로 레퍼런스 카운팅 방법으로는 GC대상을 만들 수 없다. 마크 앤 스윕은  이들 객체가 최상위 부모와 연결되어 있는지 검사하는 방법이다. 부모로 부터 꼬리에 꼬리를 물고 객체가 참조되어 있는 것인지 조사해나가는 방법으로 GC대상을 검색한다.
    레퍼런스 카운팅과 마크앤 스윕 방법으로 찾아낸 GC대상은 메모리에 상주되지만 Flash Player의 GC 삭제방법 로직에 의해 어느 순간 삭제된다.(이 시점은 프로그래머가 어찌할 수 없다. AIR 또는 Flash Player Debug버전의 경우에만 System.gc()를 이용해 강제적으로 GC를 메모리에서 삭제할 수 있긴 하다.)
  • addEventListener()의 useWeakReference를 true로 설정하면 약참조가 되어 레퍼런스 카운팅을 증가시키지 않는다. 강참조의 경우 이벤트 대상(target)의 참조를 지웠다고 해도 addEventListener로 등록한 청취자 메소드가 GC대상이 아니라면 이벤트 대상의 레퍼런스 카운팅 값은 0이 될 수 없다. 결국 프로그래머의 뜻과 다르게 메모리를 낭비하는 일을 초래할 수 있다. 물론 이벤트 대상이 사라지는 시점에 removeEventListener() 사용하면 되겠지만 때로는 그 시점을 모르는 경우도 발생한다. 이러한 경우 useWeakReference를 true로 설정해서 메모리 낭비를 없앨 수 있겠다.
  • Flex에서 List 계열의 컴포넌트를 살펴보면 dataProvider()에 ArrayCollection 객체를 받을때 ArrayCollection의 변화를 살펴보기 위해 addEventListener()를 사용한다. 그런데 ArrayCollection 객체는 언제 어떻게 될지 List 계열 컴포넌트는 알 수 없을 것이다. 이때 유용하게 사용하는 것이 바로 useWeakReference=true로 설정해서 필요없어진 ArrayCollection이 메모리 남는 것을 방지할 수 있다. 참고 : http://blog.jidolstar.com/375



19. 이벤트가 전파되는 중간에 전파를 멈추게 하기 위해 stopPropagation()을 사용하지 않고removeEventListener() 를 사용하면 안되는 이유를 설명하라. (힌트, ActionScript 3.0의 Native 메커니즘에 입각한다. http://bixworld.egloos.com/2149717)

  • 이벤트 송출을 시작하면 flash player는 이벤트가 송출될 디스플레이 객체 리스트를 전부 조사해 미리 이벤트 객체를 복사해두기 때문이다. 이미 전파할 대상이 모두 정해져있고 전파가 시행중인 경우에는 중간에 removeEventLisener를 사용하더라도 일단 송출된 이벤트는 끝까지 전파된다. 다음 시점 이벤트 송출시에는 removeEventLisener()가 적용되어 있는 상태가 된다.


20. willTrigger()와 hasEventListener()의 의미를 설명하고와 두 메소드의 차이점을 보여줄 수 있는 대한 예를 들어보자.

  • 공통점
    hasEventListener()이나 willTrigger() 모두 이벤트 청취자가 있는지 검사한다.
  • 차이점
    hasEventListener() : 지정한 객체에(만) 이벤트 청취자가 있는지 조사한다. 이벤트 흐름과는 무관하다.
    willTrigger() : 지정한 객체를 포함하는 디스플레이 객체 리스트에 해당 이벤트가 청취자가 있는지 조사한다. 그러므로 이벤트 흐름과 관련 있다.
  • 예제
    var button:Sprite = new Sprite();
    addChild( button );
    this.addEventListener( MouseEvent.CLICK, onClick );
    trace( button.willTrigger( MouseEvent.CLICK ) ); //true
    trace( button.hasEventListener( MouseEvent.CLICK ) ); //false ->왜 false가 되는가 아는게 중요!
    trace( this.willTrigger( MouseEvent.CLICK ) ); //true
    trace( this.hasEventListener( MouseEvent.CLICK ) ); //true
  • 이 두개의 함수를 잘 활용하면 쓸데없이 이벤트를 송출하는 경우를 방지할 수 있다.

21. ActionScript 3.0 이벤트에 대해 참고할 만한 문헌 및 자료를 조사하자.
Introduction to event handling in ActionScript 3.0
Jasu님의 ActionScript 3.0 이벤트 오브젝트
Event 한글 문서
EventDispatcher 한글 문서


글쓴이 : 지돌스타(http://blog.jidolstar.com/415)

재사용/유지보수를 극대화를 위해 다형성, 추상화 개념이 들어간 객체지향프로그래밍을 이용한다.


객체지향프로그래밍(OOP) 5대 원칙도 마찬가지입니다. 결국 재사용/유지보수를 위해서 필요한 원칙입니다. 이런 원칙을 잘 지키는 것은 초보 개발자에게 무척 어려운 일이기도 합니다. 왜냐하면 이해와 설계과정이 함께 들어가는 것이기 때문입니다. 하지만 지속적으로 적용하고 몸에 베면 그리 어려운것만은 아닙니다.


[공부]

http://muticd.com/lec/director/viewBody.php?t_Name=Lec_drbook2

11, 12 동영상을 학습한다.



[과제]


1. 인터페이스(Interface), 추상 클래스(Abstract Class), 구상 클래스(Concrete Class)

1.1 인터페이스란 무엇인가?  

1.2 추상클래스란 무엇인가? 추상클래스의 역할을 정확히 언급하라. 인터페이스와의 차이점을 언급하라.

1.3 구상클래스란 무엇인가?

1.4 AS3에는 추상 클래스의 키워드로 abstract가 없다. 그러나 실제 설계시에는 추상클래스 역할을 하는 클래스가 필요한 경우가 많다. 이런 경우에 대한 적절한 예제를 들고 Class Diagram을 작성하라. (단,추상클래스는 직접 사용할 수 없도록 해야한다.)

1.5 1.4에서 만든 추상클래스를 가지고 구상클래스를 3가지 이상 예제를 들고 Class Diagram을 작성하라.

1.6 1.4, 1.5에서 만든 추상클래스와 구상클래스를 가지고 문제없이 사용할 수 있다. 하지만 다중상송의 경우 문제가 발생할 수 있다. 이런경우 예를 들고 왜 인터페이스-추상클래스-구상클래스를 셋트로 설계하는게 좋은지 설명하라.



2. Open-Close 원칙 (OCP, 개방-폐쇄의 원칙)

2.1 OCP원칙에 의하면 확장 상속(is-a)에 관해서 어떻게 하나?

2.2 OCP원칙에 의하면 합성 상속(has-a)에 관해서 어떻게 하나?

2.3 OCP원칙에 따르기 위해 Class를 어떻게 설계해야하는가?

2.4 OCP 원칙에 맞지 않는 예를 Class Diagram으로 표현하라.

2.5. OCP 원칙에 대한 적절한 예를 Class Diagram으로 표현하라.



3. The Liskov Substitution 원칙(LSP, 리스코프 치환 원칙)

3.1 LSP 원칙에 대해 설명하라

3.2 LSP 원칙에 맞지 않는 적절한 예를 Class Diagram으로 설명하라.

3.3 LSP 원칙에 맞는 적절한 예를 Class Diagram으로 설명하라.



4. The Dependency Inversion 원칙(DIP, 의존 관계 역전 원칙)

4.1 DIP 원칙에 대해서 간단하게 설명하라.

4.2 DIP 원칙에 맞지 않는 예제를 Class Diagram으로 설명하라.

4.3 DIP 원칙에 맞는 예즈를 Class Diagram으로 설명하라.



5. The Interface Segregation 원칙(ISP, 인터페이스 격리 원칙)

5.1 ISP에 대해서 간단히 설명하라.

5.2 ISP에 맞지 않는 예제를 Class Diagram으로 설명하라

5.3 ISP에 맞는 예제를 Class Diagram으로 설명하라.



6. The Single Responsibility 원칙(SRP, 단일 책임의 원칙)

6.1 SRP에 대해서 간단히 설명하라.

6.2 SRP에 맞지 않는 예제를 Class Diagram으로 설명하라.

6.3. SRP에 맞는 예제를 Class Diagram으로 설명하라.



[참고서적]

Flex/AIR Bible (대림, 윤훈남저)

액션스크립트 3.0 디자인 패턴(에이콘, 조이로트.대니 패터슨 저)



이들에 대해서 학습을 완료하면 재사용/유지보수를 위해 아래와 같은 작업을 해야할 것을 느낄겁니다.


1. 인터페이스 또는 추상클래스로 설계한다.

2. 참조시 인터페이스 및 추상클래스를 참조해야한다.(의존성을 줄임)

3. 인터페이스나 추상클래스는 확실한 자기 역할을 부여해라.(유지보수,확장성 향상)


OOP 5대 원리 및 다양성, 추상화 과정에 대해서 이해하고 계속 적용할려고 노력하는 것은 개발의 삽질을 줄여주는데 반드시 필요한 부분입니다. 처음에는 머리 아플지 모르지만 예전보다 이쁘게 만들어진 자신의 프로그램을 보며 웃을날이 분명 올것입니다. ^^


객체기반프로그래밍(OBP, object based programming)이 아닌 객체지향프로그래밍(OOP, object oriented programming)을 하시기 바랍니다.


댓글이 곧 달리겠죠? ^^

글쓴이: 지돌스타(http://blog.jidolstar.com/419)

많은 프로그래머들이 클래스 관계 및 설계에 대해서 명확히 이해하지 않고 손가는데로 프로그램을 만들다가 나중에 유지보수에 어려움을 겪는다.(본인도 예외가 아님) 이러한 문제를 극복하는 취지에서 이번 학습이 필요하다고 생각한다. 물론 Flex의 경우 Flex자체의 내부구조를 이해하고 효율적인 사용이 필요하다. 이 부분은 따로 학습해야할 것이다.


[공부할 것]

클래스들 간에 관계설정

출처:http://cafe.naver.com/javasolution/26


윤훈남의 Flex강의중 10.객체지향프로그래밍-클래스 다이어그램 : http://muticd.com/lec/director/viewBody.php?t_Name=Lec_drbook2


[숙제]

1. 클래스의 관계에서 is a, use a, has a 에 대해서 예시와 함께 설명하라.(예시는 3가지 이상)


2. UML에서 클래스 관계

2.1 "의존관계"의 영문, 의미, UML 표기법 설명, AS3 예시

2.2 "연관관계"의 영문, 의미와 2가지 종류에 대한 설명과 UML 표기법 설명, AS3 예시

2.3 "일반화관계"의 영문, 의미와 UML 표기법 설명, AS3 예시

2.3 "실체화관계"의 영문, 의미와 UML 표기법 설명, AS3 예시

2.4 위 관계에 대해 UML을 작성해보자

  (http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=AmaterasUML 에서 다운받에 Flex Builder에 설치하고 다 만든 결과물을 스크린샷해서 붙인다)


3. 합성과 상속

3.1 의미를 설명하고 AS3로 예시를 들어라.

3.2 각각의 장단점을 설명하고 예시를 들어라.


4. 인터페이스

4.1 인터페이스의 의미와 AS3에서 일반적으로 통용되는 인터페이스 명명규칙

4.2 AS3는 다중상속을 할 수 없다. 하지만 인터페이스를 이용하면 다중상속의 문제점을 해소시키면서 클래스를 만들 수 있다. 예를 들어 설명하라.

4.3 인터페이스는 클래스간에 결합도를 줄여준다. 예를들어 설명하라.

4.4 ActionScript 3.0에서 부모SWF에서 자식SWF를 로드하여 자식 SWF안에 있는 클래스를 사용할 수 있다. 인터페이스를 이용하면 부모와 자식간에 클래스 공유의 결합도를 줄여줄 수 있는데 그 예를 들어보자.



5. 추상화 클래스(AS3는 이런 개념이 없지만 충분히 활용할 수 있다.)

5.1 추상화 클래스의 의미.

5.2 추상화 클래스를 사용해야하는 이유

5.3 인터페이스를 이용해서 추상화 클래스를 만들고 이 추상화 클래스를 이용한 구체화된 클래스에 대한 예제를 만들라(AS3, UML)


6. 캡슐화(은닉화)

6.1 캡슐화의 의미

6.2 캡슐화가 적절하게 된 클래스와 아닌 클래스에 대한 예시를 들어라.(Array 활용)


7. 다형성

7.1 다형성의 의미

7.2 다형성의 의미를 대변할 수 있는 예시(코드/UML)를 들어라.


8. 절차지향 방식과 객체지향 방식의 차이점(장,단점 포함)을 설명하라.


답은 언제 달릴까요? ^^

글쓴이: 지돌스타(http://blog.jidolstar.com)

POST방식으로 MultiPart 컨텐츠 타입으로 지정하여 데이타를 서버에 올리는 경우가 종종 있다.
가령, 이미지를 BitmapData로 캡쳐해서 ByteArray형태로 만든다음 서버에 전송하기 위해서는 URLLoader를 이용해 URLRequest의 contentType을 "multipart/form-data;"으로 지정해서 전송할 수 있다. 이와 관련되서는 예전에
"[Flex/AIR]BitmapData를 PNG나 JPG로 변환하여 ByteArray로 서버에 전송하는 방법" 제목으로 글을 올린적 있으니 참고바란다.

하지만 Flash Player 10부터 이러한 과정에서 보안이슈에 걸린다. 아래는 Flash Player 10 기준 ActionScript 3.0 메뉴얼에서 URLLoader의 load()에 설명되어 있는 글이다.  

Flash Player 10 이상에서는 multipart Content-Type(예: "multipart/form-data")을 사용하고 POST 본문 내 "content-disposition" 헤더에 "filename" 매개 변수를 지정하여 업로드를 처리하는 경우 POST 작업이 업로드에 적용되는 보안 규칙의 영향을 받을 수 있습니다.

  • POST 작업은 마우스 클릭이나 키 누르기 같은 사용자 동작에 대한 응답으로 수행됩니다.
  • POST 작업이 크로스 도메인인 경우, 즉 POST 대상이 POST 요청을 보내는 SWF 파일과 같은 서버에 없는 경우 대상 서버는 크로스 도메인 액세스를 허용하는 URL 정책 파일을 제공해야 합니다.

또한 multipart Content-Type의 경우 구문이 RFC2046 표준에 따라 유효해야 합니다. 구문이 유효하지 않은 경우 POST 작업은 업로드에 적용되는 보안 규칙의 영향을 받을 수 있습니다.


마우스 클릭이나 키 누르기 같은 사용자 동작에 대한 응답이 있은 직후에 하지 않고 이 기능을 사용하게 되면 "SecurityError: Error #2176 Certain actions, such as those that display a pop-up window, may only be invoked upon user interaction, for example by a mouse click or button press." 에러가 발생한다. 이 메시지는 자바스크립트(Flash가 아닌 외부)에서 Flash 컨텐츠의 FileReference browse() 메소드를 같은 이유의 보안 문제로 호출할 수 없을때와 동일하다. 이와 관련되어서는 다음 글을 참고한다.

[Flash Player 10]FileReference의 변경된 보안정책과 새롭게 추가된 기능에 대한 나의 생각

아무튼, Flash Player 10 으로 업그레이드 되면서 보안문제때문에 걸리는게 한두개가 아니다. 이와 관련되서 명확히 이해할 수 있으면 좋겠다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/)

Flash Player 10에 맞게 제작된 ActionScript 3.0 한글문서 입니다.
참조하세요. ^^

http://help.adobe.com/ko_KR/AS3LCR/Flash_10.0/
http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/


글쓴이 : 지돌스타(http://blog.jidolstar.com/410)

+ Recent posts