COM이란 무엇인가 - Windows COM 설계가 지금도 아름다운 이유

· 업데이트: · · COM, ActiveX, Windows 개발

수정 이력(8건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다. 수정 전 버전 보기 (DOI: 10.5281/zenodo.21635075)
리뷰 지적에 따라, 오늘 추가한 그림 가운데 너비가 너무 컸던 것을 세로 구성으로 고치고, 일부 그림과 캡션 표현을 본문 설명에 맞게 바로잡았습니다. 본문 문장은 바꾸지 않았습니다.
세 요소와 강점의 관계, 참조 카운트의 원칙, C#에서 본 계약, In-proc과 Out-of-proc의 대비 등을 그림으로도 따라갈 수 있도록 Mermaid 그림 5개를 추가했습니다(본문 500~750자당 그림 1개 규약에 맞춘 것입니다). 본문 문장은 바꾸지 않았습니다.
글 맨 앞에 '이 글의 지식 맵' 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
바이너리 계약의 구조와, QueryInterface로 구·신 조합을 보이는 그림을 추가했습니다. 본문 설명은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응해 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참고하세요.
코드 예 장을 새로 만들었습니다. C++ raw COM으로 `CoCreateInstance`부터 `QueryInterface`, `Release`까지를 보이고(`E_NOINTERFACE`가 정상 경로인 점을 포함), 같은 GUID를 쓰는 C# 버전을 나란히 실었습니다. In-proc과 Out-of-proc 대비표, 세 요소와 네 가지 강점의 대응표, COM과 현대 기술의 대응표를 추가했습니다.
최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635074)

이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.

Go Komura (2026). 「COM이란 무엇인가 - Windows COM 설계가 지금도 아름다운 이유」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635074 https://comcomponent.com/ko/blog/why-com-is-beautiful/

DOI(최신 버전)
10.5281/zenodo.21635074
DOI(이 버전)
10.5281/zenodo.22217395

COM이란?

COM(Component Object Model)은 Windows에서 컴포넌트끼리 주고받기 위한 ‘바이너리 계약’입니다. 언어나 컴파일러가 달라도, 인터페이스라는 엄격한 계약으로 통신하는 메커니즘이며, 밑바탕에는 ‘구현이 아니라 계약을 대상으로 프로그래밍한다’는 설계 사상이 있습니다.

이 글의 지식 맵

COM은 Windows에서 컴포넌트끼리 주고받기 위한 바이너리 계약으로, 모든 인터페이스가 상속하는 IUnknown이 QueryInterface에 의한 기능 확인과 AddRef/Release에 의한 참조 카운트 관리를 제공합니다. CLSID와 IID라는 GUID로 컴포넌트와 인터페이스를 유일하게 식별함으로써 이름 충돌을 피하고, 기존 인터페이스를 바꾸지 않고 새 인터페이스를 추가해 QueryInterface로 확인하는 설계가 버전 공존을 실현합니다. 호출하는 쪽과 같은 프로세스에 로드하는 In-proc(DLL 서버)은 비트 수의 일치가 필수이며 상대의 크래시에 함께 죽지만, 별도 프로세스에서 동작하는 Out-of-proc(LocalServer)은 그 제약에서 자유로운 대신 상대가 사라졌을 때의 RPC_E_DISCONNECTED 같은 연결 끊김 오류에 대한 대비가 필요합니다. HRESULT라는 반환값에 의한 성공·실패 표현과 Proxy/Stub, IDL에 의한 계약의 사전 정의가 이 구조 전체를 언어와 프로세스의 경계로부터 독립시킵니다.

COM 설계가 아름다운 이유의 지식 맵COM이 IUnknown에 의한 참조 카운트와 QueryInterface, CLSID·IID를 통한 식별을 토대로 바이너리 호환성·인터페이스 분리·버전 공존·프로세스 경계를 넘는 재사용이라는 강점을 실현하고, In-proc과 Out-of-proc의 배치 형태나 HRESULT, .NET과의 상호운용으로 이어지는 관계를 보여주는 그림이용한다이용한다이용한다이용한다이용한다이용한다구현을 담당한다전제로 한다구현을 담당한다전제로 한다이용한다이용한다전제로 한다원인이 될 수 있다방지한다원인이 될 수 있다이용한다이용한다이용한다이용한다이용한다이용한다전제로 한다COM(컴포넌트 오브젝트 모델)IUnknownQueryInterfaceCOM 참조 카운트(AddRef/Release)CLSID(Class ID)IID(인터페이스 식별자)HRESULTCOM 바이너리 호환성COM 버전 공존In-proc COM(DLL 서버)COM LocalServer(별도 프로세스 COM 서버)비트수 일치 요건In-proc 크래시 동반 다운Out-of-proc COM의 연결 끊김 오류Proxy/StubIDL(인터페이스 정의 언어).NET의 COM 상호 운용(COM Interop)ProgID(Programmatic Identifier)ActiveX

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 23건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

