דוגמת גשר COM שקורא ל-DLL של 64bit מאפליקציית 32bit

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

הדרישה לקרוא מאפליקציית 32bit ל-DLL של 64bit היא די טיפוסית ב-Windows. במיוחד כשרוצים להשתמש רק בפונקציונליות של צד ה-64bit תוך שמירה על נכסים קיימים, מבנה של גשר COM נוטה להיות פתרון מעשי.

קהל היעד: מי שמתחזק אפליקציית Windows קיימת של 32bit ורוצה להשתמש ב-DLL או בספרייה של צד ה-64bit. כתוב כך שאפשר לקרוא גם אם רק “שמעתם על COM אבל מעולם לא בניתם דבר בעצמכם”.

סביבה נדרשת: גרסת 64bit של Windows (x64), וסביבת פיתוח שיכולה לכתוב ‏C# (כמו Visual Studio). רישום שרת COM עבור כל המחשב (תחת HKEY_LOCAL_MACHINE) דורש הרשאות מנהל. את התפיסה הבסיסית של COM סיכמנו במאמר “מה זה COM — למה התכנון של COM ב-Windows עדיין יפה”.

תוכן עניינים

  1. התרחיש
  2. שיטת הפתרון
  3. זרימת העיבוד (תרשים רצף)
  4. קוד לדוגמה (רעיון)
  5. קוד לדוגמה מלא
  6. סיכום
  7. מקורות

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

אפליקציית 32bit לא יכולה לטעון ישירות DLL של 64bit כ-in-proc COM בגלל אי-התאמת מבנה הביטים, ולכן במאמר הזה מפרידים את העיבוד בצד ה-64bit לתהליך נפרד כ-COM LocalServer (שרת EXE), וקוראים לו בצורה מוקלדת דרך ממשק COM משותף ב-IDL וב-TypeLib. מרשלינג ו-Proxy/Stub מגשרים על הקריאה שחוצה את גבול התהליכים, וההנחה היא רישום כפול הן ל-CLSID והן ל-ProgID, וגם ל-WOW64 Registry Redirector שבו תת-המפתח של ה-CLSID נבדל בין תצוגת 32bit לתצוגת 64bit. רישום עבור כל המחשב דורש הרשאות מנהל, ומכיוון שב-‎.NET (מגרסה 5 ואילך) ה-EnableComHosting הסטנדרטי מיועד רק ל-in-proc, יש לכתוב בעצמכם את לוגיקת הרישום של שרת ה-Out-of-proc.

מפת הידע של גשר COM בין 32bit ל-64bitתרשים המראה איך המבנה שבו אפליקציית 32bit קוראת ל-DLL של 64bit דרך שרת EXE של COM LocalServer, קשור לספריית טיפוסים, מרשלינג, רישום CLSID/ProgID, ו-WOW64 Registry Redirector.משתמש במחייבמשתמש במשתמש במחייבמצמצםמחייבמחייבמחייבנשמר במוגדר באמצעותמחייבמחייבשימוש לא מומלץ למחייבמשתמש בשימוש לא מומלץ למחייבמשתמש בגשר COM בין 32 ל-64 סיביות‏COM LocalServer (שרת COM בתהליך נפרד)ספריית טיפוסים (TLB)מרשלינג (marshaling)Proxy/Stub‏In-proc COM (שרת DLL)דרישת התאמת bitnessCLSID(Class ID)ProgID(Programmatic Identifier)מנתב הרישום של WOW64WOW6432Nodereg.exe(/reg:32 /reg:64)רישום מפעל המחלקות (class factory)הרשאות מנהלRegasm.exe‏.NET (מ-Core ואילך).NET Framework‏EnableComHosting/‎.comhost.dll ב-‎.NET‏ (5 ואילך)‏COM (Component Object Model)

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

1. התרחיש

מקרה שבו רוצים להשאיר את האפליקציה הקיימת של 32bit כמות שהיא, אך להשתמש בעיבוד של DLL בן 64bit. אלא שתהליך 32bit אינו יכול לטעון DLL של 64bit. זו מגבלה ברמת מערכת ההפעלה, ולא משהו שניתן לפתור בתחכום.

מצבים נפוצים הם:

  • האפליקציה הקיימת של 32bit היא נכס גדול, ואי אפשר להעביר אותה במהירות
  • יש פונקציונליות חדשה בצד ה-DLL של 64bit, או שספריות התלות הן 64bit בלבד
  • רוצים לקרוא מצד ה-32bit “בצורה מוקלדת”

בשילוב הזה, הדרך לקרוא בתוך אותו תהליך חסומה מלכתחילה.

התרחיש הנתוןתרשים המראה שהאפליקציה הקיימת של 32bit רוצה להשתמש בפונקציונליות של DLL בן 64bit, אך תהליך 32bit אינו יכול לטעון DLL של 64bit בגלל מגבלה ברמת מערכת ההפעלה, כך שהדרך לקרוא באותו תהליך חסומה.אפליקציית 32bit קיימתרוצה להשתמש ב-DLL של 64bitאי אפשר לטעון באותו תהליךמגבלת מערכת הפעלה, אין עקיפה

איור 1: תהליך 32bit אינו יכול לטעון DLL של 64bit, ולכן אין מלכתחילה דרך לקרוא באותו תהליך.

2. שיטת הפתרון

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

מונח משמעות
In-proc COM (שרת DLL) טוענים את רכיב ה-COM באותו תהליך כמו הצד הקורא. מהיר, אך לא נטען אם מספר הביטים לא תואם
Out-of-proc COM (שרת EXE) מפעילים את רכיב ה-COM כתהליך נפרד. אפשר לשתף פעולה גם כשמספר הביטים שונה
LocalServer שרת COM שרץ כתהליך נפרד באותו מחשב. רושמים את הנתיב ל-EXE במפתח LocalServer32 ברג’יסטרי
IDL / TypeLib ההגדרה (IDL) של צורת הממשק (שמות שיטות, טיפוסי ארגומנטים), וגרסתו הבינארית (TypeLib). משמשים כדי ששני הצדדים יראו את אותו “חוזה”
Marshaling סידור מחדש של ארגומנטים וערכים מוחזרים לצורה שאפשר לשלוח, כדי לחצות גבול תהליכים. ההפך הוא unmarshaling
Proxy / Stub קוד ייצוג שמבצע בפועל את ה-marshaling. Proxy עומד בצד הקורא, Stub עומד בצד השרת
WOW6432Node המיקום הפיזי שבו מאוחסן תוכן הרג’יסטרי עבור אפליקציות 32bit ב-Windows של 64bit. אותו שם מפתח מכיל תוכן שונה בצד 32bit ובצד 64bit

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

המבנה הבסיסי של גשר COMתרשים המראה שאפליקציית 32bit קוראת דרך COM לשרת COM של 64bit בתור EXE, והשרת קורא באופן פנימי ל-DLL של 64bit.קורא דרך COMקורא באופן פנימיאפליקציית 32bitשרת COM של 64bit(EXE)DLL של 64bit

איור 2: את ה-DLL של 64bit ממקמים בתוך שרת EXE של 64bit, ואפליקציית 32bit משתמשת בשרת הזה דרך COM.

הזרימה היא כדלקמן.

  1. מכינים COM LocalServer (EXE) של 64bit, שקורא באופן פנימי ל-DLL של 64bit
  2. משתפים ממשק COM (IDL/TypeLib) וחושפים את הטיפוסים
  3. אפליקציית ה-32bit קוראת ל-COM בצורה “מוקלדת” (תקשורת דרך Proxy/Marshal)

עם זאת, יש גם נקודות לתשומת לב.

  • הרישום של 32bit ו-64bit נפרד (כולל WOW6432Node)
  • מבנים מותאמים-אישית דורשים תכנון marshaling
  • יש תקורת IPC, ולכן צריך להיזהר מקריאות בתדירות גבוהה

במילים אחרות, הדרך הסטנדרטית היא “להעביר את העיבוד של 64bit לתהליך נפרד, ולגשר באמצעות COM”.

שלושת שלבי בניית הגשרתרשים המראה שמכינים COM LocalServer של 64bit, חושפים את הטיפוסים ב-IDL/TypeLib, ואפליקציית 32bit קוראת בצורה מוקלדת - שלושה שלבים שמרכיבים את הגשר.הכנת COM LocalServer של 64bitחשיפת טיפוסים ב-IDL / TypeLibאפליקציית 32bit קוראת בצורה מוקלדתהתקשורת דרך Proxy / Marshal

איור 3: שלושת השלבים — הכנת ה-LocalServer, חשיפת הטיפוסים, והקריאה המוקלדת — יוצרים יחד את הגשר.

3. זרימת העיבוד (תרשים רצף)

להלן הזרימה כאשר אפליקציית 32bit קוראת לעיבוד של DLL בן 64bit.

זרימת הקריאה מ-32bit ל-64bitתרשים רצף המראה איך קריאה מאפליקציית 32bit עוברת דרך Proxy, תקשורת בין-תהליכית ו-Stub אל שרת ה-64bit וה-DLL, והתוצאה חוזרת באותו מסלול.תשתית ה-marshaling של COM המותקנת מטפלת‏DLL של 64bitשרת ‏COM של 64bit(EXE)‏COM Stub(צד 64bit)‏RPC/IPC(תקשורת בין-תהליכית)‏COM Proxy(צד 32bit)לקוח 32bit‏DLL של 64bitשרת ‏COM של 64bit(EXE)‏COM Stub(צד 64bit)‏RPC/IPC(תקשורת בין-תהליכית)‏COM Proxy(צד 32bit)לקוח 32bitמבצע marshaling לפרמטריםמבצע unmarshaling לפרמטריםמבצע marshaling לערך המוחזרמבצע unmarshaling לערך המוחזרICalcService.Add(1, 2)נתונים מסודרים (serialized)מועברים דרך גבול התהליכיםAdd(1, 2)קריאה לפונקציה nativeתוצאה: 3תוצאה: 3תוצאה מסודרת (serialized)מועברת דרך גבול התהליכיםתוצאה: 3

איור 4: הקריאה מאפליקציית 32bit עוברת דרך Proxy, תקשורת בין-תהליכית ו-Stub אל שרת ה-64bit וה-DLL, והתוצאה חוזרת באותו מסלול.

נקודות מרכזיות:

  • אפליקציית 32bit יכולה לקרוא בבטיחות טיפוסים דרך הממשק ICalcService
  • זמן הריצה של COM חוצה את גבול התהליכים באמצעות DLL רשום של Proxy/Stub, marshaler מסוג TypeLib, marshaler סטנדרטי ועוד
  • בגלל התקורה של תקשורת בין-תהליכית, עדיף עיבוד באצווה על פני קריאות קטנות
עקרון גודל הקריאהתרשים המראה שלתקשורת בין-תהליכית יש תקורה, כך שקריאות קטנות וחוזרות מצטברות, ואילו ריכוז לעיבוד באצווה מקטין את ההשפעה - וזו הגישה המועדפת.תקורת התקשורת הבין-תהליכיתקריאות קטנות חוזרות מצטברותריכוז לעיבוד באצווה מקטין השפעהזו הגישה המועדפת

איור 5: לכל חציית גבול תהליכים יש עלות, ולכן עדיף לרכז את הקריאות לגודל גס.

4. קוד לדוגמה (רעיון)

4.1. ממשק משותף, שרת ולקוח

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

// ממשק משותף (שקול ל-IDL)
[ComVisible(true)]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
    int Add(int a, int b);
}

// COM LocalServer של 64bit (צד ה-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 של 64bit
        return a + b;
    }
}

// צד אפליקציית 32bit (הלקוח)
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService");
var calc = (ICalcService)Activator.CreateInstance(t);
int result = calc.Add(1, 2);

בצורה הזו, צד ה-32bit יכול לטפל בזה “בצורה מוקלדת”. ‏COM משתמש באופן פנימי ב-Proxy/Stub וקורא דרך IPC בשבילכם.

הסיבה שמוסיפים [ProgId("KomuraSoft.CalcService")] היא כדי שהלקוח יוכל למצוא אותו באמצעות Type.GetTypeFromProgID("KomuraSoft.CalcService"). ‏ProgID הוא רק “כינוי קריא לבני אדם”, ומה שבאמת מוצא את השרת הוא רישום ה-CLSID שיוסבר בהמשך.

מ-ProgID עד הגעה לשרתתרשים המראה שה-ProgID שהלקוח מציין הוא רק כינוי קריא לבני אדם, שממנו נגזר CLSID, ורישום ה-CLSID הוא מה שמאתר בפועל את השרת.ProgID(כינוי קריא לבני אדם)רישום CLSIDהשרת נמצא

איור 6: ‏ProgID הוא כינוי הכניסה, אבל מה שמצביע בפועל על השרת הוא רישום ה-CLSID.

4.2. שלבי הרישום המינימליים

‏COM הוא מנגנון שבו “זמן הריצה של COM שולף CLSID רשום ברג’יסטרי ומפעיל אותו”, ולכן קוד שלא נרשם לעולם לא יפעל (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 בן 64bit
CLSID ← חיפוש הפוך ל-ProgID HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\ProgID KomuraSoft.CalcService

כאן מסתתרת המלכודת שהיא ממש נושא המאמר הזה. התיעוד של Microsoft קובע במפורש ש-HKEY_LOCAL_MACHINE\SOFTWARE\Classes משותף בין אפליקציות 32bit ל-64bit, בעוד שתת-המפתח CLSID שתחתיו (וגם Interface וכדומה) נפרד בין צד 32bit לצד 64bit (הישות הפיזית בצד 32bit היא WOW6432Node). כלומר, מפתח ה-ProgID נראה משני הצדדים לאחר כתיבה אחת בלבד, אבל אם רישום ה-CLSID לא נכתב גם בתצוגת 32bit וגם בתצוגת 64bit, לקוח 32bit לא ימצא את השרת.

החלק המשותף והחלק הנפרד ברישוםתרשים המראה שמפתח ה-ProgID תחת Classes נראה משני הצדדים אחרי כתיבה אחת, בעוד תת-המפתח CLSID נפרד בין תצוגת 32bit ל-64bit ודורש כתיבה לשתיהן, וכשחסר צד 32bit השרת לא נמצא מלקוח 32bit.מפתח ProgID(תחת Classes)נראה משני הצדדיםרישום תחת CLSIDנכתב בתצוגת 64bitנכתב בתצוגת 32bitבפועל נמצא ב-WOW6432Nodeחסר = לא נראה מ-32bit

איור 7: מפתח ה-ProgID משותף, אבל תת-המפתח CLSID נפרד לפי תצוגת 32bit/64bit, ולכן צריך לרשום בשתיהן.

הדרך הבטוחה היא להשתמש בדגלים /reg:32 ו-/reg:64 של הפקודה reg בשורת פקודה עם הרשאות מנהל (Microsoft ממליצה שלא לכתוב את Wow6432Node בעצמכם בנתיב).

:: הריצו בשורת פקודה עם הרשאות מנהל
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, הוא עלול להיות מופעל תחילה. אם הנתיב מכיל רווחים, יש להוסיף מרכאות תמיד.

איך הפרשנות משתנה לפי מרכאות ב-LocalServer32תרשים המראה שאחסון הנתיב ללא מרכאות ב-LocalServer32 מאפשר פרשנות שנחתכת לפני הרווח, כך שקובץ בשם C:\Program.exe עלול להיות מופעל תחילה, בעוד אחסון עם המרכאות מפעיל את קובץ ה-EXE המיועד.אוחסן בלי מרכאותאפשרית פרשנות שנחתכת לפני הרווחC:\Program.exe עלול לרוץ קודםאוחסן עם המרכאותה-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. רישום הרג’יסטרי מטפל רק עד שלב הפעלת ה-EXE על ידי COM, ולכן אם ההצהרה הזו חסרה, מתקבל כישלון מבלבל שבו ה-EXE מופעל אך לא ניתן ליצור אובייקט.

רישום ברג'יסטרי מול הצהרת ה-EXEתרשים המראה שרישום הרג'יסטרי מטפל רק עד הפעלת ה-EXE על ידי COM, ורק כשה-EXE שהופעל מצהיר שהוא אחראי ל-CLSID אפשר ליצור אובייקט - כשההצהרה חסרה מתקבל כישלון מבלבל של EXE שרץ אך לא יוצר אובייקט.אם ההצהרה חסרהרישום ברג'יסטריCOM מפעיל את ה-EXEה-EXE מצהיר שהוא אחראי ל-CLSIDניתן ליצור אובייקטרץ אך לא ניתן ליצור

איור 9: רישום הרג’יסטרי מטפל רק עד הפעלת ה-EXE; בלי הצהרת ה-EXE עצמו, לא ניתן ליצור אובייקט.

לשם השוואה, אם רק בודקים במהלך הפיתוח, אפשר לכתוב באותו מבנה תחת HKCU\SOFTWARE\Classes במקום HKLM, ולהירשם בלי הרשאות מנהל (HKEY_CLASSES_ROOT הוא תצוגה מאוחדת של HKLM ו-HKCU). עם זאת, גם HKCU\SOFTWARE\Classes\CLSID מטופל בנפרד עבור 32bit/64bit, כך שעדיין צריך לכתוב לשתי התצוגות.

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 ניתן לשימוש מלקוחות 32bit ו-64bit כאחד קובץ ה-*.comhost.dll הנלווה הוא בברירת מחדל 64bit, ולכן ניתן לשימוש רק מלקוחות 64bit

המבנה במאמר הזה (שרת EXE) חורג מהטווח הסטנדרטי של EnableComHosting ב-‎.NET (מגרסה 5 ואילך), ולכן תצטרכו לכתוב את לוגיקת הרישום בעצמכם. יש דוגמה רשמית של Microsoft בשם OutOfProcCOM, ואם בונים בצד ‎.NET, זו נקודת ההתחלה המומלצת.

הדרך לבניית שרת EXE ב-‎.NET (מגרסה 5 ואילך)תרשים המראה ששרת ה-EXE של המאמר הזה נמצא מחוץ לטווח EnableComHosting הסטנדרטי ב-‎.NET 5 ואילך, ולכן צריך לכתוב את הרישום בעצמכם, כשהדוגמה הרשמית OutOfProcCOM משמשת נקודת פתיחה.שרת EXE ב-‎.NET(מגרסה 5 ואילך)מחוץ לטווח EnableComHosting הסטנדרטיכותבים את הרישום בעצמכםהדוגמה הרשמית OutOfProcCOM כנקודת פתיחה

איור 10: שרת EXE ב-‎.NET (מגרסה 5 ואילך) חורג מהפונקציונליות הסטנדרטית, ולכן הרישום נכתב באופן עצמאי כברירת מחדל.

5. קוד לדוגמה מלא

פרסמנו ב-GitHub דוגמה שמממשת את הרעיון שלמעלה בצורה שפועלת בפועל.

Call64bitDLLFrom32bitProc - GitHub

המאגר הזה כולל:

  • Call64bitDLLFrom32bitProc/ - ‏COM LocalServer של 64bit (EXE)
  • X64DLL/ - ‏DLL של 64bit (העיבוד בפועל)
  • X86App/ - לקוח 32bit (WinForms)
  • scripts/ - סקריפטים לרישום/ביטול רישום של שרת COM

אם בונים ורושמים לפי השלבים שמתוארים ב-README, אפשר לוודא בפועל שהקריאה מתהליך 32bit ל-DLL של 64bit אכן פועלת.

6. סיכום

גשר COM אינו פתרון-על, אלא מבנה שבו מובחן בבירור בין משימות שמתאימות למשימות שלא. לפני שמחליטים לאמץ אותו, נסו להתאים את המקרה שלכם לטבלה הבאה.

מקרים מתאימים מקרים לא מתאימים
אי אפשר לבנות מחדש את גוף אפליקציית ה-32bit (עלות השינוי לא משתלמת) ניתן לבנות מחדש את צד ה-32bit ל-64bit מלכתחילה (זה הפתרון הקצר ביותר)
הקריאות בגודל גס (למשל תמונה אחת בקריאה, קובץ אחד בקריאה) קריאות קטנות ותכופות של אלפי-עשרות אלפי פעמים (התקורה של IPC הופכת דומיננטית)
הנתונים המועברים הם מספרים, מחרוזות, מערכים — טיפוסים קלים ל-marshaling מעבירים בכמות גדולה מצביעים גולמיים או מבנים מותאמים-אישית מורכבים
רוצים שגוף האפליקציה יישאר בחיים גם אם העיבוד בצד 64bit קורס (הפרדת תהליכים היא יתרון) לא רוצים לכתוב לוגיקת התאוששות שמטפלת בקריסה או הפעלה מחדש של השרת
רוצים לשמור על קריאות מוקלדות (IntelliSense ובדיקה בזמן קומפילציה) מספיק עיבוד באצווה חד-פעמי, והעברה דרך קלט/פלט סטנדרטי או קובץ מספיקה

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

נקודת ההפרדה בין גשר COM לחלופה הפשוטהתרשים המראה שאם נדרשת קריאה מוקלדת או שמירת מצב על ידי שרת שנקרא שוב ושוב, גשר COM עוזר; אם מספיק עיבוד באצווה חד-פעמי, שווה לשקול חלופה פשוטה של EXE קונסולי עם ארגומנטים וקבצים.כןלאנדרשת קריאה מוקלדת או שמירת מצב?גשר COMEXE קונסולי עם ארגומנטים וקבצים

איור 11: הצורך בקריאה מוקלדת ובשמירת מצב הוא מה שמפריד בין גשר COM לחלופה הפשוטה.

כפעולות הבאות, מומלץ לפעול בסדר הזה:

  1. קודם כול לשכפל (clone) את מאגר הדוגמה מסעיף 5, לבנות ולרשום לפי ה-README, וליצור אצלכם מצב אחד שפועל בפועל.
  2. לבחור פונקציה אחת בלבד מה-DLL בן 64bit שלכם, ולהוסיף לה שיטה אחת בממשק המקביל ל-ICalcService בדוגמה, ולוודא שהיא עוברת.
  3. אחרי שזה עובד, למדוד את מספר הקריאות ואת כמות הנתונים בכל קריאה. אם מקבלים כאן מראש את ההחלטה התכנונית “לרכז לגודל גס” (איחוד כמה קריאות לאחת), חוזרים אחורה פחות בהמשך.

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

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

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

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

שאלות נפוצות

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

האם אפשר לקרוא ישירות מאפליקציית 32bit ל-DLL של 64bit?
לא אפשרי. תהליך 32bit אינו יכול לטעון DLL של 64bit — זו מגבלה ברמת מערכת ההפעלה, ולא משהו שאפשר לעקוף בתחכום. מכיוון שהדרך לקרוא בתוך אותו תהליך חסומה מלכתחילה, נדרש מבנה שמפריד את העיבוד בצד ה-64bit לתהליך נפרד.
איך אפשר להשתמש בפונקציונליות של DLL בן 64bit מתוך אפליקציית 32bit?
הדרך הסטנדרטית היא הפרדה באמצעות Out-of-proc COM (שרת EXE). קוראים ל-DLL של 64bit מתוך COM LocalServer (EXE) של 64bit, ואפליקציית ה-32bit משתמשת בו בצורה מוקלדת דרך ממשק COM. חושפים את הטיפוסים באמצעות שיתוף ממשק COM (IDL/TypeLib), וזמן הריצה של COM חוצה את הגבול באמצעות Proxy/Stub ותקשורת בין-תהליכית.
מהן נקודות תשומת הלב במבנה של גשר COM?
יש שלוש נקודות עיקריות: הרישום של 32bit ו-64bit נפרד (כולל WOW6432Node), מבנים מותאמים-אישית דורשים תכנון marshaling, ובגלל התקורה של תקשורת בין-תהליכית יש להיזהר מקריאות תכופות וקטנות. עדיף לרכז לעיבוד באצווה מאשר לשלוח כמות גדולה של קריאות קטנות.
יש קוד לדוגמה שבאמת פועל?
כן. במאגר Call64bitDLLFrom32bitProc ב-GitHub מפורסמת ערכת דוגמה מלאה, הכוללת COM LocalServer (EXE) של 64bit,‏ DLL של 64bit, לקוח 32bit (WinForms) וסקריפטים לרישום/ביטול רישום של שרת COM. אם בונים ורושמים לפי ההוראות ב-README, אפשר לוודא שהקריאה מתהליך 32bit ל-DLL של 64bit אכן פועלת.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג