גשר COM: אפליקציית 32-bit קוראת ל-DLL של 64-bit
· עודכן בתאריך: · Go Komura · COM, פיתוח Windows, 32bit, 64bit
היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 25 Jan 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173251)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). גשר COM: אפליקציית 32-bit קוראת ל-DLL של 64-bit. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173251 https://comcomponent.com/he/blog/com-case-study-32bit-to-64bit/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22173251
- DOI (הגרסה הזו)
- 10.5281/zenodo.22173252
הדרישה לקרוא מאפליקציית 32-bit ל-DLL של 64-bit היא די טיפוסית ב-Windows. במיוחד כשרוצים להשתמש רק בפונקציונליות של צד 64-bit תוך שמירה על legacy קיים, מבנה של גשר COM נוטה להיות פתרון מעשי.
קהל היעד: מי שמתחזק אפליקציית Windows קיימת של 32-bit ורוצה להשתמש ב-DLL או בספרייה של צד 64-bit. כתוב כך שאפשר לקרוא גם אם רק “שמעתם על COM אבל מעולם לא בניתם דבר בעצמכם”.
סביבה נדרשת: גרסת 64-bit של Windows (x64), וסביבת פיתוח שיכולה לכתוב C# (כמו Visual Studio). רישום שרת COM עבור כל המחשב (תחת HKEY_LOCAL_MACHINE) דורש הרשאות Administrator. את התפיסה הבסיסית של COM סיכמנו במאמר “מה זה COM — למה התכנון של Windows COM עדיין מחזיק”.
תוכן עניינים
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 19, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
1. התרחיש
מקרה שבו רוצים להשאיר את האפליקציה הקיימת של 32-bit כמות שהיא, אבל להשתמש בעיבוד של DLL בן 64-bit. אלא ש-process של 32-bit לא יכול לטעון DLL של 64-bit. זו מגבלה ברמת ה-OS, לא משהו שניתן לפתור בטריק.
מצבים נפוצים:
- האפליקציה הקיימת של 32-bit היא נכס גדול, ואי אפשר להעביר אותה במהירות
- יש פונקציונליות חדשה בצד ה-DLL של 64-bit, או שספריות התלות הן 64-bit בלבד
- רוצים לקרוא מצד ה-32-bit בצורה typed
בשילוב הזה, הדרך לקרוא באותו process חסומה מלכתחילה.
flowchart TB
accTitle: התרחיש הנתון
accDescr: אפליקציית 32-bit קיימת רוצה להשתמש בפונקציונליות של DLL בן 64-bit, אבל process של 32-bit לא יכול לטעון DLL של 64-bit בגלל מגבלה ברמת ה-OS, כך שהקריאה באותו process חסומה.
app["אפליקציית 32-bit קיימת"] --> want["רוצה להשתמש ב-DLL של 64-bit"]
want --> deny["אי אפשר לטעון באותו process"]
deny -.-> os["מגבלת OS, אין עקיפה"]
איור 1: process של 32-bit לא יכול לטעון DLL של 64-bit, ולכן אין מלכתחילה דרך לקרוא באותו process.
2. שיטת הפתרון
מכאן ואילך מופיע מינוח מקצועי, אז נסדר קודם כמה מילים מינימליות.
| מונח | משמעות |
|---|---|
| In-proc COM (DLL server) | טוענים את רכיב ה-COM באותו process כמו ה-caller. מהיר, אבל לא נטען אם ה-bitness לא תואם |
| Out-of-proc COM (EXE server) | מפעילים את רכיב ה-COM כ-process נפרד. אפשר לשתף פעולה גם כשה-bitness שונה |
| LocalServer | שרת COM שרץ כ-process נפרד באותו מחשב. רושמים את הנתיב ל-EXE במפתח LocalServer32 ב-Registry |
| IDL / TypeLib | ההגדרה (IDL) של צורת ה-interface (שמות methods, טיפוסי ארגומנטים), וגרסתו הבינארית (TypeLib). משמשים כדי ששני הצדדים יראו את אותו “חוזה” |
| Marshaling | packing מחדש של ארגומנטים וערכים מוחזרים לצורה שאפשר לשלוח, כדי לחצות גבול process. ההפך הוא unmarshaling |
| Proxy / Stub | קוד ייצוג שמבצע בפועל את ה-marshaling. Proxy עומד בצד ה-caller, Stub עומד בצד השרת |
| WOW6432Node | המיקום הפיזי שבו מאוחסן תוכן ה-Registry עבור אפליקציות 32-bit ב-Windows של 64-bit. אותו שם מפתח מכיל תוכן שונה בצד 32-bit ובצד 64-bit |
יסוד הפתרון הוא הפרדה עם Out-of-proc COM (EXE server). קוראים ל-DLL של 64-bit מתוך שרת COM (EXE) של 64-bit, ואפליקציית ה-32-bit משתמשת בו דרך COM.
flowchart TB
accTitle: המבנה הבסיסי של גשר COM
accDescr: אפליקציית 32-bit קוראת דרך COM לשרת COM של 64-bit בתור EXE, והשרת קורא פנימית ל-DLL של 64-bit.
a32["אפליקציית 32-bit"] -->|"קורא דרך COM"| srv["שרת COM של 64-bit (EXE)"]
srv -->|"קורא פנימית"| dll["DLL של 64-bit"]
איור 2: את ה-DLL של 64-bit ממקמים בתוך שרת EXE של 64-bit, ואפליקציית 32-bit משתמשת בשרת הזה דרך COM.
הזרימה היא כדלקמן.
- מכינים COM LocalServer (EXE) של 64-bit, שקורא פנימית ל-DLL של 64-bit
- משתפים COM interface (IDL/TypeLib) וחושפים את הטיפוסים
- אפליקציית ה-32-bit קוראת ל-COM בצורה typed (תקשורת דרך Proxy/Marshal)
עם זאת, יש גם נקודות לתשומת לב.
- הרישום של 32-bit ו-64-bit נפרד (כולל WOW6432Node)
- structs מותאמים דורשים תכנון marshaling
- יש תקורת IPC, ולכן צריך להיזהר מקריאות בתדירות גבוהה
במילים אחרות, הדרך הסטנדרטית היא “להעביר את העיבוד של 64-bit ל-process נפרד, ולגשר עם COM”.
flowchart TB
accTitle: שלושת שלבי בניית הגשר
accDescr: מכינים COM LocalServer של 64-bit, חושפים את הטיפוסים ב-IDL/TypeLib, ואפליקציית 32-bit קוראת בצורה typed — שלושה שלבים שמרכיבים את הגשר.
s1["הכנת COM LocalServer של 64-bit"] --> s2["חשיפת טיפוסים ב-IDL / TypeLib"]
s2 --> s3["אפליקציית 32-bit קוראת בצורה typed"]
s3 -.-> ps["התקשורת דרך Proxy / Marshal"]
איור 3: שלושת השלבים — הכנת ה-LocalServer, חשיפת הטיפוסים, והקריאה ה-typed — יוצרים יחד את הגשר.
אם מה שרוצים להשתמש בו בצד ה-64-bit הוא מלכתחילה שרת COM מסוג in-proc (DLL שנרשם ב-InprocServer32), לפעמים אפשר לוותר על כתיבת שרת EXE. מצמידים ל-CLSID ערך AppID, וכותבים תחת מפתח ה-AppID הזה ערך DllSurrogate של מחרוזת ריקה — ואז ה-DLL נטען ב-surrogate process שמגיע עם Windows (עבור DLL של 64-bit זה System32\dllhost.exe), ומבחינת client 32-bit הוא נראה כשרת COM מסוג out-of-proc שרץ ב-process נפרד (לא רושמים כאן נתיב של EXE ב-LocalServer32 — להפך, אם LocalServer32 קיים, ה-surrogate לא בשימוש). גם בכיוון ההפוך (שימוש ב-COM DLL של 32-bit מתוך אפליקציית 64-bit) המנגנון זהה, וה-bitness של ה-surrogate שעולה נקבע לפי ה-DLL ולא לפי ה-client. אבל אם מה שרוצים לקרוא לו הוא סתם DLL native, כמו במאמר הזה, אין מלכתחילה שרת COM שאפשר לטעון ב-surrogate, ולכן צריך את שיטת שרת ה-EXE שמתוארת מכאן והלאה. את שלבי הרישום סיכמנו ב-3.5 של Registration and Bitness Pitfalls in COM/OCX/ActiveX Development (שם ההנחה היא DLL של 32-bit, אז צריך רק להחליף את ה-view שאליו כותבים את ערך ה-AppID), ואת קו ההפרדה בין surrogate שמספיק לבין כתיבת EXE משלכם סיכמנו ב-5.2 של ActiveX / OCX: להשאיר, לעטוף או להחליף.
3. זרימת העיבוד (sequence diagram)
להלן הזרימה כאשר אפליקציית 32-bit קוראת לעיבוד של DLL בן 64-bit.
sequenceDiagram
participant App as client 32-bit
box rgba(100,100,255,0.1) תשתית ה-marshaling של COM הרשומה מטפלת
participant Proxy as COM Proxy<br/>(צד 32-bit)
participant RPC as RPC/IPC<br/>(IPC)
participant Stub as COM Stub<br/>(צד 64-bit)
end
participant Server as שרת COM של 64-bit<br/>(EXE)
participant DLL as DLL של 64-bit
App->>Proxy: ICalcService.Add(1, 2)
rect rgba(100,100,255,0.1)
Note over Proxy: marshaling לפרמטרים
Proxy->>RPC: נתונים serialized
RPC->>Stub: מועברים דרך גבול ה-process
Note over Stub: unmarshaling לפרמטרים
end
Stub->>Server: Add(1, 2)
Server->>DLL: קריאה לפונקציה native
DLL-->>Server: תוצאה: 3
Server-->>Stub: תוצאה: 3
rect rgba(100,100,255,0.1)
Note over Stub: marshaling לערך המוחזר
Stub-->>RPC: תוצאה serialized
RPC-->>Proxy: מועברת דרך גבול ה-process
Note over Proxy: unmarshaling לערך המוחזר
end
Proxy-->>App: תוצאה: 3
איור 4: הקריאה מאפליקציית 32-bit עוברת דרך Proxy, IPC ו-Stub אל שרת ה-64-bit וה-DLL, והתוצאה חוזרת באותו מסלול.
נקודות מרכזיות:
- אפליקציית 32-bit יכולה לקרוא type-safe דרך ה-interface
ICalcService - ה-COM runtime חוצה את גבול ה-process עם DLL רשום של Proxy/Stub, TypeLib marshaler, marshaler סטנדרטי ועוד
- בגלל תקורה של IPC, עדיף עיבוד באצווה על פני קריאות קטנות
flowchart TB
accTitle: עקרון גודל הקריאה
accDescr: ל-IPC יש תקורה, כך שקריאות קטנות וחוזרות מצטברות, ואילו ריכוז לעיבוד באצווה מקטין את ההשפעה — וזו הגישה המועדפת.
ipc["תקורת IPC"] --> fine["קריאות קטנות חוזרות מצטברות"]
ipc --> batch["ריכוז לעיבוד באצווה מקטין השפעה"]
batch -.-> rec["זו הגישה המועדפת"]
איור 5: לכל חציית גבול process יש עלות, ולכן עדיף לרכז את הקריאות לגודל גס.
4. קוד לדוגמה (רעיון)
4.1. interface משותף, שרת ו-client
זהו רעיון עקרוני בלבד. כדי שזה יעבוד בפועל, יידרש גם הרישום שמתואר בהמשך בסעיף 4.2.
// interface משותף (שקול ל-IDL)
[ComVisible(true)]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
int Add(int a, int b);
}
// COM LocalServer של 64-bit (צד ה-EXE)
[ComVisible(true)]
[Guid("1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("KomuraSoft.CalcService")]
public class CalcService : ICalcService
{
public int Add(int a, int b)
{
// כאן קוראים ל-DLL של 64-bit
return a + b;
}
}
// צד אפליקציית 32-bit (ה-client)
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService");
var calc = (ICalcService)Activator.CreateInstance(t);
int result = calc.Add(1, 2);
בצורה הזו, צד ה-32-bit יכול לטפל בזה בצורה typed. COM משתמש פנימית ב-Proxy/Stub וקורא דרך IPC בשבילכם.
הסיבה שמוסיפים [ProgId("KomuraSoft.CalcService")] היא כדי שה-client יוכל למצוא אותו עם Type.GetTypeFromProgID("KomuraSoft.CalcService"). ProgID הוא רק “כינוי קריא לבני אדם”, ומה שבאמת מוצא את השרת הוא רישום ה-CLSID שיוסבר בהמשך.
flowchart LR
accTitle: מ-ProgID עד הגעה לשרת
accDescr: ה-ProgID שה-client מציין הוא רק כינוי קריא לבני אדם, שממנו נגזר CLSID, ורישום ה-CLSID הוא מה שמאתר בפועל את השרת.
progid["ProgID (כינוי קריא לבני אדם)"] --> clsid["רישום CLSID"]
clsid --> srv["השרת נמצא"]
איור 6: ProgID הוא כינוי הכניסה, אבל מה שמצביע בפועל על השרת הוא רישום ה-CLSID.
4.2. שלבי הרישום המינימליים
COM הוא מנגנון שבו “ה-COM runtime שולף CLSID רשום ב-Registry ומפעיל אותו”, ולכן קוד שלא נרשם לעולם לא יעבוד (Type.GetTypeFromProgID יחזיר null, או ש-CreateInstance יחזיר REGDB_E_CLASSNOTREG). הרישום הדרוש עבור שרת EXE (LocalServer) מצטמצם בסופו של דבר לשלושה מפתחות בלבד.
| מה נרשם | מפתח | ערך |
|---|---|---|
| התאמת ProgID → CLSID | HKEY_CLASSES_ROOT\KomuraSoft.CalcService\CLSID |
{1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11} |
| CLSID → נתיב ה-EXE | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\LocalServer32 |
הנתיב המלא ל-EXE של שרת COM בן 64-bit |
| CLSID → חיפוש הפוך ל-ProgID | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\ProgID |
KomuraSoft.CalcService |
כאן מסתתרת המלכודת שהיא ממש נושא המאמר הזה. התיעוד של Microsoft קובע במפורש ש-HKEY_LOCAL_MACHINE\SOFTWARE\Classes משותף בין אפליקציות 32-bit ל-64-bit, בעוד שתת-המפתח CLSID שתחתיו (וגם Interface וכדומה) נפרד בין צד 32-bit לצד 64-bit (הישות הפיזית בצד 32-bit היא WOW6432Node). כלומר, מפתח ה-ProgID נראה משני הצדדים לאחר כתיבה אחת בלבד, אבל אם רישום ה-CLSID לא נכתב גם ב-32-bit view וגם ב-64-bit view, client 32-bit לא ימצא את השרת.
flowchart TB
accTitle: החלק המשותף והחלק הנפרד ב-Registry
accDescr: מפתח ה-ProgID תחת Classes נראה משני הצדדים אחרי כתיבה אחת, בעוד תת-המפתח CLSID נפרד בין 32-bit view ל-64-bit view ודורש כתיבה לשתיהן, וכשחסר צד 32-bit השרת לא נמצא מ-client 32-bit.
progk["מפתח ProgID (תחת Classes)"] --> shared["נראה משני הצדדים"]
clsk["רישום תחת CLSID"] --> v64["נכתב ב-64-bit view"]
clsk --> v32["נכתב ב-32-bit view"]
v32 -.-> wow["בפועל נמצא ב-WOW6432Node"]
v32 -.-> warn["חסר = לא נראה מ-32-bit"]
איור 7: מפתח ה-ProgID משותף, אבל תת-המפתח CLSID נפרד לפי 32-bit/64-bit view, ולכן צריך לרשום בשתיהן.
הדרך הבטוחה היא להשתמש בדגלים /reg:32 ו-/reg:64 של הפקודה reg בשורת פקודה עם הרשאות Administrator (Microsoft ממליצה שלא לכתוב את Wow6432Node בעצמכם בנתיב).
:: הריצו בשורת פקודה עם הרשאות Administrator
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
set SERVER=C:\Program Files\KomuraSoft\CalcServer.exe
:: 1) ProgID → CLSID (HKLM\SOFTWARE\Classes ישירות משותף בין 32/64)
reg add "HKLM\SOFTWARE\Classes\%PROGID%\CLSID" /ve /d "%CLSID%" /f
:: 2) CLSID → LocalServer32 ו-ProgID (CLSID נפרד בין 32/64, לכן כותבים לשניהם)
:: בערך של LocalServer32 יש להכניס את הנתיב לקובץ ההרצה כולל המרכאות.
:: אם כותבים /d "%SERVER%", המרכאות נעלמות בניתוח הארגומנטים של reg.exe, והערך
:: נשמר כ-C:\Program Files\... בלבד. COM מפרש זאת כשורת פקודה
:: ולכן הוא מחפש קודם את C:\Program.exe, שנחתך לפני הרווח
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID" /ve /d "%PROGID%" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:32
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID" /ve /d "%PROGID%" /f /reg:32
:: בדקו את הערך שנשמר. אם מוצג "C:\Program Files\..." עם מרכאות, זה תקין
reg query "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /reg:64
המרכאות ב-LocalServer32 אינן רק עניין של סדר וניקיון. בלי מרכאות, C:\Program Files\... ניתן לפירוש כ”C:\Program עם Files\... כארגומנט”. כתוצאה מכך, בסביבה שבה מישהו יכול ליצור קובץ C:\Program.exe, הוא עלול להיות מופעל תחילה. אם הנתיב מכיל רווחים, יש להוסיף מרכאות תמיד.
flowchart TB
accTitle: איך הפרשנות משתנה לפי מרכאות ב-LocalServer32
accDescr: אחסון הנתיב בלי מרכאות ב-LocalServer32 מאפשר פרשנות שנחתכת לפני הרווח, כך שקובץ בשם C:\Program.exe עלול לרוץ קודם, בעוד אחסון עם המרכאות מפעיל את קובץ ה-EXE המיועד.
noq["אוחסן בלי מרכאות"] --> cut["אפשרית פרשנות שנחתכת לפני הרווח"]
cut --> evil["C:\Program.exe עלול לרוץ קודם"]
q["אוחסן עם המרכאות"] --> safe["ה-EXE המיועד רץ"]
איור 8: רישום נתיב עם רווח בלי מרכאות פותח פתח להפעלת קובץ הרצה אחר.
ביטול הרישום מתבצע פשוט על ידי מחיקת אותם מפתחות.
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:64
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:32
reg delete "HKLM\SOFTWARE\Classes\%PROGID%" /f
בנוסף, ה-EXE צריך להצהיר בפני COM, בזמן ההפעלה, שהוא “האחראי ל-CLSID הזה”. ב-C/C++ זה נעשה עם CoRegisterClassObject, וב-.NET Framework עם RegistrationServices.RegisterTypeForComClients. רישום ה-Registry מטפל רק עד שלב הפעלת ה-EXE על ידי COM, ולכן אם ההצהרה הזו חסרה, מתקבל כישלון מבלבל שבו ה-EXE מופעל אבל אי אפשר ליצור אובייקט.
flowchart TB
accTitle: רישום ב-Registry מול הצהרת ה-EXE
accDescr: רישום ה-Registry מטפל רק עד הפעלת ה-EXE על ידי COM, ורק כשה-EXE שהופעל מצהיר שהוא אחראי ל-CLSID אפשר ליצור אובייקט — כשההצהרה חסרה מתקבל כישלון מבלבל של EXE שרץ אבל לא יוצר אובייקט.
regd["רישום ב-Registry"] --> boot["COM מפעיל את ה-EXE"]
boot --> ann["ה-EXE מצהיר שהוא אחראי ל-CLSID"]
ann --> ok["אפשר ליצור אובייקט"]
boot -.->|"אם ההצהרה חסרה"| ng["רץ אבל אי אפשר ליצור"]
איור 9: רישום ה-Registry מטפל רק עד הפעלת ה-EXE; בלי הצהרת ה-EXE עצמו, אי אפשר ליצור אובייקט.
לשם השוואה, אם רק בודקים במהלך הפיתוח, אפשר לכתוב באותו מבנה תחת HKCU\SOFTWARE\Classes במקום HKLM, ולהירשם בלי הרשאות Administrator (HKEY_CLASSES_ROOT הוא view מאוחד של HKLM ו-HKCU). עם זאת, גם HKCU\SOFTWARE\Classes\CLSID מטופל בנפרד עבור 32-bit/64-bit, כך שעדיין צריך לכתוב לשתי ה-views.
4.3. השיטה שונה בין .NET Framework ל-.NET (מגרסה 5 ואילך)
הקוד למעלה כתוב ב-C#, אבל השלבים משתנים מאוד בהתאם לגרסת .NET שבה משתמשים. אם מערבבים בין השתיים, נתקעים.
| .NET Framework | .NET (Core 3.0 / 5 ומעלה) | |
|---|---|---|
| כלי רישום | קיים RegAsm.exe (אבל הוא יוצר רישום InprocServer32 עבור in-proc, כך שבסופו של דבר צריך לכתוב את LocalServer32 בעצמכם) |
אין כלי מקביל ל-RegAsm |
| חשיפת COM סטנדרטית | מוסיפים attributes ל-assembly ומריצים RegAsm |
מגדירים <EnableComHosting>true</EnableComHosting> ליצירת *.comhost.dll ורושמים עם regsvr32 (in-proc בלבד) |
| יצירת TypeLib (.tlb) | אפשר ליצור עם TlbExp / RegAsm /tlb |
לא נתמך. כותבים IDL ידנית ומקמפלים ב-MIDL (מ-.NET 6 ואילך אפשר להטמיע את ה-.tlb שנוצר בתוך comhost) |
| ציון CLSID | אופציונלי | עבור מחלקה שנוצרת על ידי COM, חובה לציין CLSID במפורש |
| טיפול ב-AnyCPU | ניתן לשימוש מ-clients 32-bit ו-64-bit כאחד | קובץ ה-*.comhost.dll הנלווה הוא בברירת מחדל 64-bit, ולכן ניתן לשימוש רק מ-clients 64-bit |
המבנה במאמר הזה (שרת EXE) חורג מהטווח הסטנדרטי של EnableComHosting ב-.NET (מגרסה 5 ואילך), ולכן תצטרכו לכתוב את לוגיקת הרישום בעצמכם. יש דוגמה רשמית של Microsoft בשם OutOfProcCOM, ואם בונים בצד .NET, זו נקודת ההתחלה המומלצת.
flowchart TB
accTitle: הדרך לבניית שרת EXE ב-.NET (מגרסה 5 ואילך)
accDescr: שרת ה-EXE של המאמר הזה נמצא מחוץ לטווח EnableComHosting הסטנדרטי ב-.NET 5 ואילך, ולכן צריך לכתוב את הרישום בעצמכם, כשהדוגמה הרשמית OutOfProcCOM משמשת נקודת פתיחה.
exe["שרת EXE ב-.NET (מגרסה 5 ואילך)"] --> range["מחוץ לטווח EnableComHosting הסטנדרטי"]
range --> self["כותבים את הרישום בעצמכם"]
self -.-> smp["הדוגמה הרשמית OutOfProcCOM כנקודת פתיחה"]
איור 10: שרת EXE ב-.NET (מגרסה 5 ואילך) חורג מהפונקציונליות הסטנדרטית, ולכן הרישום נכתב באופן עצמאי כברירת מחדל.
5. קוד לדוגמה מלא
פרסמנו ב-GitHub דוגמה שמממשת את הרעיון שלמעלה בצורה שרצה בפועל.
Call64bitDLLFrom32bitProc - GitHub
המאגר הזה כולל:
- Call64bitDLLFrom32bitProc/ - COM LocalServer של 64-bit (EXE)
- X64DLL/ - DLL של 64-bit (העיבוד בפועל)
- X86App/ - client 32-bit (WinForms)
- scripts/ - סקריפטים לרישום/ביטול רישום של שרת COM
אם בונים ורושמים לפי השלבים שמתוארים ב-README, אפשר לוודא בפועל שהקריאה מ-process 32-bit ל-DLL של 64-bit אכן עובדת.
6. סיכום
גשר COM אינו פתרון-על, אלא מבנה שבו מובחן בבירור בין משימות שמתאימות למשימות שלא. לפני שמחליטים לאמץ אותו, נסו להתאים את המקרה שלכם לטבלה הבאה.
| מקרים מתאימים | מקרים לא מתאימים |
|---|---|
| אי אפשר לבנות מחדש את גוף אפליקציית ה-32-bit (עלות השינוי לא משתלמת) | אפשר לבנות מחדש את צד ה-32-bit ל-64-bit מלכתחילה (זה הפתרון הקצר ביותר) |
| הקריאות בגודל גס (למשל תמונה אחת בקריאה, קובץ אחד בקריאה) | קריאות קטנות ותכופות של אלפי-עשרות אלפי פעמים (התקורה של IPC הופכת דומיננטית) |
| הנתונים המועברים הם מספרים, מחרוזות, מערכים — טיפוסים קלים ל-marshaling | מעבירים בכמות גדולה pointers גולמיים או structs מותאמים מורכבים |
| רוצים שגוף האפליקציה יישאר בחיים גם אם העיבוד בצד 64-bit קורס (הפרדת process היא יתרון) | לא רוצים לכתוב לוגיקת recovery שמטפלת ב-crash או restart של השרת |
| רוצים לשמור על קריאות typed (IntelliSense ובדיקה בזמן קומפילציה) | מספיק עיבוד באצווה חד-פעמי, והעברה דרך stdin/stdout או קובץ מספיקה |
כתמונת המראה של השורה האחרונה, שווה תמיד לשקול גם את החלופה הפשוטה של “להפוך את העיבוד בצד 64-bit ל-EXE קונסולי רגיל, ולתקשר דרך ארגומנטים וקבצים”. גשר COM משתלם כאשר רוצים לשמור על קריאות typed, וכאשר רוצים לקרוא שוב ושוב לשרת ששומר state.
flowchart TB
accTitle: נקודת ההפרדה בין גשר COM לחלופה הפשוטה
accDescr: אם נדרשת קריאה typed או שמירת state על ידי שרת שנקרא שוב ושוב, גשר COM עוזר; אם מספיק עיבוד באצווה חד-פעמי, שווה לשקול חלופה פשוטה של EXE קונסולי עם ארגומנטים וקבצים.
q{"נדרשת קריאה typed או שמירת state?"} -->|"כן"| br["גשר COM"]
q -->|"לא"| alt["EXE קונסולי עם ארגומנטים וקבצים"]
איור 11: הצורך בקריאה typed ובשמירת state הוא מה שמפריד בין גשר COM לחלופה הפשוטה.
כפעולות הבאות, מומלץ לפעול בסדר הזה:
- קודם כול לשכפל (clone) את מאגר הדוגמה מסעיף 5, לבנות ולרשום לפי ה-README, וליצור אצלכם מצב אחד שרץ בפועל.
- לבחור פונקציה אחת בלבד מה-DLL בן 64-bit שלכם, ולהוסיף לה method אחת ב-interface המקביל ל-
ICalcServiceבדוגמה, ולוודא שהיא עוברת. - אחרי שזה עובד, למדוד את מספר הקריאות ואת כמות הנתונים בכל קריאה. אם מקבלים כאן מראש את ההחלטה התכנונית “לרכז לגודל גס” (איחוד כמה קריאות לאחת), חוזרים אחורה פחות בהמשך.
7. מקורות
- סקירה כללית של Component Object Model (COM) https://learn.microsoft.com/en-us/windows/win32/com/component-object-model–com–portal
- רישום COM LocalServer32 https://learn.microsoft.com/en-us/windows/win32/com/localserver32
- יסודות ממשקי COM https://learn.microsoft.com/en-us/windows/win32/com/the-component-object-model
- COM Interop (שימוש מ-.NET) https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cominterop
- WOW64 Registry Redirector (
HKLM\SOFTWARE\Classesמשותף, תת-המפתחCLSIDנפרד בין 32/64) https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys - חשיפת רכיבי .NET (Core / 5 ומעלה) ל-COM https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
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...
Context menu ו-file association ב-Windows 11 — IExplorerCommand, MSIX ו-sparse package
ב-Windows 11 פריטי context menu של האפליקציה נדחקים מאחורי "הצג אפשרויות נוספות". המאמר מסביר את שרשרת extension → ProgID → verb, מגבלות ...
מה זה Reg-Free COM: COM בלי רישום ב-registry
יסודות Reg-Free COM: תפקיד ה-activation context וה-manifest, היתרונות, המגבלות, ואיך מחליטים בפועל.
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
יכולת פעולה הדדית בין 32 ל-64 סיביות
תאימות 32/64 סיביות, גבולות native והחלטות תכנון ב-Windows.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
שימוש חוזר והעברה של נכסים קיימים
מדובר בגישור לצד 64-bit תוך שמירה על 32-bit legacy קיים, נושא שקשור ישירות למינוף מערכות קיימות ולתמיכה במעבר.
ייעוץ טכני וסקירת תכנון
אם רוצים לגבש מראש את גשר ה-COM ואת חלוקת גבולות ה-process, אפשר לשקול זאת כייעוץ טכני וסקירת תכנון.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- אפשר לקרוא ישירות מאפליקציית 32-bit ל-DLL של 64-bit?
- לא. process של 32-bit לא יכול לטעון DLL של 64-bit — זו מגבלה ברמת ה-OS, לא משהו שאפשר לעקוף בטריק. כי הקריאה באותו process חסומה מלכתחילה, צריך מבנה שמפריד את העיבוד בצד 64-bit ל-process נפרד.
- איך משתמשים בפונקציונליות של DLL בן 64-bit מתוך אפליקציית 32-bit?
- הדרך הסטנדרטית היא הפרדה עם Out-of-proc COM (EXE server). קוראים ל-DLL של 64-bit מתוך COM LocalServer (EXE) של 64-bit, ואפליקציית ה-32-bit משתמשת בו בצורה typed דרך COM interface. חושפים את הטיפוסים עם שיתוף COM interface (IDL/TypeLib), וה-COM runtime חוצה את הגבול עם Proxy/Stub ו-IPC.
- מה נקודות תשומת הלב במבנה של גשר COM?
- שלוש עיקריות: הרישום של 32-bit ו-64-bit נפרד (כולל WOW6432Node), structs מותאמים דורשים תכנון marshaling, ובגלל תקורה של IPC צריך להיזהר מקריאות תכופות וקטנות. עדיף לרכז לעיבוד באצווה מאשר לשלוח כמות גדולה של קריאות קטנות.
- יש קוד לדוגמה שבאמת רץ?
- כן. במאגר Call64bitDLLFrom32bitProc ב-GitHub מפורסמת ערכת דוגמה מלאה, כולל COM LocalServer (EXE) של 64-bit, DLL של 64-bit, client 32-bit (WinForms) וסקריפטים לרישום/ביטול רישום של שרת COM. אם בונים ורושמים לפי ההוראות ב-README, אפשר לוודא שהקריאה מ-process 32-bit ל-DLL של 64-bit אכן עובדת.