מה זה COM — למה התכנון של COM ב-Windows עדיין יפה

· עודכן בתאריך: · · COM, ActiveX, פיתוח Windows

מה זה COM?

‏COM (Component Object Model) הוא “חוזה בינארי” לתקשורת בין רכיבים ב-Windows. זהו מנגנון שבו התקשורת מתבצעת דרך חוזה מחמיר הנקרא ממשק, החוצה הבדלי שפה ומהדר, וביסודו עומדת גישת התכנון “תכנתו מול חוזה, לא מול מימוש”.

מפת הידע של המאמר

‏COM הוא חוזה בינארי לתקשורת בין רכיבים ב-Windows, ו-IUnknown שכל הממשקים יורשים ממנו מספק בדיקת פונקציונליות באמצעות QueryInterface וניהול ספירת הפניות באמצעות AddRef/Release. זיהוי ייחודי של רכיבים וממשקים באמצעות GUID מסוג CLSID ו-IID מונע התנגשויות שמות, ותכנון שמוסיף ממשקים חדשים בלי לשנות ממשקים קיימים ובודק אותם עם 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 (Component Object Model)IUnknownQueryInterfaceספירת הפניות ב-COM‏ (AddRef/Release)CLSID(Class ID)‏IID (מזהה ממשק)HRESULTתאימות בינארית של COMדו-קיום גרסאות ב-COM‏In-proc COM (שרת DLL)‏COM LocalServer (שרת COM בתהליך נפרד)דרישת התאמת bitnessגרירת קריסה ב-in-procשגיאת ניתוק ב-COM חוץ-תהליכיProxy/Stub‏IDL (שפת הגדרת ממשקים)פעולה הדדית עם COM ב-‎.NET‏ (COM Interop)ProgID(Programmatic Identifier)ActiveX

בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 23, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle

שלושת המרכיבים החשובים של COM

מה שמפורט בפרק הזה הם הרכיבים שמהם בנוי המנגנון של COM. הפרק הבא, “ארבעת היתרונות של COM”, מתאר תכונות שמתקבלות כתוצאה משילוב הרכיבים האלה — כך שלא מדובר בהסבר כפול על אותו הדבר. הקשר בין השניים מוצג בטבלה בסוף פרק היתרונות.

1. תכנון ממוקד-ממשקים

ב-COM “החוזה קודם למימוש”. אפשר להשתמש באובייקט מבלי לדעת דבר על המימוש הפנימי שלו, כל עוד מכירים את הממשק שנחשף.

2. זיהוי באמצעות GUID(CLSID / IID)

לכל רכיב ולכל ממשק מוקצה מזהה ייחודי בעולם (GUID), כך שלא יכולה להתרחש התנגשות שמות מלכתחילה.

3. IUnknown

הממשק הבסיסי שכל ממשקי COM יורשים ממנו. הוא מספק שלוש פונקציות:

שיטה תפקיד
QueryInterface שואלת אם קיים ממשק אחר
AddRef מגדילה את ספירת ההפניות
Release מקטינה את ספירת ההפניות (משמידה את עצמה כשהיא מגיעה לאפס)

כששלושת המרכיבים האלה משולבים יחד, מה שנשאר בין הצד הקורא לבין המימוש הוא רק “ממשק המזוהה על ידי GUID, עם סדר שיטות קבוע”. לא השפה ולא המהדר מופיעים בחוזה הזה.

החוזה הבינארי של COMתרשים המראה שהצד הקורא, בכל שפה שהיא, וצד המימוש, בכל שפה שהיא, שניהם מתחברים רק לחוזה הקבוע בבינארי, כך שאין צורך לבנות מחדש את הצד השני כשמחליפים אחד מהם.המימוש — כל שפההצד הקורא — כל שפהרכיב שנכתב ב-‏C++רכיב שנכתב ב-‏C#אפליקציית ‏C++אפליקציית ‏C#‏VBA / Python וכו׳החוזה(קבוע בבינארי)‏IID(GUID)קובע איזה חוזהסדר השיטות מ-IUnknownטיפוסי ארגומנטים וקריאה

איור 1: החוזה הבינארי של COM — לא שפת הצד הקורא ולא שפת המימוש מופיעות בחוזה, כך שאפשר להחליף כל צד בלי לבנות מחדש את האחר.