COM의 세 가지 중요한 요소

이 장에서 다루는 것은 COM이라는 메커니즘을 구성하는 부품입니다. 이어지는 ‘COM의 네 가지 강점’은 이 부품들을 조합한 결과로 얻는 성질이므로, 같은 이야기를 두 번 하는 것이 아닙니다. 대응 관계는 강점 장의 끝에 표로 정리합니다.

1. 인터페이스 중심 설계

COM에서는 ‘구현보다 계약이 먼저’입니다. 객체의 내부 구현을 몰라도, 공개된 인터페이스만 알면 사용할 수 있습니다.

2. GUID(CLSID / IID)로 식별

모든 컴포넌트와 인터페이스에는 세계에서 유일한 ID(GUID) 가 부여되므로, 이름 충돌이 애초에 일어나지 않습니다.

3. IUnknown

모든 COM 인터페이스가 상속하는 기본 인터페이스입니다. 다음 세 기능을 제공합니다.

메서드 역할
QueryInterface 다른 인터페이스를 갖고 있는지 묻는다
AddRef 참조 카운트를 늘린다
Release 참조 카운트를 줄인다(0이 되면 자신을 소멸시킨다)

이 세 요소가 맞물리면, 호출 측과 구현 사이에 남는 것은 ‘GUID로 식별되고, 메서드 나열 순서가 정해진 인터페이스’뿐입니다. 언어도 컴파일러도 이 계약에는 나타나지 않습니다.

구현 ── 언어는 가리지 않는다호출 측 ── 언어는 가리지 않는다C++로 작성한 컴포넌트C#으로 작성한 컴포넌트C++ 앱C# 앱VBA / Python 등계약(바이너리로 고정된 부분)·IID(GUID)로 '어느 계약인지'를 고유하게 정한다·IUnknown의 메서드 3개부터 시작하는 나열 순서·메서드별 인자·반환값 타입과 호출 규약

그림 1: COM의 바이너리 계약. 호출 측 언어도 구현 언어도 계약에는 나타나지 않으므로, 어느 쪽을 바꿔도 상대를 다시 빌드할 필요가 없습니다

코드로 보는 ‘계약을 대상으로 프로그래밍한다’

글로만 보면 잘 와닿지 않으니, 최소 코드로 만들어 봅니다. 덧셈만 하는 컴포넌트 ICalcService를, 구현을 전혀 모른 채 쓰는 예입니다.

먼저 C++(raw COM)부터입니다. 여기에 적힌 ICalcService 정의가 계약 그 자체이며, 구현이 C++로 쓰였는지 C#으로 쓰였는지는 호출 측 코드 어디에도 나오지 않습니다.

#include <objbase.h>

// 계약의 정의. 보통은 IDL에서 이 형태의 헤더가 생성된다
// IUnknown을 상속하므로 QueryInterface / AddRef / Release를 반드시 가진다
struct __declspec(uuid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001"))
ICalcService : public IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE Add(int a, int b, int* result) = 0;
};

// 나중에 추가된 확장판 계약. GUID는 다른 것이 된다
struct __declspec(uuid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B002"))
ICalcServiceEx : public ICalcService
{
    virtual HRESULT STDMETHODCALLTYPE Multiply(int a, int b, int* result) = 0;
};

// 구현 컴포넌트의 CLSID. 보통은 IDL에서 생성되는 헤더에서 정의된다
static const CLSID CLSID_CalcService =
    { 0x1C9B6F4D, 0x1E9A, 0x4E61, { 0x9A, 0x4F, 0x6A, 0x0F, 0x1D, 0x2D, 0x9A, 0x11 } };

// 호출 측
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) { return hr; }

ICalcService* calc = nullptr;
hr = CoCreateInstance(CLSID_CalcService, nullptr, CLSCTX_ALL,
                      __uuidof(ICalcService), reinterpret_cast<void**>(&calc));
// 여기서 돌아온 포인터는 AddRef가 끝난 상태(참조 카운트는 1)

if (SUCCEEDED(hr))
{
    int sum = 0;
    hr = calc->Add(1, 2, &sum);          // 구현을 몰라도 호출할 수 있다

    // '확장판 계약도 갖고 있는지'를 실행 시점에 묻는다 = 버전 공존의 구현
    ICalcServiceEx* calcEx = nullptr;
    if (SUCCEEDED(calc->QueryInterface(__uuidof(ICalcServiceEx),
                                       reinterpret_cast<void**>(&calcEx))))
    {
        // 새 버전 컴포넌트였을 때만 여기로 온다
        calcEx->Release();               // 받은 쪽이 돌려준다
    }
    // 갖고 있지 않으면 E_NOINTERFACE가 돌아올 뿐이고, 이전 버전에서도 계속 동작한다

    calc->Release();                     // 참조 카운트가 0이 되어 소멸된다
}
CoUninitialize();

읽어야 할 곳은 세 가지입니다.

  • AddRef를 직접 쓰는 장면은 거의 없습니다. CoCreateInstanceQueryInterface도, 돌려주는 포인터에 대해 이미 AddRef를 마친 상태입니다. 즉 ‘포인터를 받은 쪽이 Release한다’가 원칙이고, AddRef를 명시적으로 호출하는 것은 같은 포인터를 한 곳 더 유지하기 시작할 때뿐입니다.
  • QueryInterface의 실패는 이상이 아닙니다. ‘그 계약은 갖고 있지 않습니다’(E_NOINTERFACE)는 정상적인 답이며, 이것이 ‘오래된 컴포넌트를 깨지 않고 새 기능을 더한다’를 성립시킵니다.
  • 반환값은 모두 HRESULT입니다. 예외가 아니라 반환값으로 성공 여부를 알리는 것은, 언어를 넘나들기 위해 필요한 약속입니다. 예외를 던지는 방식은 언어마다 다르지만, 정수 반환값이면 누구나 해석할 수 있습니다.
참조 카운트와 Release의 원칙CoCreateInstance나 QueryInterface는 AddRef가 끝난 포인터를 돌려주므로, 받은 쪽이 다 쓰면 Release를 호출하고, 참조 카운트가 0이 되면 객체가 소멸된다는 원칙을 나타내는 그림.AddRef를 명시CoCreateInstance나 QueryInterfaceAddRef가 끝난 포인터가 돌아온다받은 쪽이 사용한다받은 쪽이 Release를 호출한다참조 카운트가 0이 되면 소멸같은 포인터를 한 곳 더 유지

그림 2: 포인터를 받은 쪽이 Release하는 것이 원칙이며, AddRef를 명시하는 것은 유지 지점을 늘릴 때뿐입니다.

같은 계약을 C#에서 쓰면 이렇게 됩니다. GUID가 같으면 같은 계약으로 보므로, 상대가 C++ 구현이어도 상관없습니다.

using System;
using System.Runtime.InteropServices;

// C++ 측과 같은 GUID를 적는다. 이것이 '같은 계약'의 선언
[ComImport]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
    int Add(int a, int b);
}

// 호출 측
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService")
         ?? throw new InvalidOperationException("COM 서버가 등록되어 있지 않습니다.");
object server = Activator.CreateInstance(t)!;
try
{
    var calc = (ICalcService)server;   // 이 캐스트가 QueryInterface에 해당한다
    int sum = calc.Add(1, 2);
    Console.WriteLine(sum);            // 3
}
finally
{
    Marshal.ReleaseComObject(server);  // Release에 해당한다
}

C# 쪽에서 Add의 반환값이 int가 되는 것은, .NET의 COM 상호 운용이 ‘마지막 [out, retval] 인수를 반환값으로 만들고, 실패한 HRESULT는 예외로 변환한다’는 정해진 변환을 해 주기 때문입니다. AddRef/Release도 호출 측에서는 보이지 않게 되지만, 사라진 것이 아니라 .NET이 마련한 얇은 래퍼(RCW)가 대신 호출합니다. 같은 계약을, 언어마다 자연스러운 작성 방식으로 쓸 수 있다──이것이 ‘바이너리 계약’의 실제 모습입니다.

C#의 작성 방식과 COM의 대응C#에서는 인터페이스로의 캐스트가 QueryInterface에, Marshal.ReleaseComObject가 Release에 해당하고, 실패한 HRESULT는 예외로 변환되며, AddRef와 Release는 RCW가 대신 호출한다는 대응을 나타내는 그림.C# 코드타입으로의 캐스트ReleaseComObject실패 HRESULTQueryInterface에 해당Release에 해당예외로 변환된다RCW가 참조 카운트를 대신 처리

그림 3: 같은 계약이어도, C# 쪽에서는 언어마다 자연스러운 작성 방식으로 바뀌고, 변환은 RCW와 상호 운용 계층이 맡습니다.

COM의 네 가지 강점

1. 바이너리 호환성

한 번 빌드한 컴포넌트는 프로그래밍 언어나 런타임에 상관없이 재사용할 수 있습니다. C++로 만든 COM 컴포넌트를 C#이나 Python에서 호출하는 일이 흔합니다.

2. 인터페이스 분리

구현을 완전히 숨기고 계약만 공개하므로, 내부 구현을 자유롭게 바꿔도 호출 측에는 영향이 없습니다.

3. 버전 공존

하위 호환성을 유지한 채 기능을 추가하려면 새 인터페이스를 추가해 가는 설계가 기본입니다. 오래된 인터페이스를 바꾸지 않고 새 기능을 제공할 수 있습니다.

구·신 호출 측과 구·신 컴포넌트를 조합하면, 네 가지 중 세 가지는 그대로 동작하고, 남은 한 가지도 ‘갖고 있지 않다’는 답이 돌아올 뿐입니다.

QueryInterface(IID_ICalcService)S_OKQueryInterface(IID_ICalcService)S_OK ── 갱신해도 깨지지 않는다QueryInterface(IID_ICalcServiceEx)S_OK ── 새 기능을 쓸 수 있다QueryInterface(IID_ICalcServiceEx)E_NOINTERFACE ── 이전 기능으로 계속오래된 호출 측ICalcService만 안다새 호출 측ICalcServiceEx를 물어 본다이전 버전 컴포넌트ICalcService만 구현새 버전 컴포넌트양쪽을 구현

그림 4: 기존 인터페이스를 바꾸지 않고 새 IID를 더하면, 구·신 어느 조합에서도 계속 동작합니다. 점선은 ‘그 계약은 갖고 있지 않다’는 정상적인 답입니다

4. 프로세스 경계를 넘는 재사용

COM 컴포넌트를 두는 위치는 두 종류입니다. 호출 측과 같은 프로세스에 DLL로 로드하는 In-proc(DLL 서버) 와, 별도 프로세스의 EXE로 시작하는 Out-of-proc(EXE 서버, LocalServer) 입니다. 호출하는 코드의 겉모습은 어느 쪽이든 바뀌지 않습니다.

컴포넌트를 두는 두 위치겉모습이 같은 호출 코드에서, 호출 측과 같은 프로세스에 DLL로 로드하는 In-proc과, 별도 프로세스의 EXE로 시작하는 Out-of-proc 어느 쪽으로도 이어진다는 것을 나타내는 그림.호출 측 코드(겉모습은 같다)In-proc(DLL 서버)Out-of-proc(EXE 서버)같은 프로세스에 DLL로 로드한다별도 프로세스의 EXE로 시작

그림 5: 두는 위치는 In-proc과 Out-of-proc 두 종류이지만, 호출하는 코드의 겉모습은 바뀌지 않습니다.

  In-proc(DLL 서버) Out-of-proc(EXE 서버)
실행되는 위치 호출 측과 같은 프로세스 별도 프로세스
호출의 실제 함수 포인터를 통한 직접 호출 인수를 다시 포장해(마샬링) 프로세스 간 통신으로 넘긴다
속도 빠르다 프로세스 간 통신만큼 느리다
상대가 죽었을 때 호출 측도 함께 죽는다 호출 측은 살아남는다(호출이 실패로 돌아온다)
비트 수(32/64bit) 일치하지 않으면 로드할 수 없다 달라도 된다

Out-of-proc COM(EXE 서버)을 쓰면 다른 프로세스의 기능을 안전하게 호출할 수 있습니다. ‘32bit/64bit가 달라도 된다’는 마지막 행은 실무에서 도움이 되는 경우가 있으며, 32bit 앱에서 64bit DLL을 쓰는 구체적인 구성은 ‘32bit 앱에서 64bit DLL을 호출하는 COM 브리지 사례‘에서 다룹니다.

다만 별도 프로세스라는 것은 상대가 언제든 사라질 수 있다는 뜻이기도 합니다. 서버 프로세스가 크래시하거나 종료하면, 호출 측에는 다음과 같은 실패가 돌아옵니다. 이는 ‘망가졌다’가 아니라 ‘상대가 더 이상 없다’를 나타내는, Out-of-proc 특유의 오류입니다.

오류 코드 의미
RPC_E_DISCONNECTED 호출하려던 객체가 클라이언트에서 분리되어 있다(상대 객체가 이미 없다)
RPC_S_SERVER_UNAVAILABLE 호출 대상 서버(프로세스)에 도달할 수 없다

In-proc이라면 ‘죽으면 나도 죽는다’라서 신경 쓰지 않아도 됐던 이야기가, Out-of-proc에서는 재시작이나 재연결의 복구 처리로 설계에 들어옵니다. 안전성과 맞바꿔 늘어나는 일이라고 보는 것이 정확합니다.

상대 프로세스가 사라졌을 때의 흐름Out-of-proc에서는 서버 프로세스가 크래시나 종료로 사라지면 호출이 실패로 돌아오고, 호출 측은 살아남으므로, 재시작이나 재연결의 복구 처리를 설계에 넣어야 한다는 흐름을 나타내는 그림.서버 프로세스의 크래시·종료호출이 실패로 돌아온다호출 측은 살아남는다재시작·재연결의 복구 처리로망가진 것이 아니라 상대가 더 이상 없다

그림 6: Out-of-proc에서는 상대가 사라져도 함께 죽지 않는 대신, 복구 처리가 설계에 들어옵니다.

세 요소와 네 가지 강점의 대응

서두에서 말한 대로, ‘세 가지 요소’는 부품이고 ‘네 가지 강점’은 그 결과입니다. 어느 부품이 어느 강점을 받치는지 늘어놓으면, 겹쳐 보이던 것들의 역할 분담이 분명해집니다.

강점 주로 작용하는 요소
1. 바이너리 호환성 인터페이스 중심 설계 + IUnknown(호출의 나열 순서가 바이너리 수준에서 정해진다)
2. 인터페이스 분리 인터페이스 중심 설계(계약만 공개한다)
3. 버전 공존 GUID + QueryInterface(다른 계약을 실행 시점에 물을 수 있다)
4. 프로세스 경계를 넘는 재사용 인터페이스 중심 설계(구현의 위치까지 계약에서 떼어 낼 수 있다)

COM은 지금도 현역

‘오래된 기술’로 여겨지기 쉽지만, COM은 Windows의 핵심에서 지금도 계속 쓰이는 메커니즘입니다.

COM이 쓰이는 곳

  • 탐색기 확장(우클릭 메뉴, 미리보기)
  • Office 자동화(Excel이나 Word의 외부 제어)
  • .NET과의 상호 운용(COM Interop)
  • ActiveX를 포함한 기존 시스템
  • DirectX, Windows Shell API 등 다수의 Windows API

‘나와는 상관없다’고 생각해도, Windows 개발을 하는 한 COM은 어딘가에 등장합니다.

정리

COM 설계의 중심은 ‘언어·프로세스·구현으로부터의 독립’입니다. 언어에 중립적인 인터페이스 설계, GUID로 고유 식별하고 버전을 관리하는 방식, IUnknown의 참조 카운트, 그리고 프로세스 간 통신을 투명하게 다루는 메커니즘.

COM 설계의 중심언어에 중립적인 인터페이스 설계, 언어에 중립적인 인터페이스 설계, GUID로 고유 식별하고 버전을 관리하는 방식, IUnknown의 참조 카운트, 투명한 프로세스 간 통신이라는 네 메커니즘이, 언어·프로세스·구현으로부터의 독립이라는 설계의 중심을 받친다는 것을 나타내는 그림.언어에 중립적인 설계언어·프로세스·구현으로부터의 독립GUID로 고유 식별IUnknown의 참조 카운트투명한 프로세스 간 통신

그림 7: 네 메커니즘이 모여 ‘언어·프로세스·구현으로부터의 독립’이라는 COM의 중심을 받칩니다.

‘아름답다’는 주관적인 표현이지만, 그렇게 평가하는 근거는 구체적입니다. COM이 풀려고 했던 문제와 그 풀이 방법을 나열하면 다음과 같습니다.

당시(그리고 지금도) 있는 제약 COM의 답
C++에는 표준 바이너리 규약(ABI)이 없어, 컴파일러가 다르기만 해도 재사용할 수 없다. name mangling도 객체 레이아웃도 맞지 않는다 ‘가상 함수 테이블의 나열 순서’만 규약으로 삼았다. 이 한 점에 좁혔기 때문에, 언어도 컴파일러도 가리지 않게 되었다
라이브러리를 갈아끼우면, 그것을 쓰는 전부를 다시 빌드해야 한다 계약(인터페이스)이 바뀌지 않는 한 다시 빌드할 필요 없다. 바이너리 그대로 교체할 수 있다
기능을 더하고 싶지만, 기존 호출 측을 깨면 안 된다 인터페이스를 바꾸지 않고 추가하고, QueryInterface로 ‘갖고 있는지’를 실행 시점에 묻는다
이름이 충돌한다(같은 클래스 이름의 다른 것이 공존한다) GUID로 유일하게 식별한다. 이름 조정을 애초에 불필요하게 만들었다
다른 프로세스·다른 머신의 기능을 호출하면, 호출 코드의 형태가 완전히 달라진다 Proxy/Stub을 끼워, 호출 측 코드를 같은 형태로 유지한다

즉 COM의 설계는 ‘어떻게 하면 아름다운가’에서 시작한 것이 아니라, 재사용을 가로막던 구체적인 장애를 하나씩 없앤 결과로 지금의 형태가 되었습니다. 제약과 해답의 대응을 이만큼 곧게 따라갈 수 있는 설계는 많지 않습니다.

그리고 이 풀이 방식은 현대의 컴포넌트 지향 개발에 그대로 남아 있습니다.

COM 현대의 대응 개념
IDL로 계약을 먼저 정하고, 거기서 양쪽 코드를 생성한다 OpenAPI나 Protocol Buffers의 스키마에서 클라이언트/서버를 생성한다
기존 인터페이스를 바꾸지 않고, 새 인터페이스를 추가한다 Protocol Buffers에서 필드 번호를 재사용하지 않고 추가한다, API를 버전으로 나눈다
구현 언어를 가리지 않는다(바이너리 계약) 구현 언어를 가리지 않는다(네트워크 너머의 메시지 계약)
Proxy/Stub이 프로세스 경계를 숨긴다 RPC의 클라이언트 스텁이 네트워크 경계를 숨긴다

다른 것은 경계의 종류(같은 머신의 프로세스 경계인가, 네트워크인가)와 계약의 표현(바이너리인가, 텍스트/스키마인가)뿐입니다. COM을 이해해 두면, 마이크로서비스 간 인터페이스 설계에서 ‘왜 계약을 먼저 정하는가’‘왜 기존 필드를 지우면 안 되는가’가, 유행이 아니라 필연으로 납득됩니다.

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

ActiveX 이관

COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

COM이란 무엇인가요?
COM(Component Object Model)은 Windows에서 컴포넌트끼리 주고받기 위한 '바이너리 계약'입니다. 언어나 컴파일러가 달라도, 인터페이스라는 엄격한 계약으로 통신하는 메커니즘입니다. 밑바탕에는 '구현이 아니라 계약을 대상으로 프로그래밍한다'는 설계 사상이 있습니다.
IUnknown이란 무엇인가요?
모든 COM 인터페이스가 상속하는 기본 인터페이스입니다. 다른 인터페이스를 갖고 있는지 묻는 QueryInterface, 참조 카운트를 늘리는 AddRef, 참조 카운트를 줄여 0이 되면 자신을 소멸시키는 Release라는 세 기능을 제공합니다. COM의 객체 수명 관리는 이 참조 카운트로 성립합니다.
COM은 지금도 사용되나요?
네, COM은 Windows의 핵심에서 지금도 계속 쓰이는 메커니즘입니다. 탐색기 확장(우클릭 메뉴나 미리보기), Excel이나 Word의 Office 자동화, .NET과의 COM Interop, ActiveX를 포함한 기존 시스템, DirectX나 Windows Shell API 등 여러 곳에서 등장합니다. Windows 개발을 하는 한, COM은 어딘가에 등장합니다.
COM의 강점은 무엇인가요?
크게 네 가지입니다. 한 번 빌드한 컴포넌트를 언어나 런타임에 상관없이 재사용할 수 있는 바이너리 호환성, 구현을 숨기고 계약만 공개하는 인터페이스 분리, 새 인터페이스를 추가해 하위 호환을 유지하는 버전 공존, 그리고 Out-of-proc COM(EXE 서버)으로 다른 프로세스의 기능을 안전하게 호출하는 점입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기