Component Object Model (COM) Development on Mac OS X

OSXDEV

Jump to: navigation, 찾기

최근에 회사에서 COM (Componet Object Model)을 사용할 일이 생겨서 찾다가, MacOS X에서도 MS의 COM을 이용할 수 있다는 것을 알게 되었습니다. COM의 유용성에 대해선 개인적으론 별로 잘 모르겠어서 그동안 관심을 가지지 않았습니다. 아마 어떤 분들은 "굳이 COM을 이용해야 하나"라는 생각을 가지고 계실 것입니다. 저도 개인적으론 그렇지만 남들이 그렇게 쓰기 때문에, 그런 코드를 보고 이해하려면, 그리고 그들과 소통을 하려면 알아야 할 것입니다. 특히나 이미 C++과 같은 OOP 언어만을 주로 사용해서 개발된 코드가 있다면 더욱 그러할 것입니다. 하지만 여러 언어를 쓰는 환경에 있다면, 더군다나 한국의 개발자들이 아무래도 교육 자체를 Windows로 받게 되는 경우가 많아서 Windows에서 얻은 지식을 Mac에서도 그대로 쓸 수 있다면, 그 효과는 배가될 것입니다. 그런 측면에서 Mac에 COM을 이용할 수있다는 것은 큰 뉴스거리가 아닐 수없습니다.

요새 BootCamp를 이용해서 Mac H/W에서 윈도우즈를 구동할 수있게 되었는데, 개발자 입장에서는 Windows에서 Mac 프로그래밍이 가능하거나, Mac에서 Windows에서 구동될 수있는 프로그래밍이 가능한 것이 아마 더 구미를 자극하지 않을까 싶은데, 그래서 그런지 요새 은근히 Cocoa for Windows에 대한 소문이 은근히 나오고 있습니다. 혹자는 Apple이 H/W 사업을 포기하는게 아니냐는 소리도 하고 있습니다만, 개발자 입장에서는 자신이 시간을 들인 지식을 곧바로 쓸 수있고, 저렴한 투자로 계속 그 지식을 쓸 수있다면 그보다 더 좋은 것은 없겠습니다. 이런 측면에서도 COM에 대한 소식은 뭔가 희망을 갖게 하는 것 같습니다.

여기서 다룰 내용은 MS의 COM을 맥에서 쓸 수있다는 것입니다. 만약 MS의 COM 아닌 플랫폼에 종속적이지 않은 COM에 대해서 알아보시려면 Mozilla의 XPCOM, http://www.mozilla.org/projects/xpcom/,을 참조하시기 바랍니다.

목차

[편집] 대체 COM이 뭘까?

COM은 프로그램을 만들때, 코드를 다시 사용하기 위해서 제공하는 메커니즘입니다. COM을 이용하면 OOP의 encapsulation, 상속, 그리고 polymorphism같은 것을 프로그래밍 언어에 상관하지 않고 이용할 수있습니다.

어디서 많이 들어본 소리입니다. 이미 OOP 언어에선 지원하고 있는 것 아닙니까? 이미 C++이나 Objective-C와 같은 OOP 언어를 사용하고 있다면, 왜 굳이 COM이라는 새 메커니즘을 이용해야 하는가라는 생각을 할 수있습니다. 저도 그렇게 생각합니다. 하지만 COM의 장점은 "프로그래밍 언어에 상관하지 않고"라는 점이 되겠습니다.

추가 : 뭔가 더 장점이 있을까 해서 cocoa 메일링 리스트와 usenet에 글을 올렸는데 답변들이 왔습니다. 원래 질문에 대한 답보다는 서로들간에 새로운 토픽으로 와전되기는 했는데, 개중에 질문에 대한 답이 있었습니다. 엄밀하겐 제가 듣고자 했던 답은 아닌데, 여기 정리해 보겠습니다.

COM을 사용하는 이유는

  • code substitute : 코드의 재 사용보다는 다른 코드로 치환을 할 수있다는게 더 큰 장점이랍니다. 즉 이 말은 그냥 OOP 언어에서 지원하는거나 dll같은 것을 이용해서 하면 fragile base class의 문제가 있다고 합니다. 즉 그 코드를 가져다 쓰는 측이 아니라, 만든 측에서 그 코드를 변경하면, 가져다 쓰는 측도 재컴파일을 해야하는데, COM을 쓰면 재 컴파일할 필요가 없다는 것입니다. 결국 궁극적인 모듈러 프로그래밍이 가능하다는 소리인데, 사실 제가 물어 본 입장은 이걸 몰라서가 아니라, 이상하게도 굳이 COM을 쓸 필요가 없는 상황인데도 COM으로 굳이 쓰려고 하는 시도들을 많이 봐서 입니다. 어떠면에선 "새 기술에의 중독"으로 설명할 수가 있겠습니다.
  • version : COM은 인터페이스가 변할때, 버전 정보를 넣을 수있습니다. 그러므로 쓰는 측에서 버젼을 확인하고서 해당 클래스 구조라던가 메소드가 있는지 알 수있습니다.
  • 언어 중립성 : 언급된 내용이라 설명은 안하겠습니다.

사람들 답변이 Objective-C Runtime에 기반한 COM이 나오지 않은게 신기하다라고 합니다. 아시다시피 COM에서 제공하는 많은 것들이 이미 Objective-C에선 언어에서 기본적으로 제공합니다. 여기에 추가적인 것들만 덧대면 되는데, 왜 그런게 안나왔는가.. 아무래도 개발자 집단의 크기와, Apple사의 노력 부족등을 이야기 하더군요. 의외로 사람들이 Objective-C를 좋아하는 이유가 이런 기능들을 이미 복잡한 C++에 COM과 같은 것을 덧대서 하지 않고도 어느 정도 다 된다라는 이유가 있더군요.

그리고 비록 Core Foundation이 MS의 COM 인터페이스를 이용하지만, Mac에선 Mac의 방식이 있다는 것도 언급해 두겠습니다.


또한 COM 오브젝트가 실행 화일에 있건, DLL에 있건, 혹은 다른 컴퓨터에 라이브러리 형태로 있건 상관하지 않고 가져다 쓸 수있는 location transparency를 제공하는 장점도 있습니다.

[편집] MacOS X서의 COM

Mac에서는 COM이 Core Foundation 프레임워크의 한 부분으로 구현되어 있다고 합니다. 이렇게 된 이유는 Core Foundation의 Plugin 구조를 지원하기 위해서라고 합니다. 그래서 Mac에서는 in-process COM 서버만이 지원됩니다. 다른 말로 하자면 dynamic link library 속에서만 COM을 구현할 수있다는 말입니다.

Windows에서는 location transparency 지원하는데, 좀 기능이 제약되는거 아닌가 하지만, 대부분의 COM 객체가 Windows에서도 dynamic link library에 구현된다는 것을 보면 뭐 괜찮은 거라고 봅니다.

Windows와의 차이점은 Mac에서는 COM 객체를 registry에 기록해 놓을 필요가 없다는 것입니다. Windows서는 registry에 등록을 해놓으면, 어디에 그 COM 객체가 있는지 알기때문에 그저 CoCreateInstance를 호출하면 사용하고자하는 COM의 인스턴스를 만들 수있습니다.

Mac에서는 원한다면 여러분만의 레지스트리를 만들고 CoCreateInstance를 구현해도 되겠지만, 보통은 dynamic link library를 수동으로 메모리에 로딩합니다. Windows에서도 이렇게 할 수있습니다. 즉 같은 코드를 맥과 윈도우즈에서 쓸 수있다는 것입니다.

이제까지 설명한 것을 정리하자면, dynamic link library에 COM 컴포넌트를 구현하고, 그것을 수동으로 로드한다음에 쓴다는 것입니다.

[편집] COM Programming 예

이제 구체적으로 Mac에서 COM 프로그래밍을 어떻게 하는지 예를 통해서 알아봅시다. ( 이 예제는 http://www.macdevcenter.com/mac/2004/04/16/examples/OSXCOM.zip에서 받으실 수있습니다. )

[편집] 1. COM Client 예
// Initialise COM

CoInitialize(0);

// Build our server's complete filespec into the UTF16 
// format (Unicode) required by Automation

unsigned short* theServerSpecP = UTF16Create(
                        ".."
                        "/.."
                        "/Sample Carbon COM Server"
                        "/build"
                        "/server.bundle"
                        );

// Load our servers type lib so that we can create 
// instances of our COM interfaces

ITypeLib* theTypeLibraryP;
HRESULT theResult = LoadTypeLib(
                            theServerSpecP, 
                            &theTypeLibraryP
                            );
delete[] theServerSpecP;
theServerSpecP = 0;     // No longer required

if (SUCCEEDED(theResult))
{
    // Here's the meat of it - now we've done the hard 
    // bit by loading our type lib, we can create 
    // instances using it - this is a standard 
    // Automation thing. First we obtain an ITypeInfo 
    // instance for our PhoneDialer class. ITypeInfo 
    // 'describes' a class i.e. provides meta 
    // information.
    
    ITypeInfo* theInterfaceTypeP;
    theResult = theTypeLibraryP->GetTypeInfoOfGuid(
                                    CLSID_PhoneDialer,                
                                    &theInterfaceTypeP 
                                    );

    if (SUCCEEDED(theResult))
    {
        // Now we have our type info for IPhoneDialer, 
        // we can create instances of the interface 
        // we're interested in.
        
        IPhoneDialer* thePhoneDialerP;
        theResult = theInterfaceTypeP->CreateInstance(
                            0, 
                            IID_IPhoneDialer, 
                            reinterpret_cast>void**<(
                                &thePhoneDialerP
                                )
                            );
        if (SUCCEEDED(theResult))
        {
            // Now call on our interface to do something
            // interesting. This is what all of the 
            // effort is about!
            
            thePhoneDialerP->Dial("1234");
            
            // We're done with our interface so release
            
            thePhoneDialerP->Release();
        }
        
        // We're done with our type info now so release
        
        theInterfaceTypeP->Release();
    }

    // We're done with our type lib now so we release

    theTypeLibraryP->Release();
}

// Finish up COM

CoUninitialize();
[편집] 2. COM 인터페이스를 만들기

앞에서는 만들어져 있는 COM 객체를 불러 쓰는 예였습니다. 그럼 그런 COM 객체 자체를 만드는 법도 알아야 할것입니다. 여기서는 그런 객체를 만들기 위해서, COM 인터페이스를 작성하는 예제를 보겠습니다. C++을 이용하겠지만, 다른 언어를 이용할 수도 있습니다.

// IPhoneDialer.h

// 9B5FD3E4-83A2-11D8-8ED2-000393C360A2
DEFINE_GUID(
    CLSID_PhoneDialer,  
    0x9B5FD3E4, 
    0x83A2, 
    0x11D8, 
    0x8E, 0xD2, 0x00, 0x03, 0x93, 0xC3, 0x60, 0xA2
    );

// AE809768-83A2-11D8-B171-000393C360A2
DEFINE_GUID(
    IID_IPhoneDialer,  
    0xAE809768, 
    0x83A2, 
    0x11D8, 
    0xB1, 0x71, 0x00, 0x03, 0x93, 0xC3, 0x60, 0xA2
    );

class IPhoneDialer : public IUnknown
{
  public:

    // IPhoneDialer methods
    
    STDMETHOD(Dial(char* inPhoneNumP)) PURE;
};

첫 두 라인에서는 클래스를 정의하고 그 인터페이스의 unique identifier를 선언하고있습니다. COM 객체를 인스턴스화하기 위해서, 그 COM 객체를 지정하고자 할때 씁니다. 이 ID는 터미널에서 사용하는 uuidgen이라는 툴을 이용해서 만듭니다.

보통 Universally Unique Identifiers(UUIDs)나 Globally Unique Identifiers(GUIDs)라는 말을 쓰는데 MS의 COM에서는 GUID란 말을 씁니다.

클래스를 선언함으로써, 인터페이스를 정의합니다. 모든 COM 인터페이스는 IUnknown이라는 인터페이스에서 상속을 받습니다. 그렇게 함으로써, query를 할 수있고 또한 몇개나 그런 객체가 있는지 reference count를 이용해서 알 수있습니다. 이것은 흡사 Cocoa의 NSObject에 있는 그런 메커니즘과 같습니다.

위의 예에서는 Dial이라는 한개의 메소드를 선언했는데, 이때 STDMETHOD라는 방식으로 되었습니다. 또한 PURE라고 되어 있어서, 추상적인 것으로 선언되었는데, 이것을 이용하는 클라이언트 프로그램은 COM을 이용해서만 이 인터페이스를 인스턴스화 할 수있도록 해 주는 것입니다. 이렇게 하면 COM이 인스턴스화하는 것을 관장하게 되므로 reference count를 유지/관리할 수있게 해 줍니다.

[편집] 3. COM 서버를 만들기

이렇게 COM 인터페이스를 선언했으면, 이제 COM 서버를 만들 수있습니다. 이때 COM 서버란, COM 컴포넌트를 가지게 되는 dynamic link library를 의미합니다.

Mac OS X에서 COM 서버를 만들려면 Xcode의 프로젝트 템플렛 중 하나인 CFPlugin Bundle 템플렛을 이용해서 합니다. 이렇게 만들어진 프로젝트는 다음과 같습니다.


이 파일 중 PhoneDialer.h에선 IPhoneDialer의 concrete한 subclass 즉 추상 클래스가 아닌 보통의 클래스를 선언합니다. 또한 class factory와 이 서버에 포함된 COM 객체의 숫자를 세주기 위한 counter도 선언합니다.

PhoneDialer.cpp는 IPhoneDialer 컴포넌트를 구현한 파일입니다. 여기선 Dial 메소들르 구현하며, COM 객체가 어떻게 reference counting이 되는지, 그리고 어떻게 query하는지, 그리고 어떻게 class factory가 이 COM 객체를 인스턴스화 하는지를 명시합니다. 이 파일에 있는 내용이 제일 중요하니, 집중적으로 보시기 바랍니다.

server.h는 PhoneDialer factory의 Core Foundation UUID를 선언합니다. 이 UUID는 Server.cpp에서만 씁니다.

Server.cpp는 PhoneDialer factory를 만들기 위해 필요한 모든 것을 제공하며, 이 factory를 이용해서 만든 컴포넌트를 관리합니다. CFPhoneDialer는 Core Foundation과 연동하기 위한 PhoneDialer의 서브 클래스이며, 어떤 factory가 자신을 만들었는지 기록합니다. Core Foundation을 이용해서 만든 컴포넌트를 등록하는게 중요한데, 이렇게 함으로써, Mac OS X가 더 이상 이 라이브러리를 필요로하지 않으면 unload 시킬 수있습니다. CFPhoneDialer에서 이 메커니즘을 제공합니다.

Carbon COM은 COM,Automation 헤더 파일, 그리고 플랫폼에 상관하지 않는 COM 컴포넌트를 만들기 위한 구현을 담고 있습니다.

Carbon COM 프로젝트는 Core Foundation과 CoreServices 프레임워크에 의존하는데, Core Foundation은 CFPlugin을 관리하기 위해서, CoreServices는 쓰레드 간에 메모리 관리를 하기 위해서 필요합니다.

여러분이 직접 COM을 구현하시려면 다음과 같은 부분을 바꾸면 됩니다.

PhoneDialer.h : 새 파일 이름, 여러분만의 인터페이스와 GUIDs PhoneDialer.cpp : 새 파일 이름, 그리고 여러분만의 인터페이스 Server.h : 새 UUID Server.cpp : 여러분의 인터페이스 파일을 include하고, PhoneDialer를 만들어준 인터페이스 이름으로 바꿉니다. 또한 필요한 다른 인터페이스들에 대한 factory들을 선언할 수있습니다. Carbon COM : 기능을 확장할게 아니라면 바꾸지 마십시오.

또한 Core Foundation Plugin 메커니즘을 이용하기 때문에 info.plist에서 바꿀 것이 있습니다. 기본적으로 server에 factory들의 UUID를 선언하고, 어떤 함수가 factory 생성을 관장하는지, 그리고 COM 클래스의 UUID가 factory들에 어떻게 매핑되는지등을 기록하면 됩니다. 다음의 예를 보십시오.

<key>CFPlugInFactories</key>
<dict>
    <key>AB3831E4-83AB-11D8-B989-000393C360A2</key>
    <string>PluginFactory</string>
</dict>
<key>CFPlugInTypes</key>
<dict>
    <key>9B5FD3E4-83A2-11D8-8ED2-000393C360A2</key>
    <array>
        <string>AB3831E4-83AB-11D8-B989-000393C360A2</string>
    </array>
</dict>


[편집] 5. COM Client 만들기

이렇게 만들어진 COM 객체를 사용하는 Client를 만드는 것은 좀 더 쉽습니다. 다음의 그림에선 COM client 프로젝트의 한 예를 보여줍니다.


역시 사용하고자 하는 COM 객체의 인터페이스를 선언해야 합니다. 이것은 iPhoneDialer.h에서 합니다. 이 파일이 서버와 클라이언트간에 공유되는 파일입니다.

main.cpp는 플랫폼에 종속적이지 않는 방식으로 어떻게 COM 객체를 이용하는지 보여줍니다.

Carbon COM은 COM과 Automation 헤더 파일, 그리고 서버당 구현을 담고있습니다.

Mac OS X에서 COM 클라이언트를 만드는 것은 대부분이 Xcode의 템플렛에서 이미 다루어집니다. 여기선 C++ Tool 템플렛을 이용했습니다. 역시 서버와 마찬가지로 Core Foundation과 CoreServices 프레임워크를 사용합니다.

이 클라이언트를 수행하면 PhoneDialer COM 컴포넌트를 만들고 Dial 메소드를 호출합니다.

[편집] 마지막으로

이렇게 함으로써 Windows와 Mac 간의 COM으로 구현된 코드를 공유할 수있습니다. 보통의 경우에 있어서 portable한 코드는 오픈 소스나 standard library를 주로 이용해서 하게 되는데, 여기에 MS의 COM을 쓸 수있다는 것은 이미 있는 Windows 코드를 포팅할때 아주 유용하겠습니다.

이 글은 Oreilly의 MacDevCenter에 있는 것을 재작성한 것입니다. 원래의 글은 http://www.macdevcenter.com/pub/a/mac/2004/04/16/com_osx.html?page=1에서 보실 수있습니다.

(작성: 박종암) P.S. Oreilly 측에서 이의를 제기하면 글을 내리겠습니다.