“תכנתו מול חוזה” — בקוד

כדי שלא יישאר רק כמושג מופשט בטקסט, נראה קוד מינימלי. זו דוגמה לשימוש ברכיב ICalcService, שרק מבצע חיבור, בלי לדעת דבר על המימוש שלו.

נתחיל מ-‏C++ (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();                     // ספירת ההפניות מגיעה לאפס והאובייקט מושמד
}
CoUninitialize();

שלוש נקודות חשובות:

  • כמעט אין מצב שבו כותבים AddRef בעצמכם. גם CoCreateInstance וגם QueryInterface כבר ביצעו AddRef על המצביע שהם מחזירים. כלומר, העיקרון הוא ש”הצד שמקבל את המצביע קורא ל-Release”, וקוראים ל-AddRef באופן מפורש רק כאשר מתחילים להחזיק את אותו מצביע במקום נוסף.
  • כישלון של QueryInterface אינו חריגה. “אין לי את החוזה הזה” (E_NOINTERFACE) היא תשובה תקינה, וזה מה שמאפשר “להוסיף פונקציונליות חדשה בלי לשבור רכיבים ישנים”.
  • כל ערכי ההחזרה הם HRESULT. החזרת הצלחה/כישלון כערך מוחזר, ולא כחריגה, היא הסכמה הכרחית כדי לחצות שפות. אופן זריקת החריגות שונה משפה לשפה, אך ערך מוחזר שלם יכול להתפרש על ידי כולם.
עקרון ספירת ההפניות ו-Releaseתרשים המראה ש-CoCreateInstance ו-QueryInterface מחזירים מצביע לאחר AddRef, כך שהצד המקבל קורא ל-Release בסיום השימוש, וכאשר ספירת ההפניות מגיעה לאפס האובייקט מושמד.AddRef מפורשCoCreateInstance או QueryInterfaceמוחזר מצביע לאחר AddRefהצד המקבל משתמש בוהצד המקבל קורא ל-Releaseספירת הפניות אפס = השמדהשמירת המצביע במקום נוסף

איור 2: הצד המקבל את המצביע הוא זה שקורא ל-Release, ו-AddRef נקרא במפורש רק כשמוסיפים מקום החזקה.

כך משתמשים באותו חוזה מ-‏C#. מכיוון שאותו GUID נחשב לאותו חוזה, לא משנה אם המימוש בפועל הוא ב-‏C++.

using System;
using System.Runtime.InteropServices;

// כותבים את אותו GUID כמו בצד ה-‏C++. זו ההצהרה על "אותו חוזה"
[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
}

הסיבה שערך ההחזרה של Add בצד ‏C# הוא int היא שהפעולה ההדדית של ‎.NET עם COM מבצעת המרה קבועה: “הארגומנט האחרון מסוג [out, retval] הופך לערך ההחזרה, ו-HRESULT כושל מומר לחריגה”. גם AddRef/Release נעלמים מנקודת המבט של הצד הקורא, אבל הם לא נעלמו בפועל — עטיפה דקה שמספקת ‎.NET (RCW) קוראת להם במקום. להשתמש באותו חוזה, בדרך הכתיבה הטבעית של כל שפה — זו הצורה האמיתית של “החוזה הבינארי”.

התאמה בין קוד C# ל-COMתרשים המראה שב-C# הטלת טיפוס לממשק מקבילה ל-QueryInterface, Marshal.ReleaseComObject מקביל ל-Release, HRESULT כושל מומר לחריגה, ו-AddRef ו-Release נקראים על ידי ה-RCW במקום הצד הקורא.קוד ‏C#הטלת טיפוס‏ReleaseComObject‏HRESULT כושלמקביל ל-QueryInterfaceמקביל ל-Releaseמומר לחריגהה-RCW מנהל את ספירת ההפניות

איור 3: גם באותו חוזה, בצד ‏C# הוא מתורגם לדרך הכתיבה הטבעית של השפה, וההמרה מתבצעת על ידי ה-RCW ושכבת הפעולה ההדדית.

ארבעת היתרונות של COM

1. תאימות בינארית

רכיב שנבנה פעם אחת ניתן לשימוש חוזר ללא תלות בשפת התכנות או בזמן הריצה. אפשר, למשל, לקרוא לרכיב COM שנכתב ב-‏C++ מתוך ‏C# או Python, וזה עובד באופן שגרתי.

2. הפרדת ממשקים

מכיוון שהמימוש מוסתר לחלוטין ורק החוזה נחשף, אפשר לשנות את המימוש הפנימי בחופשיות מבלי להשפיע על הצד הקורא.

3. דו-קיום גרסאות

כדי להוסיף פונקציונליות תוך שמירה על תאימות לאחור, העיקרון הבסיסי הוא להוסיף ממשקים חדשים. כך אפשר לספק פונקציונליות חדשה בלי לשנות את הממשקים הישנים.

כאשר משלבים צד קורא ישן/חדש עם רכיב ישן/חדש, שלושה מתוך ארבעת הצירופים פועלים כרגיל, והצירוף הרביעי מסתפק בתשובה של “אין את זה”.

תאימות בין גרסאות ישנות וחדשותתרשים המראה שצד קורא ישן שמכיר רק ICalcService מצליח לקרוא גם לרכיב הישן וגם לרכיב החדש, צד קורא חדש ששואל על ICalcServiceEx מצליח מול הרכיב החדש ומקבל E_NOINTERFACE מהרכיב הישן וממשיך לפעול עם הפונקציונליות הישנה.QueryInterface(ICalcService) → S_OKQueryInterface(ICalcService) → S_OK, לא נשברQueryInterface(ICalcServiceEx) → S_OK, פונקציונליות חדשהQueryInterface(ICalcServiceEx) → E_NOINTERFACE, ממשיך עם הישןצד קורא ישן(מכיר רק ICalcService)צד קורא חדש(שואל גם על ICalcServiceEx)רכיב ישן(מיישם רק ICalcService)רכיב חדש(מיישם את שניהם)

איור 4: הוספת IID חדש בלי לשנות ממשק קיים משאירה את כל צירופי הישן-חדש פועלים; הקו המקווקו הוא תשובה תקינה של “אין את החוזה הזה”.

4. שימוש חוזר החוצה גבולות תהליכים

לרכיבי COM יש שני מיקומי הרצה אפשריים: In-proc (שרת DLL), שנטען כ-DLL באותו תהליך כמו הצד הקורא, ו-Out-of-proc (שרת EXE,‏ LocalServer), שמופעל כ-EXE בתהליך נפרד. המראה של קוד הקריאה זהה בשני המקרים.

שני מיקומי ההרצה של רכיבתרשים המראה שמאותו קוד קריאה נראה זהה, In-proc נטען כ-DLL באותו תהליך כמו הצד הקורא, ו-Out-of-proc מופעל כ-EXE בתהליך נפרד.קוד הקריאה(נראה זהה בשני המקרים)In-proc(שרת DLL)Out-of-proc(שרת EXE)נטען כ-DLL באותו תהליךמופעל כ-EXE בתהליך נפרד

איור 5: יש שני מיקומי הרצה, In-proc ו-Out-of-proc, אבל קוד הקריאה נראה זהה בשניהם.

  In-proc (שרת DLL) Out-of-proc (שרת EXE)
מיקום ההרצה אותו תהליך כמו הצד הקורא תהליך נפרד
מהות הקריאה קריאה ישירה דרך מצביע לפונקציה סידור מחדש של הארגומנטים (marshaling) והעברה דרך תקשורת בין-תהליכית
מהירות מהיר איטי יותר בגלל התקשורת הבין-תהליכית
כשהצד השני קורס הצד הקורא נגרר איתו הצד הקורא נשאר בחיים (הקריאה חוזרת ככישלון)
מספר ביטים (32/64bit) חייב להתאים כדי להיטען מותר שיהיה שונה

עם Out-of-proc COM (שרת EXE) אפשר לקרוא בבטחה לפונקציונליות בתהליך נפרד. השורה האחרונה, “מותר ש-32bit/64bit יהיו שונים”, רלוונטית בפועל בשטח, ואת אופן הבנייה הקונקרטי של שימוש ב-DLL של 64bit מתוך אפליקציית 32bit נסביר במאמר “דוגמת גשר COM שקורא ל-DLL של 64bit מאפליקציית 32bit”.

עם זאת, העובדה שמדובר בתהליך נפרד גם אומרת שהצד השני עלול להיעלם בכל רגע. כשתהליך השרת קורס או מסתיים, הצד הקורא מקבל את הכשלים הבאים. אלה אינם “תקלה” אלא “הצד השני כבר לא קיים” — שגיאות ייחודיות ל-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 נמצא בשימוש

  • הרחבות ל-Explorer (תפריטי קליק ימני, תצוגה מקדימה)
  • אוטומציה של Office (שליטה חיצונית ב-Excel וב-Word)
  • פעולה הדדית עם ‎.NET (COM Interop)
  • מערכות קיימות הכוללות ActiveX
  • ‏DirectX,‏ Windows Shell API ועוד ה-API הרבים של Windows

גם אם נדמה ש”זה לא קשור אליי”, כל עוד מפתחים ל-Windows, COM צץ איפשהו.

סיכום

מרכז התכנון של COM הוא “עצמאות משפה, מתהליך וממימוש”. תכנון ממשקים ניטרלי-שפה, זיהוי ייחודי וניהול גרסאות באמצעות GUID, ספירת הפניות באמצעות IUnknown, ומנגנון שמטפל בתקשורת בין-תהליכית בצורה שקופה.

מרכז התכנון של COMתרשים המראה שתכנון ניטרלי-שפה של ממשקים, זיהוי ייחודי באמצעות GUID, ספירת הפניות של IUnknown, ותקשורת שקופה בין תהליכים - ארבעת המנגנונים האלה תומכים במרכז התכנון של COM - עצמאות משפה, מתהליך וממימוש.תכנון ניטרלי-שפהעצמאות משפה, תהליך ומימושזיהוי ייחודי באמצעות 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 מסתירים את גבול התהליך ה-stub של לקוח ה-RPC מסתיר את גבול הרשת

ההבדל היחיד הוא סוג הגבול (גבול תהליך באותו מחשב, או רשת) ואופן ייצוג החוזה (בינארי, או טקסט/סכימה). מי שמבין את COM, כשהוא ניגש לתכנון ממשקים בין מיקרו-שירותים, מבין מעצמו — לא כמגמה חולפת אלא כהכרח — למה קובעים את החוזה מראש ולמה אסור למחוק שדות קיימים.

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

מה זה COM?
‏COM (Component Object Model) הוא "חוזה בינארי" לתקשורת בין רכיבים ב-Windows. זהו מנגנון שבו התקשורת מתבצעת דרך חוזה מחמיר הנקרא ממשק, שחוצה הבדלי שפה ומהדר. בבסיסו עומדת גישת התכנון "תכנתו מול חוזה, לא מול מימוש".
מה זה IUnknown?
‏IUnknown הוא הממשק הבסיסי שכל ממשקי COM יורשים ממנו. הוא מספק שלוש פונקציות: QueryInterface ששואל האם קיים ממשק אחר, AddRef שמגדיל את ספירת ההפניות, ו-Release שמקטין את ספירת ההפניות ומשמיד את האובייקט כשהיא מגיעה לאפס. ניהול חיי האובייקטים ב-COM מבוסס כולו על ספירת ההפניות הזו.
האם COM עדיין בשימוש היום?
כן, COM הוא מנגנון שממשיך לשמש בליבת Windows גם היום. הוא מופיע במקומות רבים: הרחבות ל-Explorer (תפריטי קליק ימני ותצוגה מקדימה), אוטומציה של Office ב-Excel וב-Word,‏ COM Interop מול ‎.NET, מערכות קיימות שכוללות ActiveX,‏ DirectX ו-Windows Shell API ועוד. כל עוד מפתחים ל-Windows, COM צץ איפשהו.
מהם היתרונות של COM?
יש ארבעה יתרונות עיקריים: תאימות בינארית שמאפשרת לעשות שימוש חוזר ברכיב שנבנה פעם אחת, ללא תלות בשפה או בזמן ריצה; הפרדת ממשקים שמסתירה את המימוש וחושפת רק את החוזה; דו-קיום גרסאות ששומר על תאימות לאחור באמצעות הוספת ממשקים חדשים; וקריאה בטוחה לפונקציונליות בתהליך נפרד באמצעות COM חוץ-תהליכי (שרת EXE).

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג