מה זה Reg-Free COM - שימוש ב-COM בלי רישום

· עודכן בתאריך: · · COM, Reg-Free COM, Registration-Free COM, פיתוח Windows, טכנולוגיה ישנה

בפרויקטים של COM /‏ ActiveX /‏ OCX, אותה בעיה חוזרת בכל הפצה ועדכון.

  • נדרש regsvr32
  • לרוב נדרשות הרשאות מנהל
  • התנגשות עם גרסה אחרת שהתקינה אפליקציה אחרת
  • הסרת התקנה שגוררת גם מוצר אחר
  • עובד במחשב הפיתוח, לא עובד בסביבה נקייה

מה שיכול לצמצם משמעותית את הטרחה הזו הוא Reg-Free COM. עם זאת, למרות השם, זה לא “קסם שמוחק את כל הטרחה של COM”. מה שנעלם הוא בעיקר הטרחה שנגררת מרישום גלובלי. bitness,‏ DLL-ים תלויים, ספריית טיפוסים, וקושי מודל ה-thread לא נעלמים.

מה נעלם ומה נשאר ב-Reg-Free COMתרשים שמראה שב-Reg-Free COM מה שנעלם בעיקר הוא הטרחה שנגררת מרישום גלובלי, ואילו bitness, DLL-ים תלויים, ספריית טיפוסים וקושי מודל ה-thread לא נעלמים.Reg-Free COMפחות טרחת רישום גלובליגם קושי שנשארbitness / DLL תלוי / ספריית טיפוסים

איור 1: לא קסם - מצפים בנפרד לטרחה שנעלמת ולטרחה שנשארת.

המאמר הזה מסדר את Reg-Free COM בעיקר בהקשר של שימוש ב-COM DLL /‏ OCX כמוגבל לאפליקציה בודדת (private) באפליקציית שולחן עבודה ל-Windows.

קהל היעד והנחות היסוד

המאמר נכתב עבור מפתחים שמפיצים אפליקציית שולחן עבודה ל-Windows שמשתמשת ב-COM DLL /‏ OCX קיימים, ורוצים להיפטר מ-regsvr32 ומהרשאות מנהל. ההנחה היא שנתקלתם לפחות פעם אחת ביסודות COM (‏CLSID,‏ ProgID,‏ CoCreateInstance, שרת in-proc). אם זה עדיין לא ברור, מהיר יותר לקרוא קודם את “מה זה COM / ActiveX / OCX - הסבר על ההבדלים והקשרים”.

חלק ההליכים משתמש ב-mt.exe וב-sxstrace מ-Windows SDK, ולכן ההנחה היא סביבה עם Visual Studio או Windows SDK מותקנים.

1. קודם כל - המסקנה (בקצרה)

אם אומרים את זה בגסות אך שימושית, זה כך:

  • Reg-Free COM היא דרך שבה מידע הרישום של COM מוחזק ב-manifest ולא ב-registry
  • בזמן ריצה, בפתרון של CoCreateInstance או CLSIDFromProgID, קודם נבדק ‏activation context
  • לכן, אפשר להחזיק COM DLL /‏ OCX כ-‏private לכל אפליקציה
  • היתרונות העיקריים הם קלות הפצה ב-XCOPY, הימנעות מהתנגשות גרסאות, הסרת התקנה שפחות נשברת
  • עם זאת, בעיית 32bit / 64bit לא נעלמת. אין דרך לעקוף את זה בכתיבת manifest
  • כמו כן, יש צורך לחשוב בנפרד על DLL-ים תלויים, ספריית טיפוסים, הפניה בזמן תכנון, תלות ברישום לא סטנדרטי
  • בפועל, זה מתאים מאוד ל-מצבים שרוצים להניח בצד רכיב COM ייעודי לאפליקציה

בקיצור, Reg-Free COM היא מנגנון שמחזיר את הפעלת COM (activation) לרמת אפליקציה בודדת.

מקום מידע הרישום משתנהתרשים שמראה ש-Reg-Free COM מחזיקה את מידע הרישום של COM ב-manifest ולא ב-registry, ומכיוון שבפתרון של CoCreateInstance וכדומה נבדק קודם ה-activation context, אפשר להחזיק רכיבי COM כ-private לכל אפליקציה.מידע הרישום ב-manifestבפתרון, activation context נבדק קודםאפשר להחזיק רכיבי COM כ-private לאפליקציהקל יותר להפיץ ב-XCOPY ולהימנע מהתנגשות

איור 2: המהות היא שכניסת הפתרון עוברת מ-registry ל-manifest.

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

Reg-Free COM הוא מנגנון שמחזיק את מידע רישום ה-COM ב-manifest ולא ב-registry, ובזמן ריצה בודק תחילה את ה-activation context כדי לפתור את ה-DLL. מניפסט היישום מצהיר על side-by-side assembly שהוא תלוי בו, ומניפסט הרכיב מחזיק מידע כמו CLSID ו-ProgID שבמקור היה ב-registry - בחלוקת התפקידים הזו, משבצים עם mt.exe ועוקבים אחרי כשל פתרון עם sxstrace. עם זאת, דרישת התאמת bitness בין 32 ל-64 לא נפתרת, וגם הטיפול ב-DLL תלוי ובספריית טיפוסים נשאר כסוגיה נפרדת, ולכן זה מתאים להפיכת נכסי שולחן עבודה קיימים כמו VB6,‏ MFC ו-WinForms למקומיים לאפליקציה, אך פחות מתאים למצבים שדורשים שיתוף COM ברמת המכונה כולה. ב-.NET 5+‏/.NET 8, יוצרים comhost עם EnableComHosting, ואם מפעילים EnableRegFreeCom אפשר לייצא side-by-side manifest עבור Reg-Free COM.

