מה זה COM — למה התכנון של Windows COM עדיין מחזיק
· עודכן בתאריך: · Go Komura · COM, ActiveX, פיתוח Windows
היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 25 Jan 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173242)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). מה זה COM — למה התכנון של Windows COM עדיין מחזיק. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173242 https://comcomponent.com/he/blog/why-com-is-beautiful/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22173242
- DOI (הגרסה הזו)
- 10.5281/zenodo.22173243
מה זה COM?
COM (Component Object Model) הוא binary contract לתקשורת בין components ב-Windows. זה מנגנון שבו מדברים דרך interface כחוזה קשיח, מעבר להבדלי שפה ו-compiler, וביסודו עומדת גישת התכנון “תכנתם מול החוזה, לא מול ה-implementation”.
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 23, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
שלושת החלקים של COM
הפרק הזה מתאר את החלקים שמהם בנוי COM. הפרק הבא, “ארבעת היתרונות של COM”, מתאר תכונות שמתקבלות כתוצאה משילוב החלקים האלה — לא הסבר כפול על אותו דבר. ההתאמה מופיעה בטבלה בסוף פרק היתרונות.
1. תכנון ממוקד-interfaces
ב-COM “החוזה קודם ל-implementation”. אפשר להשתמש באובייקט בלי לדעת כלום על ה-implementation הפנימי, כל עוד מכירים את ה-interface שנחשף.
2. זיהוי עם GUID (CLSID / IID)
לכל component ולכל interface יש ID ייחודי בעולם (GUID), כך ש-name collision לא קורה מלכתחילה.
3. IUnknown
ה-interface הבסיסי שכל COM interface יורש. הוא נותן שלוש פונקציות:
| method | תפקיד |
|---|---|
QueryInterface |
שואל אם יש interface אחר |
AddRef |
מעלה את ה-reference count |
Release |
מוריד את ה-reference count (משמיד את עצמו כשמגיעים לאפס) |
כששלושת החלקים האלה עובדים יחד, מה שנשאר בין ה-caller ל-implementation הוא רק “interface שמזוהה ב-GUID, עם סדר methods קבוע”. לא השפה ולא ה-compiler מופיעים בחוזה.
flowchart LR
subgraph CALLER["ה-caller — השפה לא משנה"]
A1["אפליקציית C++"]
A2["אפליקציית C#"]
A3["VBA / Python וכו'"]
end
CONTRACT["החוזה (החלק הקבוע בבינארי)<br/>· IID (GUID) קובע איזה חוזה זה<br/>· סדר ה-methods שמתחיל מ-IUnknown<br/>· טיפוסי הארגומנטים, ערך ההחזרה ו-calling convention לכל method"]
subgraph IMPL["ה-implementation — השפה לא משנה"]
B1["component שנכתב ב-C++"]
B2["component שנכתב ב-C#"]
end
A1 --> CONTRACT
A2 --> CONTRACT
A3 --> CONTRACT
CONTRACT --> B1
CONTRACT --> B2
איור 1: ה-binary contract של COM. לא שפת ה-caller ולא שפת ה-implementation מופיעות בחוזה, אז אפשר להחליף כל צד בלי לבנות מחדש את השני.
“תכנתם מול החוזה” — בקוד
כדי שזה לא יישאר מושג מופשט, הנה קוד מינימלי. דוגמה לשימוש ב-component ICalcService שרק מחבר מספרים, בלי לדעת כלום על ה-implementation.
נתחיל מ-C++ (raw COM). ההגדרה של ICalcService כאן היא החוזה עצמו. אין שום מקום בקוד של ה-caller שמגלה אם ה-implementation נכתב ב-C++ או ב-C#.
#include <objbase.h>
// הגדרת החוזה. בדרך כלל header בצורה הזו נוצר מ-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 של רכיב ה-implementation. בדרך כלל מוגדר ב-header שנוצר מ-IDL
static const CLSID CLSID_CalcService =
{ 0x1C9B6F4D, 0x1E9A, 0x4E61, { 0x9A, 0x4F, 0x6A, 0x0F, 0x1D, 0x2D, 0x9A, 0x11 } };
// ה-caller
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));
// ה-pointer שחוזר כאן כבר עבר AddRef (ה-reference count הוא 1)
if (SUCCEEDED(hr))
{
int sum = 0;
hr = calc->Add(1, 2, &sum); // אפשר לקרוא בלי לדעת על ה-implementation
// שאלה ב-runtime "יש גם את חוזה גרסת ההרחבה?" = מימוש של version coexistence
ICalcServiceEx* calcEx = nullptr;
if (SUCCEEDED(calc->QueryInterface(__uuidof(ICalcServiceEx),
reinterpret_cast<void**>(&calcEx))))
{
// מגיעים לכאן רק אם זה component מהגרסה החדשה
calcEx->Release(); // הצד שקיבל הוא זה שעושה Release
}
// אם אין, חוזר פשוט E_NOINTERFACE, וגם הגרסה הישנה ממשיכה לעבוד
calc->Release(); // ה-reference count מגיע לאפס והאובייקט מושמד
}
CoUninitialize();
שלוש נקודות לקריאה:
- כמעט אין מצב שבו כותבים
AddRefבעצמכם. גםCoCreateInstanceוגםQueryInterfaceכבר עשוAddRefעל ה-pointer שהם מחזירים. כלומר העיקרון הוא “מי שקיבל את ה-pointer קורא ל-Release”, וקוראים ל-AddRefבמפורש רק כשמתחילים להחזיק את אותו pointer במקום נוסף. - כישלון של
QueryInterfaceאינו חריג. “אין לי את החוזה הזה” (E_NOINTERFACE) היא תשובה תקינה, וזה מה שמאפשר להוסיף פונקציונליות חדשה בלי לשבור components ישנים. - כל ערכי ההחזרה הם
HRESULT. הצלחה/כישלון כערך מוחזר, לא כ-exception, היא הסכמה הכרחית כדי לחצות שפות. אופן זריקת exceptions שונה משפה לשפה, אבל integer return value כולם יודעים לפרש.
flowchart TB
accTitle: עקרון ה-reference count ו-Release
accDescr: CoCreateInstance ו-QueryInterface מחזירים pointer אחרי AddRef, אז הצד שקיבל קורא ל-Release בסיום השימוש, וכשה-reference count מגיע לאפס האובייקט מושמד.
api["CoCreateInstance או QueryInterface"] --> ret["חוזר pointer אחרי AddRef"]
ret --> use["הצד שקיבל משתמש בו"]
use --> rel["הצד שקיבל קורא ל-Release"]
rel --> zero["reference count אפס = השמדה"]
use -.->|"AddRef מפורש"| add["שומרים את ה-pointer במקום נוסף"]
איור 2: מי שקיבל את ה-pointer הוא זה שקורא ל-Release. AddRef במפורש רק כשמוסיפים מקום החזקה.
כך משתמשים באותו חוזה מ-C#. אותו GUID נחשב לאותו חוזה, אז לא משנה אם ה-implementation בפועל הוא 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);
}
// ה-caller
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService")
?? throw new InvalidOperationException("שרת COM לא רשום.");
object server = Activator.CreateInstance(t)!;
try
{
var calc = (ICalcService)server; // ה-cast הזה מקביל ל-QueryInterface
int sum = calc.Add(1, 2);
Console.WriteLine(sum); // 3
}
finally
{
Marshal.ReleaseComObject(server); // מקביל ל-Release
}
הסיבה שערך ההחזרה של Add בצד C# הוא int היא ש-COM Interop של .NET עושה המרה קבועה: “הארגומנט האחרון מסוג [out, retval] הופך לערך ההחזרה, ו-HRESULT שנכשל הופך ל-exception”. גם AddRef/Release נעלמים מנקודת המבט של ה-caller, אבל הם לא נעלמו — wrapper דק ש-.NET מספק (RCW) קורא להם במקום. להשתמש באותו חוזה, בצורה הטבעית של כל שפה — זו הצורה האמיתית של “binary contract”.
flowchart TB
accTitle: התאמה בין קוד C# ל-COM
accDescr: ב-C# cast ל-interface מקביל ל-QueryInterface, Marshal.ReleaseComObject מקביל ל-Release, HRESULT שנכשל הופך ל-exception, ו-AddRef ו-Release נקראים על ידי ה-RCW.
cs["קוד C#"] --> cast["cast לטיפוס"]
cs --> rco["ReleaseComObject"]
cs --> hrx["HRESULT שנכשל"]
cast --> qi["מקביל ל-QueryInterface"]
rco --> rel["מקביל ל-Release"]
hrx --> ex["הופך ל-exception"]
cs -.-> rcw["ה-RCW מנהל את ה-reference count"]
איור 3: גם באותו חוזה, בצד C# הוא מתורגם לצורה הטבעית של השפה, וההמרה נעשית על ידי ה-RCW ושכבת ה-interop.
ארבעת היתרונות של COM
1. Binary compatibility
רכיב שנבנה פעם אחת אפשר לעשות בו reuse בלי תלות בשפת התכנות או ב-runtime. לקרוא ל-COM component שנכתב ב-C++ מתוך C# או Python זה דבר שגרתי.
2. הפרדת interfaces
כי ה-implementation מוסתר לגמרי ורק החוזה נחשף, אפשר לשנות את ה-implementation הפנימי בחופשיות בלי להשפיע על ה-caller.
3. Version coexistence
כדי להוסיף פונקציונליות תוך שמירה על backward compatibility, העיקרון הבסיסי הוא להוסיף interfaces חדשים. כך מספקים פונקציונליות חדשה בלי לשנות את ה-interfaces הישנים.
כשמשלבים caller ישן/חדש עם component ישן/חדש, שלושה מתוך ארבעת הצירופים עובדים כרגיל, והצירוף הרביעי מסתפק בתשובה “אין את זה”.
flowchart LR
OLDC["caller ישן<br/>מכיר רק ICalcService"]
NEWC["caller חדש<br/>שואל גם על ICalcServiceEx"]
OLDS["component ישן<br/>מיישם רק ICalcService"]
NEWS["component חדש<br/>מיישם את שניהם"]
OLDC -->|"QueryInterface(IID_ICalcService)<br/>S_OK"| OLDS
OLDC -->|"QueryInterface(IID_ICalcService)<br/>S_OK — עדכון לא שובר"| NEWS
NEWC -->|"QueryInterface(IID_ICalcServiceEx)<br/>S_OK — אפשר להשתמש בפונקציונליות חדשה"| NEWS
NEWC -.->|"QueryInterface(IID_ICalcServiceEx)<br/>E_NOINTERFACE — ממשיכים עם הישן"| OLDS
איור 4: הוספת IID חדש בלי לשנות interface קיים משאירה את כל צירופי הישן-חדש עובדים. הקו המקווקו הוא תשובה תקינה של “אין את החוזה הזה”.
4. Reuse מעבר לגבול process
ל-COM components יש שני מיקומי הרצה. In-proc (DLL server) נטען כ-DLL באותו process כמו ה-caller, ו-Out-of-proc (EXE server, LocalServer) מופעל כ-EXE ב-process נפרד. קוד הקריאה נראה זהה בשני המקרים.
flowchart TB
accTitle: שני מיקומי ההרצה של component
accDescr: מאותו קוד קריאה שנראה זהה, In-proc נטען כ-DLL באותו process כמו ה-caller, ו-Out-of-proc מופעל כ-EXE ב-process נפרד.
caller["קוד הקריאה (נראה זהה בשני המקרים)"] --> ip["In-proc (DLL server)"]
caller --> op["Out-of-proc (EXE server)"]
ip -.-> ipd["נטען כ-DLL באותו process"]
op -.-> opd["מופעל כ-EXE ב-process נפרד"]
איור 5: יש שני מיקומי הרצה, In-proc ו-Out-of-proc, אבל קוד הקריאה נראה זהה בשניהם.
| In-proc (DLL server) | Out-of-proc (EXE server) | |
|---|---|---|
| איפה זה רץ | אותו process כמו ה-caller | process נפרד |
| מהות הקריאה | קריאה ישירה דרך function pointer | packing מחדש של הארגומנטים (marshaling) והעברה ב-IPC |
| מהירות | מהיר | איטי יותר בגלל ה-IPC |
| כשהצד השני קורס | ה-caller נגרר איתו | ה-caller נשאר בחיים (הקריאה חוזרת ככישלון) |
| bitness (32/64-bit) | חייב להתאים כדי להיטען | מותר שיהיה שונה |
עם Out-of-proc COM (EXE server) אפשר לקרוא בבטחה לפונקציונליות ב-process נפרד. השורה האחרונה, “מותר ש-32-bit/64-bit יהיו שונים”, רלוונטית בשטח, ואת אופן הבנייה הקונקרטי של שימוש ב-DLL של 64-bit מתוך אפליקציית 32-bit נסביר במאמר “גשר COM: איך אפליקציית 32-bit קוראת ל-DLL של 64-bit”.
עם זאת, process נפרד גם אומר שהצד השני עלול להיעלם בכל רגע. כש-process השרת קורס או מסתיים, ה-caller מקבל את הכשלים הבאים. אלה לא “תקלה” אלא “הצד השני כבר לא קיים” — שגיאות ייחודיות ל-Out-of-proc.
| קוד שגיאה | משמעות |
|---|---|
RPC_E_DISCONNECTED |
האובייקט שרצינו לקרוא לו מנותק מה-client (האובייקט של הצד השני כבר לא קיים) |
RPC_S_SERVER_UNAVAILABLE |
אי אפשר להגיע לשרת (ה-process) של הקריאה |
ב-In-proc אין צורך לחשוב על זה כי “אם הוא נופל, גם אני נופל”, אבל ב-Out-of-proc זה נכנס לתכנון כ-recovery של restart או reconnect. נכון יותר לחשוב על זה כעל עבודה נוספת שמתקבלת בתמורה לבטיחות.
flowchart TB
accTitle: מה קורה כשה-process השני נעלם
accDescr: ב-Out-of-proc crash או סיום של process השרת גורמים לקריאה לחזור ככישלון, ה-caller נשאר בחיים, ולכן צריך לתכנן recovery של restart או reconnect.
gone["crash או סיום של process השרת"] --> fail["הקריאה חוזרת ככישלון"]
fail --> alive["ה-caller נשאר בחיים"]
alive --> rec["recovery של restart / reconnect"]
fail -.-> mean["לא 'נשבר' אלא 'הצד השני כבר לא קיים'"]
איור 6: ב-Out-of-proc הצד השני שנעלם לא גורר את ה-caller, אבל בתמורה צריך לתכנן recovery.
ההתאמה בין שלושת החלקים לארבעת היתרונות
כפי שצוין בפתיחה, “שלושת החלקים” הם הרכיבים, ו”ארבעת היתרונות” הם התוצאה שלהם. כשמסדרים אילו חלקים תומכים באילו יתרונות, חלוקת התפקידים שנראתה חופפת מתבהרת.
| יתרון | החלק שמשפיע בעיקר |
|---|---|
| 1. Binary compatibility | תכנון ממוקד-interfaces + IUnknown (סדר הקריאה נקבע ברמת הבינארי) |
| 2. הפרדת interfaces | תכנון ממוקד-interfaces (חושף רק את החוזה) |
| 3. Version coexistence | GUID + QueryInterface (אפשר לשאול ב-runtime על חוזה אחר) |
| 4. Reuse מעבר לגבול process | תכנון ממוקד-interfaces (אפילו המיקום של ה-implementation מנותק מהחוזה) |
COM עדיין פעיל היום
נוטים לחשוב על COM כ”טכנולוגיה ישנה”, אבל הוא מנגנון שממשיך לשבת בליבת Windows גם היום.
איפה COM בשימוש
- הרחבות Explorer (תפריט קליק ימני, תצוגה מקדימה)
- אוטומציה של Office (שליטה חיצונית ב-Excel וב-Word)
- Interop עם .NET (COM Interop)
- מערכות קיימות שכוללות ActiveX
- DirectX, Windows Shell API ועוד Windows APIs רבים
גם אם נדמה ש”זה לא קשור אליי”, כל עוד מפתחים ל-Windows, COM צץ איפשהו.
סיכום
מרכז התכנון של COM הוא “עצמאות משפה, מ-process ומ-implementation”. תכנון interfaces ניטרלי-שפה, זיהוי ייחודי וניהול גרסאות עם GUID, reference counting עם IUnknown, ומנגנון שמטפל ב-IPC בצורה שקופה.
flowchart TB
accTitle: מרכז התכנון של COM
accDescr: תכנון ניטרלי-שפה של interfaces, זיהוי ייחודי עם GUID, reference counting של IUnknown, ו-IPC שקוף — ארבעת המנגנונים האלה תומכים במרכז התכנון של COM: עצמאות משפה, מ-process ומ-implementation.
i1["תכנון ניטרלי-שפה"] --> core["עצמאות משפה, process ו-implementation"]
i2["זיהוי ייחודי עם GUID"] --> core
i3["reference counting של IUnknown"] --> core
i4["IPC שקוף"] --> core
איור 7: ארבעת המנגנונים יחד תומכים במרכז התכנון של COM — עצמאות משפה, process ו-implementation.
“יפה” היא הגדרה סובייקטיבית, אבל הנימוקים להערכה הזו קונקרטיים. הנה הבעיות ש-COM ניסה לפתור, לצד הפתרונות שלו:
| מגבלה מאז (וגם היום) | הפתרון של COM |
|---|---|
| ל-C++ אין ABI סטנדרטי, ולכן compiler שונה בלבד מונע reuse. גם ה-name mangling וגם פריסת האובייקטים אינם אחידים | הפכו רק את “סדר טבלת ה-virtual functions” להסכם. בזכות הצמצום לנקודה הזו, השפה וה-compiler הפכו ללא רלוונטיים |
| כשמחליפים ספרייה, צריך לבנות מחדש את כל מה שמשתמש בה | כל עוד החוזה (ה-interface) לא משתנה, אין צורך ב-rebuild. אפשר להחליף במצב בינארי בלבד |
| רוצים להוסיף פונקציונליות, אבל אי אפשר לשבור את ה-caller הקיים | מוסיפים interface בלי לשנות את הקיים, ושואלים ב-runtime עם QueryInterface האם הוא קיים |
| שמות מתנגשים (אותו שם מחלקה שייך לדברים שונים שקיימים זה לצד זה) | זיהוי ייחודי עם GUID. כך תיאום השמות מיותר מלכתחילה |
| קריאה לפונקציונליות ב-process אחר או במחשב אחר משנה לגמרי את אופן הכתיבה של הקריאה | Proxy/Stub עומדים באמצע ושומרים על צורת הקוד של ה-caller זהה |
במילים אחרות, התכנון של COM לא התחיל מהשאלה “איך עושים את זה יפה”, אלא הגיע לצורתו הנוכחית כתוצאה מסילוק, אחד אחד, של המכשולים הקונקרטיים שמנעו reuse. לא הרבה תכנונים מאפשרים לעקוב כל כך בפשטות אחר ההתאמה בין המגבלה לפתרון.
ודרך הפתרון הזו נשארה כמעט ללא שינוי בפיתוח מונחה-components המודרני.
| COM | המקבילה המודרנית |
|---|---|
| קובעים את החוזה מראש ב-IDL, ומייצרים ממנו קוד לשני הצדדים | מייצרים client/server מסכימת OpenAPI או Protocol Buffers |
| לא משנים interface קיים, אלא מוסיפים interface חדש | ב-Protocol Buffers מוסיפים מספרי שדה חדשים בלי לעשות בהם reuse, מחלקים API לגרסאות |
| בלי תלות בשפת ה-implementation (binary contract) | בלי תלות בשפת ה-implementation (חוזה הודעות דרך הרשת) |
| Proxy/Stub מסתירים את גבול ה-process | ה-stub של RPC client מסתיר את גבול הרשת |
ההבדל היחיד הוא סוג הגבול (גבול process באותו מחשב, או רשת) ואופן ייצוג החוזה (בינארי, או טקסט/סכימה). מי שמבין את COM, כשהוא ניגש לתכנון interfaces בין microservices, מבין מעצמו — לא כמגמה חולפת אלא כהכרח — למה קובעים את החוזה מראש ולמה אסור למחוק שדות קיימים.
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
COM, ActiveX ו-OCX — ההבדלים והקשר ביניהם
מהו COM, מהו ActiveX ומהו OCX — סקירה מעשית של ההבדלים והקשר ביניהם, הזיקה ל-OLE, איפה זה בשימוש, ואיך כדאי להתייחס לזה היום.
ActiveX / OCX: להשאיר, לעטוף או להחליף
כשמוצאים ActiveX או OCX, איך בוחרים בין להשאיר, לעטוף או להחליף — כולל 32bit / 64bit, registration, תלות בדפדפן ותחזוקת vendor.
WinRT הוא COM — IInspectable, .winmd, language projections, ולמה WinUI עדיין יושב על חוזה בינארי
WinRT אינו managed runtime אלא ABI שנבנה על COM ועליו metadata של .winmd ו-language projections. מכסה IUnknown מול IInspectable, הגדרת HW...
מה זה OLE object? — איך embedding ו-linking עובדים, והמלכודות במסמכים עסקיים
OLE object הוא מה שמטמיע טבלת Excel ב-Word. לומדים embedding מול linking, compound files, In-Place Activation, קישורים שבורים, ניפוח ואבטחה.
איך clipboard ו-drag-and-drop עובדים — טיפול נכון ב-OLE data transfer באפליקציות עסקיות
למה הדבקת Excel מתפרקת ולמה paste נכשל אחרי סגירת המקור: clipboard formats, delayed rendering, OLE drag-and-drop, ומדיניות clipboard hist...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
שימוש חוזר והעברה של נכסים קיימים
הבנת התכנון והתאימות של COM היא נקודת כניסה טובה לחשיבה על איך לנצל Windows legacy קיים.
ייעוץ טכני וסקירת תכנון
אם רוצים לגבש כיוון עם IUnknown, GUID ותכנון גבולות בראש, זה מוביל לייעוץ טכני ולסקירת תכנון.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה זה COM?
- COM (Component Object Model) הוא binary contract לתקשורת בין components ב-Windows. השפות וה-compilers יכולים להיות שונים; התקשורת עוברת דרך interface שהוא חוזה קשיח. בבסיס עומדת גישת התכנון "תכנתם מול החוזה, לא מול ה-implementation".
- מה זה IUnknown?
- IUnknown הוא ה-interface הבסיסי שכל COM interface יורש. הוא נותן שלוש פונקציות: QueryInterface שואל אם יש interface אחר, AddRef מעלה את ה-reference count, ו-Release מוריד אותו ומשמיד את האובייקט כשהוא מגיע לאפס. ניהול חיי האובייקטים ב-COM בנוי על ה-reference count הזה.
- האם COM עדיין בשימוש היום?
- כן. COM ממשיך לשבת בליבת Windows. הוא מופיע בהרחבות Explorer (תפריט קליק ימני ותצוגה מקדימה), אוטומציה של Office ב-Excel וב-Word, COM Interop מול .NET, מערכות קיימות שכוללות ActiveX, DirectX ו-Windows Shell API, ועוד. כל עוד מפתחים ל-Windows, COM צץ איפשהו.
- מה היתרונות של COM?
- ארבעה עיקריים. binary compatibility: רכיב שנבנה פעם אחת אפשר לעשות בו reuse בלי תלות בשפה או ב-runtime. הפרדת interfaces: ה-implementation מוסתר ורק החוזה חשוף. version coexistence: מוסיפים interfaces חדשים ושומרים על backward compatibility. וקריאה בטוחה לפונקציונליות ב-process נפרד דרך Out-of-proc COM (EXE server).