자체엔진 제작 포트폴리오

[WinAPI] COM의 구조와 인터페이스 파헤쳐보기

doyyy_0 2025. 6. 6. 10:23

Graphics (Direct2D)Text ,

(DirectWrite),

The Windows Shell,

The Ribbon control,

UI animation

등은 Com(Component Object Model)을 기반으로 한다. 

Common Item Dialog는 Com기반으로 구현된 Windows Shell API인데, 구조를 코드로 파헤쳐보고자한다.

 

간단히 다음 코드를 보자.

IFileOpenDialog* pFileOpen;

// Common Item Dialog 객체 생성 및 IFileOpenDialog 인터페이스 포인터 획득
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
	IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

 

이 코드는 Common Item Dialog 객체를 생성한 후, 그 객체가 구현한 인터페이스 중 하나인 IFileOpenDialog 포인터를 pFileOpen에 반환한다.

 

 

 

 

https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface

 

 

COM 구조상, Common Item Dialog 객체는 IUnknown을 루트로 하여 IModalWindow → IFileDialog → IFileOpenDialog의 인터페이스 계층을 상속하고 있으며, 동시에 별도로 IFileDialogCustomize 인터페이스도 구현하고 있다.

 

COM의 모든 인터페이스는 직접적 또는 간접적으로 반드시 IUnknown을 상속하며,
이를 통해 QueryInterface, AddRef, Release 등의 표준 메서드를 공유한다.

 

다음 코드는 해당 구조를 단순화하여 C++로 흉내 낸 예제이다:

 

#include <iostream>
#include <string>

struct IUnknown {
    virtual void QueryInterface(const char* interfaceName, void** ppvObject) = 0;
    virtual void AddRef() = 0;
    virtual void Release() = 0;
    virtual ~IUnknown() {}
};

// ========== 왼쪽 줄기 ==========
struct IModalWindow : public IUnknown {
    virtual void ShowModal() = 0;
};

struct IFileDialog : public IModalWindow {
    virtual void SetTitle(const char* title) = 0;
};

struct IFileOpenDialog : public IFileDialog {
    virtual void OpenFile() = 0;
};

// ========== 오른쪽 줄기 ==========
struct IFileDialogCustomize : public IUnknown {
    virtual void AddButton(int id, const char* label) = 0;
};

// ========== 최종 객체 ==========
class CommonItemDialog : public IFileOpenDialog, public IFileDialogCustomize {
    int refCount = 1;

public:
    void QueryInterface(const char* interfaceName, void** ppvObject) override {
        if (std::string(interfaceName) == "IFileOpenDialog") {
            *ppvObject = static_cast<IFileOpenDialog*>(this);
            AddRef();
        }
        else if (std::string(interfaceName) == "IFileDialogCustomize") {
            *ppvObject = static_cast<IFileDialogCustomize*>(this);
            AddRef();
        }
        else {
            *ppvObject = nullptr;
        }
    }

    void AddRef() override {
        refCount++;
        std::cout << "[AddRef] refCount = " << refCount << "\n";
    }

    void Release() override {
        refCount--;
        std::cout << "[Release] refCount = " << refCount << "\n";
        if (refCount == 0) {
            std::cout << "[Delete] CommonItemDialog destroyed.\n";
            delete this;
        }
    }

    // IModalWindow
    void ShowModal() override {
        std::cout << "[ShowModal] Dialog shown modally.\n";
    }

    // IFileDialog
    void SetTitle(const char* title) override {
        std::cout << "[SetTitle] Title set to: " << title << "\n";
    }

    // IFileOpenDialog
    void OpenFile() override {
        std::cout << "[OpenFile] Opening file...\n";
    }

    // IFileDialogCustomize
    void AddButton(int id, const char* label) override {
        std::cout << "[AddButton] Button added: " << id << ", " << label << "\n";
    }
};


int main() {
    // Common Item Dialog 객체 생성 (보통 CoCreateInstance가 하는 역할)
    CommonItemDialog* rawDialog = new CommonItemDialog();

    // IFileOpenDialog 인터페이스 얻기
    IFileOpenDialog* pOpen = nullptr;
    rawDialog->QueryInterface("IFileOpenDialog", reinterpret_cast<void**>(&pOpen));

    if (pOpen) {
        pOpen->SetTitle("파일 열기");
        pOpen->ShowModal();
        pOpen->OpenFile();
        pOpen->Release(); // 참조 해제
    }

    // IFileDialogCustomize 인터페이스 얻기
    IFileDialogCustomize* pCustomize = nullptr;
    rawDialog->QueryInterface("IFileDialogCustomize", reinterpret_cast<void**>(&pCustomize));

    if (pCustomize) {
        pCustomize->AddButton(1, "확인");
        pCustomize->Release(); // 참조 해제

    }

    // rawDialog는 refCount가 1로 시작했고, 두 번 AddRef → 두 번 Release 되어 refCount == 1
    // 이제 마지막 Release를 호출하여 객체를 파괴
    rawDialog->Release();

    return 0;
}

 

이렇게 인터페이스를 연속으로 상속받는 구조고, 이름을 넣었을때 QueryInterface가 기능을 탐색해주는 구조이다. Com의 특징인 인터페이스의 구조를 알 수있는부분이다(구현과 호출을 분리), 또한 런타임 때 QueryInterface를 통해 어떤 인터페이스를 사용할지 결정할 수 있다. 따라서 재사용하기 편하게 모듈화가 되어있다고 볼 수있다.