מפת הידע של Reg-Free COMתרשים שמראה ש-Reg-Free COM מחזיק את מידע הרישום של COM ב-manifest ולא ב-registry ופותר DLL דרך בדיקת activation context בזמן ריצה תחילה, את חלוקת התפקידים בין מניפסט היישום למניפסט הרכיב, הרכב עם mt.exe וחקירת כשל עם sxstrace, והקשר ל-comhost של .NET 5+.משתמש במשתמש במשתמש במחייבמממש אתמחייביורש אתמשתמש במוגדר באמצעותנבדק באמצעותמוגדר באמצעותמחייבמשתמש במחייבמוגדר באמצעותמממש אתמשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במענה מומלץ למענה מומלץ למענה מומלץ לשימוש לא מומלץ ל‏Reg-Free COM (COM ללא רישום)הקשר הפעלה (activation context)מניפסט היישום (Win32 side-by-side)מניפסט רכיב (assembly manifest)side-by-side assemblyדרישת התאמת bitnessregsvr32ספריית טיפוסים (TLB)‏mt.exe (כלי המניפסטים)sxstraceסדר החיפוש של private assembly‏.NET (מ-Core ואילך)COM host(*.comhost.dll)EnableComHostingEnableRegFreeComVisual Basic 6.0(VB6)CLSID(Class ID)ProgID(Programmatic Identifier)‏COM (Component Object Model)ActiveXOCXMFC(Microsoft Foundation Classes)Windows Formsשיתוף COM ברמת המכונה כולה

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

2. Reg-Free COM כפי שמדובר עליו במאמר הזה

Reg-Free COM הוא קיצור של Registration-Free COM. לפעמים נכתב גם “COM ללא צורך ברישום”.

ה”אין צורך ברישום” כאן פירושו לא להסתמך באופן מלא על רישום גלובלי ב-registry דרך HKCR /‏ CLSID /‏ InprocServer32 וכדומה, כדי להשתמש ב-COM. זה לא אומר שCOM עצמו נעלם, ולא שה-GUID כבר לא נחוץ.

מה שבמאמר הזה עוסקים בו בעיקר הוא:

  • COM DLL נייטיבי
  • שרת COM מבוסס ATL
  • ActiveX /‏ OCX
  • Interop COM מבוסס .NET Framework
  • חשיפה דרך COM host של ‏.NET 5+ /‏ .NET 8

מנגד, שתי הנקודות שרוצים להדגיש במאמר הזה הן:

  1. ‏Reg-Free COM עוסקת ב”הפעלה” (activation)
  2. הפצת מידע הטיפוס והגדרת הפניה בזמן תכנון עלולים להישאר כסוגיה נפרדת

אם מערבבים בין אלה, השיח נעשה עמום.

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

איור 3: סוגיית זמן הריצה וסוגיית זמן התכנון מטופלות בנפרד מלכתחילה.

3. סידור ראשוני בתמונה אחת

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

מונח משמעות
‏activation context מבנה נתונים בזמן ריצה שמחזיק “איזו גרסה של איזה assembly ה-thread הזה משתמש בו כרגע”. ‏CoCreateInstance בודק את זה לפני ה-registry
‏side-by-side assembly מנגנון ב-Windows שמאפשר לרכיב עם אותו שם, בגרסאות שונות, להתקיים יחד באותה מכונה. מזוהה דרך manifest, ו-assemblyIdentity הוא התג המזהה
מניפסט היישום XML שמצמידים לצד ה-EXE, שמציין “באיזה side-by-side assembly זה תלוי”
מניפסט רכיב (‏assembly manifest) XML שמצמידים לצד הרכיב, שמציין “אילו קבצים ה-assembly הזה כולל, ואילו מחלקות COM הוא חושף”. מידע שבמקור היה ב-registry עובר לכאן

ה-activation context מוחזק לכל thread בנפרד. COM מעביר את ה-activation context של ה-thread היוצר אל ה-thread המארח לפני שהוא קורא ל-LoadLibrary או ל-DllGetClassObject, ולכן לא נדרשת הכנה מיוחדת מצד הקורא.

עם זאת, מהיר יותר לראות תחילה את התמונה הכללית בתרשים אחד.

התמונה הכללית של Reg-Free COMתרשים שמראה שהאפליקציה מפנה דרך מניפסט היישום ומניפסט הרכיב אל ה-DLL, ובמקביל האפליקציה משתמשת ב-activation context כדי לפתור את קריאת ה-COM ולהגיע לאותו DLL בלי registry.MyApp.exeמניפסט היישוםdependentAssemblyמניפסט הרכיבfile / comClass / typelibVendorControl.dll / .ocxActivation ContextCLSIDFromProgID / CoCreateInstance

איור 4: עוקבים מ-manifest האפליקציה ל-manifest הרכיב, ומגיעים ל-DLL בלי להשתמש ב-registry.

ב-COM רגיל, כשקוראים ל-CoCreateInstance, עוברים דרך ה-registry כדי לקבוע איזה DLL לטעון. ב-Reg-Free COM, לפני זה נבדק ה-activation context שפעיל כרגע, ומתוכו נפתר לפי מידע ה-manifest שכתוב שם.

בגלל זה, קל יותר שאפליקציה A ואפליקציה B על אותה מכונה יחזיקו גרסאות שונות של אותה משפחת רכיבי COM. זה מחזיר קצת אחורה את תרבות השיתוף של COM לכיוון מקומי לאפליקציה.

4. למה הפצת COM רגילה נוטה להיות כבדה

הפצת COM רגילה כבדה לא כי COM עצמו רע, אלא בגלל ההנחה של רישום גלובלי.

כדי להשתמש במחלקת COM, נדרש בערך המידע הבא.

מידע תפקיד
CLSID ‏GUID שמזהה את המחלקה באופן ייחודי
ProgID שם נוח לבני אדם
InprocServer32 איזה DLL לטעון
ThreadingModel הנחה כמו Apartment /‏ Both
TypeLib מידע טיפוס

כשאלה נכנסים ל-registry, זה נוח ברמת המכונה כולה, כי קל לשתף בין כמה אפליקציות.

אבל בפועל, השיתוף הזה מתגלה כבעייתי.

  • ההתקנה של מוצר אחד דורסת את רישום COM של מוצר אחר
  • ההסרה “חושבת” שהיא מסירה רק את שלה, ושוברת COM משותף
  • רישום שקיים במקרה במחשב הפיתוח, לא קיים במחשב הייצור
  • רישום 32bit ו-64bit לא תואמים, והתופעה בלבד נראית מוזרה

כלומר, מודל ההפצה, ולא COM עצמו, הוא מה שמטריד אנשים לעיתים קרובות. Reg-Free COM הוא מנגנון שמצמצם את הסבל הזה במודל ההפצה.

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

איור 5: הבעיה האמיתית היא לא COM עצמו, אלא ההנחה של רישום גלובלי.

5. איך המנגנון של Reg-Free COM עובד

5.1 מניפסט היישום כותב את התלות

ראשית, צד האפליקציה כותב במניפסט היישום באיזה side-by-side assembly היא תלויה.

את ה-manifest הזה אפשר:

  • למקם ליד ה-EXE כמו MyApp.exe.manifest
  • לשבץ בתוך ה-EXE כמשאב

בפועל, אם רוצים להקל על הפצה והחלפה - קובץ חיצוני; אם עדיפות לחוסן ופשטות הפצה - שיבוץ.

יש לציין שאם יש גם קובץ חיצוני וגם שיבוץ, ה-manifest שבמערכת הקבצים מקבל עדיפות.

5.2 מניפסט הרכיב כותב את מידע ה-COM

בשלב הבא, צד COM מחזיק ב-manifest הרכיב מידע שבמקור היה ב-registry.

מה שנכנס לכאן, למשל, הוא:

  • comClass
  • clsid
  • progid
  • threadingModel
  • typelib
  • אם צריך, גם proxy / stub או window class

כלומר, זה מייצג את הרעיון של לתאר את פני ה-COM עם XML במקום registry.

את ה-manifest הזה אפשר להרכיב:

  • כקובץ נפרד מה-DLL
  • כשיבוץ בתוך ה-DLL כמשאב

בפועל, שיבוץ בתוך ה-DLL כ-private assembly נוטה פחות להיכשל. תפעול עם קובץ נפרד קל להבנה, אבל קל למעוד בהתאמה בין שם הקובץ ל-assemblyIdentity, במיקום ההצבה, או בקובץ שנשכח מהעתקה.

איך מחזיקים את מניפסט הרכיבתרשים שמראה שמידע כמו comClass, clsid, typelib שבמקור היה ב-registry, מניפסט הרכיב מחזיק אותו כ-XML, ואפשר לבחור בין קובץ נפרד מה-DLL לבין שיבוץ בתוך ה-DLL כמשאב, כאשר בפועל השיבוץ נוטה פחות להיכשל.מידע שהיה ב-registry, ב-XMLמוצב כקובץ נפרדמשובץ בתוך ה-DLLקל למעוד בהעתקה שכוחה וכדומהבפועל נוטה פחות להיכשל

איור 6: תוכן המידע זהה, אבל אופן ההצבה קובע את שיעור הכשלים.

5.3 בזמן ריצה, נבדק קודם ה-activation context

כאן טמון הלב של Reg-Free COM.

כשהאפליקציה קוראת ל-CLSIDFromProgID או ל-CoCreateInstance, ה-COM runtime בודק את ה-activation context הפעיל. אם קיים שם המידע הנדרש להמרה מ-ProgID ל-CLSID, ומ-CLSID ל-DLL, הפתרון מתבצע בלי להשתמש ב-registry.

לעומת זאת, אם המידע הנדרש חסר ב-manifest, זה נופל לפתרון הרגיל המבוסס על רישום. בגלל ההתנהגות הזו, נוצרת מלכודת שבה המחשב עובד במקרה במחשב הפיתוח. חשבתם שהצלחתם להפוך את זה ל-Reg-Free, אבל בפועל זה נעזר ברישום מקומי.

זהו הבור הכי מרושע של Reg-Free COM.

המלכודת שנופלת לפתרון מבוסס רישוםתרשים שמראה שבפתרון של CoCreateInstance נבדק קודם ה-activation context, אבל אם חסר מידע ב-manifest, זה נופל לפתרון הרגיל המבוסס על רישום, ולכן נוצרת מלכודת שבה זה עובד במקרה במחשב הפיתוח בזכות רישום מקומי.ישאיןהפתרון בודק קודם את activation contextיש מספיק מידע ב-manifest?פתרון בלי registryנופל לפתרון מבוסס רישוםמלכודת: עובד במקרה במחשב הפיתוח

איור 7: ה-fallback השקט הוא הבור הגדול ביותר במנגנון הזה.

6. מה טוב בזה

היתרונות של Reg-Free COM ברורים למדי בפועל.

6.1 קל להפיץ ב-XCOPY

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

6.2 קל יותר לצמצם התנגשות גרסאות

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

6.3 לרוב אין צורך לשנות משמעותית קוד קיים

Reg-Free COM לא משנה מהיסוד את אופן הקריאה של הקוד הקיים, אלא היא מנגנון שמשנה את אופן הפתרון. לכן, אם זה מסתדר, אפשר להטמיע את זה כמעט בלי לגעת בקוד ה-CoCreateInstance.

6.4 מחיקה וחזרה אחורה נעשים קלים יותר

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

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

איור 8: כל היתרונות צומחים מתכונה אחת - ההגבלה.

7. מקרים שמתאימים ומקרים שלא

7.1 מקרים שמתאימים

במקרים הבאים, Reg-Free COM חזק למדי.

מצב התאמה
רוצים לצרף COM DLL /‏ OCX ייעודי לאפליקציה טוב מאוד
רוצים להחזיק כמה גרסאות באותו PC טוב מאוד
רוצים להימנע מתקלת רישום של רכיב ספק חיצוני טוב
רוצים להשתמש ב-ActiveX / OCX כ-private באפליקציית שולחן עבודה קיימת טוב
רוצים להקל על ההפצה בלי לשנות משמעותית את הקריאה הקיימת טוב

בדרך כלל, זה מתאים ל-אפליקציות עסקיות בשולחן עבודה, כלי חיבור לציוד, ונכסים קיימים של VB6 /‏ MFC /‏ WinForms.

7.2 מקרים שלא מתאימים, או שכדאי לבדוק בזהירות

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

מצב הערה
רוצים לשתף COM ברמת המכונה כולה היתרון של Reg-Free חלש
ה-bitness לא תואם Reg-Free לא פותר את זה
תלות חזקה במידע רישום לא סטנדרטי או בהתקנה ייחודית קשה להפוך ל-manifest
הפצת DLL תלוי או VC++ runtime לא מסודרת זה ייתקע במקום אחר בכל מקרה
כלי תכנון או הגדרת הפניה ב-IDE מניחים registry נדרש תכנון תפעול נפרד

בפרט, הנקודה האחרונה חשובה. Reg-Free COM עוזרת ל-הפעלה בזמן ריצה, אבל לא משנה במחי יד את מה שכלי ההגדרה בזמן תכנון מניח.

זמן ריצה נעזר, זמן תכנון נשארתרשים שמראה ש-Reg-Free COM עוזרת להפעלה בזמן ריצה, אבל אם כלי או IDE להגדרת הפניה בזמן תכנון מניחים registry, זה לא משתנה כך סתם, ונדרש תכנון תפעול נפרד.הפעלה בזמן ריצהReg-Free עוזרתUI להגדרת הפניה בזמן תכנוןלפעמים מניח registryנדרש תכנון תפעול נפרד

איור 9: גם כשהמנגנון שמפעיל מסודר, המנגנון שמפתחים נשאר צריך סידור נפרד.

8. אי-הבנות נפוצות

8.1 עם Reg-Free COM, בעיית ה-bitness נעלמת

לא נעלמת. תהליך 32bit יכול לטעון רק COM DLL מסוג in-proc של 32bit, ותהליך 64bit רק DLL של 64bit. זה נשאר כמות שהיה גם עם Reg-Free.

8.2 עם Reg-Free COM, ה-registry כבר לא נבדק בכלל

גם זה לא נכון. אם חסר מידע נדרש ב-manifest, זה נופל לפתרון הרגיל המבוסס על רישום. בגלל זה, הצלחה במחשב הפיתוח לא בהכרח אומרת שהרכב ה-Reg-Free נכון.

8.3 עם Reg-Free COM, גם עניין ספריית הטיפוסים נפתר אוטומטית

זה נכון רק בחצי. אפשר לכתוב גם מידע typelib ב-manifest, אבל הגדרת הפניה ב-VBA,‏ #import ב-‏C++, יצירת הפניה בזמן תכנון בצד .NET - הטיפול במידע הטיפוס לרוב עדיין דורש תכנון נפרד.

Reg-Free COM היא בעיקר סוגיה של איך לגרום לזה להתחיל לרוץ. איך מפתחים עם טיפוסים הוא הנושא הבא.

הסדר בין נושא ההפעלה לנושא הטיפוסיםתרשים שמראה שגם אם אפשר לכתוב מידע typelib ב-manifest, הטיפול במידע הטיפוס - הגדרת הפניה ב-VBA, import ב-C++, יצירת interop ב-.NET - עדיין דורש תכנון נפרד, ו-Reg-Free COM עוסקת קודם כל בהפעלה, ופיתוח עם טיפוסים הוא הנושא הבא.מגדירים Reg-Free COMקודם כל, גורמים לזה לרוץבשלב הבא, איך מפתחים עם טיפוסיםהגדרת הפניה ב-VBA / import / יצירת interop

איור 10: היכולת לכתוב typelib והשלמת ניהול מידע הטיפוסים הן שני דברים שונים.

8.4 עם Reg-Free COM, כל ActiveX / OCX עובד ישירות

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

8.5 Reg-Free COM דומה לחלוטין בין .NET Framework ל-.NET 8

יש דמיון, אבל שרשרת הכלים שונה למדי. ההקשר של .NET Framework + RegAsm וההקשר של .NET 5+ / .NET 8 + comhost הם אותו COM, אבל התשתית שונה.

9. ההבדל בין נייטיב /‏ .NET Framework /‏ .NET 5+ /‏ .NET 8

זה קל להתבלבל בו, ולכן ננתח את זה בנפרד פעם אחת.

משפחה סידור בגסות
COM DLL /‏ OCX נייטיבי בסיסי לחשוב עליו במונחי מניפסט יישום + מניפסט רכיב
Interop COM מבוסס .NET Framework בנוסף למניפסט היישום בסגנון Win32, נדרש גם manifest מצד הרכיב ה-managed
חשיפת COM ב-.NET 5+ /‏ .NET 8 יוצרים COM host עם EnableComHosting, ומייצרים manifest עבור Reg-Free עם EnableRegFreeCom

9.1 COM מבוסס .NET Framework

ב-COM מבוסס .NET Framework, יש שני שלבים: application manifest בסגנון Win32 מצד אפליקציית ה-COM, ו-component manifest מצד הרכיב ה-managed.

כלומר, בהשוואה ל-COM נייטיבי, נוסף manifest אחד. המגבלה של השם ומזהה המשאב מסעיף 10.4 חלה באותה מידה גם על ה-component manifest כאן.

9.2 חשיפת COM ב-‏.NET 5+ /‏ .NET 8

ב-‏.NET 5+ /‏ .NET 8, הכניסה לחשיפת COM הופכת ל-*.comhost.dll. בנוסף, אם מוסיפים EnableRegFreeCom=true, מיוצא side-by-side manifest עבור Reg-Free COM.

הטיפול במידע הטיפוסים כסוגיה נפרדת כפי שתואר בסעיף 8.3, אבל ב-.NET Core / .NET 5+ הדבר משתנה עוד יותר. מכיוון שזה לא עולם שבו TLB נוצר באופן טבעי מה-assembly כמו בתקופת .NET Framework, אם נדרש שימוש עם טיפוסים, יש לבנות בנפרד את יצירת ה-TLB, שיבוצו ורישומו. השלבים המדויקים מסוכמים ב-“שימוש ב-DLL של .NET 8 מ-VBA עם טיפוסים - חשיפת COM ו-TLB ב-dscom”.

Reg-Free COM ב-.NET 5 ואילךתרשים שמראה שב-.NET 5 ואילך, EnableComHosting יוצר את comhost.dll כניסה לחשיפת COM, EnableRegFreeCom מייצא side-by-side manifest עבור Reg-Free COM, אבל יצירה ורישום של TLB דורשים בנייה נפרדת.בנייה עם EnableComHostingcomhost.dll הופך לכניסהמפעילים EnableRegFreeComמיוצא manifest עבור Reg-Freeהטיפול ב-TLB דורש בנייה נפרדת

איור 11: ב-.NET 8, שתי מאפיינים מכינים את התשתית, אבל מידע הטיפוס דורש עבודה נפרדת.

10. תמונת ההרכב המינימלי

כאן נציג תמונה מינימלית שבה MyApp.exe משתמש ב-Vendor.CameraControl.dll דרך Reg-Free COM.

10.1 תמונת מבנה הקבצים

MyApp.exe
MyApp.exe.manifest
Vendor.CameraControl.Asm.manifest
Vendor.CameraControl.dll
Vendor.Helper.dll

בדוגמה למעלה, ההנחה היא שהמניפסט של הרכיב מוצב כקובץ נפרד. אפשר גם לשבץ אותו בתוך ה-DLL, אבל אז חלים מגבלות קבועות על שם ה-assembly ומזהה המשאב. ההליך המלא מובא בסעיף 10.4.

10.2 תמונת מניפסט היישום

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    type="win32"
    name="KomuraSoft.MyApp"
    version="1.0.0.0"
    processorArchitecture="amd64" />

  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Vendor.CameraControl.Asm"
        version="1.0.0.0"
        processorArchitecture="amd64" />
    </dependentAssembly>
  </dependency>
</assembly>

10.3 תמונת מניפסט הרכיב

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    type="win32"
    name="Vendor.CameraControl.Asm"
    version="1.0.0.0"
    processorArchitecture="amd64" />

  <file name="Vendor.CameraControl.dll">
    <comClass
      clsid="{01234567-89AB-CDEF-0123-456789ABCDEF}"
      progid="Vendor.CameraControl.1"
      threadingModel="Apartment"
      tlbid="{89ABCDEF-0123-4567-89AB-CDEF01234567}" />

    <typelib
      tlbid="{89ABCDEF-0123-4567-89AB-CDEF01234567}"
      version="1.0"
      helpdir="" />
  </file>
</assembly>

מה שבאמת חשוב בדוגמה הזו, יותר מפרטי ה-XML, הוא ש-dependentAssembly בצד האפליקציה ו-assemblyIdentity בצד הרכיב תואמים. אם יש כאן פער, זו הופכת לכישלון הפעלה שלא ברור מה גרם לו רק לפי מראה השגיאה.

יש לציין שה-GUID והשמות למעלה הם דוגמה להמחשה. בפועל, יש לכתוב אותם נכון בהתאם ל-CLSID /‏ TLBID /‏ ProgID / מודל ה-threading שהרכיב חושף בפועל.

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

איור 12: יותר מפרטי ה-XML, מה שקובע חיים ומוות הוא התאמת שני התגים.

10.4 איפה מציבים ואיך משבצים את ה-manifest

זו הנקודה שהכי קל להיתקע בה בפועל בהליך. אם מציבים כקובץ נפרד או משבצים בקובץ הבינארי, השם המותר משתנה.

side-by-side מחפש private assembly בתיקיית האפליקציה בסדר הזה.

  1. תיקיית WinSxS
  2. <appdir>\<assemblyname>.DLL
  3. <appdir>\<assemblyname>.manifest
  4. <appdir>\<assemblyname>\<assemblyname>.DLL
  5. <appdir>\<assemblyname>\<assemblyname>.manifest

אם נמצא DLL עם שם זהה לשם ה-assembly, החיפוש נעצר שם. מכאן, יש רק שתי אפשרויות ישימות.

הצבה שם ה-assembly קובץ מזהה משאב לשיבוץ
קובץ נפרד שם שונה משם ה-DLL. לדוגמה: Vendor.CameraControl.Asm Vendor.CameraControl.Asm.manifest ליד ה-DLL לא משובץ
שיבוץ ב-DLL יכול להיות זהה לשם ה-DLL. לדוגמה: Vendor.CameraControl רק Vendor.CameraControl.dll ‏1

כלומר, אם משתמשים בשם Vendor.CameraControl.Asm כמו בדוגמאות 10.2 ו-10.3, זה תפעול לקובץ נפרד. אם עוברים לשיבוץ, משנים את ה-name של assemblyIdentity ל-Vendor.CameraControl, ומיישרים גם את dependentAssembly לאותו שם.

המנגנון שבו החיפוש נעצרתרשים שמראה ש-side-by-side מחפש private assembly בסדר מ-WinSxS ואילך בתיקיית האפליקציה, ואם נמצא DLL עם שם זהה לשם ה-assembly, החיפוש נעצר שם, ולכן בתפעול של קובץ נפרד צריך שהשם יהיה שונה משם ה-DLL.נמצאלא נמצאמתחילים חיפוש private assemblyמחפשים DLL עם שם זהה ל-assemblyהחיפוש נעצר שםמחפשים קובץ manifest עם אותו שםבתפעול של קובץ נפרד, מבדילים את השם

איור 13: מגבלת מתן השם נובעת מהעובדה שהחיפוש נעצר באמצע.

יש עוד מגבלה שקל לטעות בה. מניפסט הרכיב לא ניתן להכניס כמשאב ב-EXE. מה שניתן להכניס ל-EXE הוא מניפסט היישום.

לשיבוץ משתמשים ב-mt.exe (כלי המניפסטים) מ-Windows SDK. הריצו אותו מ-Developer Command Prompt של Visual Studio.

rem 1. לפני השיבוץ, קודם עוברים בדיקת syntax
mt.exe -manifest MyApp.exe.manifest -validate_manifest
mt.exe -manifest Vendor.CameraControl.manifest -validate_manifest

rem 2. משבצים את מניפסט היישום לתוך ה-EXE
mt.exe -manifest MyApp.exe.manifest -outputresource:MyApp.exe;#1

rem 3. משבצים את מניפסט הרכיב לתוך ה-DLL (מזהה משאב 1)
mt.exe -manifest Vendor.CameraControl.manifest -outputresource:Vendor.CameraControl.dll;#1

rem 4. מוציאים ובודקים אם השיבוץ הצליח
mt.exe -inputresource:Vendor.CameraControl.dll;#1 -out:extracted.manifest

אם משווים את extracted.manifest שהוצא בפקודה הרביעית ל-XML המקורי, אפשר לזהות “חשבתי ששיבצתי, אבל זה לא נכנס”.

הליך השיבוץ עם mt.exeתרשים שמראה שב-mt.exe, קודם עוברים בדיקת syntax עם validate_manifest, משבצים את מניפסט היישום ל-EXE, את מניפסט הרכיב ל-DLL עם מזהה משאב 1, ולבסוף מוציאים ומשווים ל-XML המקורי.עוברים בדיקת syntaxמשבצים את צד האפליקציה ל-EXEמשבצים את צד הרכיב ל-DLL (מזהה 1)מוציאים ומשווים ל-XML המקורי

איור 14: השיבוץ מתבצע כהליך אחד שכולל גם את הבדיקה.

יש שתי הערות ל-mt.exe.

  • הקבצים שה-manifest מפנה אליהם חייבים להיות באותה תיקייה כמו ה-manifest. אם כתוב <file name="Vendor.CameraControl.dll">, יש להציב את ה-DLL הזה ליד ה-manifest לפני ההרצה. אם פלט הבנייה ומיקום ניהול ה-manifest מופרדים, זה ייתקע כאן
  • אם משמיטים את מזהה המשאב ב--outputresource, נעשה שימוש ב-CREATEPROCESS_MANIFEST_RESOURCE (‏= 1). כדי לא להסתמך על 1 בלי כוונה, עדיף לציין ;#1 במפורש - זה קריא יותר

אם רוצים רק להחליף שיבוץ קיים, אפשר להשתמש ב--updateresource:<קובץ>;#1. זה שקול לזה שמעבירים את אותם ארגומנטים ל--inputresource ול--outputresource.

10.5 הליך בדיקה כולל סביבה נקייה

ל-Reg-Free COM כמעט אין משמעות ל”עבד במחשב הפיתוח”. תמיד נשארת האפשרות שזה רק בזכות רישום מקומי ב-registry. בודקים בסדר הזה.

  1. בונים, ומרכזים את כל חבילת ההפצה בתיקייה אחת. ‏EXE, manifest,‏ COM DLL,‏ DLL תלוי, VC++ runtime, ואפילו DLL של proxy / stub
  2. מכינים סביבת בדיקה. אידיאלי שזו סביבה שה-COM הזה מעולם לא נרשם בה. Windows Sandbox מאפשר לנסות בכל פעם ממצב נקי לגמרי
  3. בסביבה הזו, קודם מוודאים שה-COM הרלוונטי לא רשום. אם הפקודה למטה מחזירה שגיאה שהמפתח לא נמצא, זה סימן שלא נרשם
  4. מעתיקים את התיקייה ומפעילים ישירות. בלי להריץ מתקין ובלי regsvr32
  5. מוודאים שגם יצירת אובייקט COM עוברת. רק ההפעלה לא בודקת רכיבים שנוצרים באיחור. מפעילים בפועל את המסך או הפונקציה שבה CoCreateInstance מתבצע
  6. אם נכשל, אוספים לוג לפי ההליך בסעיף 11.2

הפקודה בשלב 3 היא כך.

reg query "HKCR\CLSID\{01234567-89AB-CDEF-0123-456789ABCDEF}" /reg:64
reg query "HKCR\CLSID\{01234567-89AB-CDEF-0123-456789ABCDEF}" /reg:32
reg query "HKCR\Vendor.CameraControl.1"

תצוגת ה-registry של 32bit ו-64bit נפרדות, ולכן חובה לבדוק את הצד שתואם ל-bitness של האפליקציה. אם בודקים גם /reg:64 וגם /reg:32, זה בטוח יותר.

כשרוצים לנסות במחשב הפיתוח, מבטלים זמנית את הרישום עם regsvr32 /u Vendor.CameraControl.dll ואז בודקים. עם זאת, בסביבה שבה מוצר אחר משתמש באותו COM, זה משפיע גם עליו, ולכן בטוח יותר להכין סביבה נקייה.

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

איור 15: שילוב בדיקת אי-הרישום מוציא מהבדיקה את מקרה “עובד במקרה”.

11. מוקשים נפוצים

11.1 עובד במחשב הפיתוח, לא עובד ביעד ההפצה

מה שכדאי לחשוד בו ראשית הוא הדפוס שבו בעצם רישום ה-registry עזר בסתר. בטוח יותר לבצע את בדיקת Reg-Free COM בסביבה נקייה אם אפשר.

11.2 ההפעלה נכשלת עם “side-by-side configuration is incorrect”

הבעיה הזו קורית בגלל אי-התאמה ב-manifest, חוסר ב-DLL תלוי, חוסר ב-VC++ runtime, או פער בארכיטקטורה. מכיוון שהודעת השגיאה עצמה לא ידידותית, מקובל לחקור עם ‏Event Log ו-sxstrace.

ה-Event Log נפתח דרך מציג האירועים > יומני Windows > Application, ומחפשים שגיאה שהמקור שלה SideBySide. שם רואים איזה assembly נכשל בפתרון.

sxstrace נאסף תוך שחזור הכישלון. עדיף לפתוח את שורת הפקודה כמנהל.

rem 1. מתחילים trace. חלון זה נשאר פתוח
sxstrace trace -logfile:sxstrace.etl

rem 2. בחלון אחר, מפעילים את האפליקציה ומשחזרים את הכישלון

rem 3. עוצרים את ה-trace. לוחצים Enter בחלון 1, או מריצים מחלון אחר
sxstrace stoptrace

rem 4. ממירים את ה-.etl הגולמי לפורמט קריא לבני אדם
sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt

אם לא רוצים שתופיע הודעת עצירה, מוסיפים -nostop לשלב 1. אם הפלט ארוך מדי, אפשר להוסיף -filter:MyApp.exe לשלב 4 כדי לצמצם רק לאפליקציה הרלוונטית.

ב-sxstrace.txt שהתקבל אחרי ההמרה, מפורטים לפי הסדר אילו manifest חיפש ואיפה לא היה תואם. גם כשטועים בכללי השם מסעיף 10.4, אפשר לזהות זאת דרך “שם הקובץ שחיפש”.

איך חוקרים שגיאת side-by-sideתרשים שמראה שכשההפעלה נכשלת עם side-by-side configuration is incorrect, בודקים ב-Event Viewer שגיאה עם מקור SideBySide, מתחילים trace עם sxstrace, משחזרים את הכישלון, ואחרי העצירה ממירים עם parse וקוראים היכן הייתה אי-התאמה.בודקים SideBySide ב-Event Viewerמתחילים trace עם sxstraceמפעילים את האפליקציה ומשחזרים את הכישלוןעוצרים וממירים עם parseקוראים היכן היה חיפוש ואי-התאמה

איור 16: לא מתעכבים על הודעת השגיאה החיצונית - עוקבים אחרי דרך הפתרון עם לוג ו-trace.

11.3 ההתאמה בין component manifest ל-application manifest נשברת

  • ה-name שונה
  • ה-version שונה
  • ה-processorArchitecture שונה
  • ה-manifest שחשבו שהעתיקו הוא בעצם ישן

הפערים האלה נראים קטנים למראה עין, אבל בזמן ההפעלה משפיעים משמעותית.

11.4 שוכחים למקם DLL תלוי

אם מסתפקים בבדיקת Vendor.CameraControl.dll בלבד ומרגישים בטוחים, חסרים Vendor.Helper.dll שנקרא מתוכו, VC++ runtime, ו-DLL של proxy / stub. Reg-Free COM מצמצם את בעיית רישום ה-COM, אבל לא מוחק את בעיית פתרון התלות הנייטיבית.

11.5 דוחים את ניהול ספריית הטיפוסים והגדרת ההפניה

גם אם ה-runtime activation עובר, אם יש דרישות כמו:

  • רוצים קישור מוקדם מ-VBA
  • רוצים #import ב-‏C++
  • רוצים יצירת interop בזמן תכנון בצד .NET

נדרשת דרך לחלק את מידע הטיפוס. Reg-Free COM לא מסדרת את זה אוטומטית לגמרי, ולכן חשוב לחשוב בנפרד על runtime ו-design-time.

12. סיכום

אם אומרים את Reg-Free COM במשפט אחד, זה מנגנון שמעביר את מידע רישום ה-COM מרמת המכונה כולה לרמת אפליקציה בודדת.

בזכות זה, יש יתרונות כמו:

  • קל להגביל COM DLL /‏ OCX לרמה מקומית לאפליקציה
  • קל יותר לצמצם התנגשות גרסאות
  • קל יותר לפשט הפצה וחזרה אחורה

מצד שני, אלה עדיין חשובים:

  • 32bit / 64bit
  • DLL תלוי
  • TLB / הגדרת הפניה
  • תלות ברישום לא סטנדרטי
  • בדיקה בסביבה נקייה

לכן, הגישה הבסיסית להטמעת Reg-Free COM היא כך.

  1. מקבלים את זה כסוגיה של activation
  2. מפרידים בין נקודות המבט של runtime ו-design-time
  3. בודקים בסביבה נקייה
  4. מתאימים מראש bitness ו-DLL-ים תלויים

אם עוקבים אחר הסדר הזה, קל בהרבה להימנע מתקלות.

הגישה הבסיסית בזמן ההטמעהתרשים שמראה שבהטמעת Reg-Free COM, מקבלים את זה כסוגיה של activation, מפרידים בין נקודות המבט של runtime ו-design-time, בודקים בסביבה נקייה, ומתאימים מראש bitness ו-DLL-ים תלויים - אם עוקבים בסדר הזה, קל יותר להימנע מתקלות.מקבלים כסוגיית activationמפרידים runtime ו-design-timeבודקים בסביבה נקייהמתאימים מראש bitness ו-DLL תלוי

איור 17: אם שומרים על ארבע הגישות בסדר הזה, אפשר לצמצם מאוד תקלות בהטמעה.

13. מאמרים קשורים

14. מקורות

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

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

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

שאלות נפוצות

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

מה זה Reg-Free COM?
מנגנון שבו מידע הרישום של COM מוחזק ב-manifest ולא ב-registry, קיצור של Registration-Free COM. בזמן ריצה, בפתרון של CoCreateInstance או CLSIDFromProgID נבדק קודם ה-activation context, ומתוכו מפוענח ה-DLL לפי מידע ה-manifest שכתוב שם. כך אפשר להחזיק COM DLL/OCX כ-private לכל אפליקציה, ויש יתרונות כמו קלות הפצה ב-XCOPY, הימנעות מהתנגשות גרסאות, והתקנה מחדש שפחות נשברת.
אם עוברים ל-Reg-Free COM, גם בעיית 32bit/64bit נפתרת?
לא נפתרת. תהליך 32bit יכול לטעון רק COM DLL מסוג in-proc של 32bit, ותהליך 64bit רק DLL של 64bit. גם ב-Reg-Free זה נשאר כמות שהיה. בנוסף, יש לחשוב בנפרד על הפצת DLL-ים תלויים ו-VC++ runtime, ספריית טיפוסים, הגדרת הפניה בזמן תכנון, ותלות במידע רישום לא סטנדרטי. מה ש-Reg-Free COM מוחק הוא בעיקר הטרחה שנגררת מרישום גלובלי.
למה הרכב Reg-Free COM עובד במחשב הפיתוח אבל לא ביעד ההפצה?
מה שכדאי לחשוד בו ראשית הוא דפוס שבו בעצם הרישום ב-registry עזר בסתר. אם חסר מידע ל-manifest, ה-COM runtime נופל לפתרון המבוסס על רישום, ולכן במחשב הפיתוח זה עלול לעבוד במקרה בזכות רישום מקומי. לכן, בדיקת Reg-Free COM בטוחה יותר בסביבה נקייה. אם ההפעלה נכשלת עם 'side-by-side configuration is incorrect', הסיבה היא בדרך כלל אי-התאמה ב-manifest או חוסר ב-DLL תלוי, ו-Event Log יחד עם sxstrace הם הדרך המקובלת לחקור.
אפשר להפוך גם רכיב COM שנבנה עם .NET 8 ל-Reg-Free COM?
כן, אפשר. ב-.NET 5+ / .NET 8, EnableComHosting יוצר את *.comhost.dll שמשמש כניסה לחשיפת COM, ואם מוסיפים גם EnableRegFreeCom=true, מיוצא side-by-side manifest עבור Reg-Free COM. עם זאת, Reg-Free COM ואסטרטגיית TLB הן שתי סוגיות נפרדות. ב-.NET Core / .NET 5+ ה-TLB לא נוצר באופן טבעי מה-assembly כפי שהיה ב-.NET Framework, ולכן אם נדרש שימוש עם טיפוסים כמו קישור מוקדם ב-VBA, בטוח יותר לסדר בנפרד את יצירת ה-TLB, שיבוצו ורישומו.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג