המאמר הזה התחיל משיחת חולין - ‘עד כמה מותר לקרוא ל-Windows בינארי יחיד (single binary)?’ הפוסט שהיה הטריגר הוא זה שלמטה. גם אם מדלגים עליו, הכתיבה שאחריו נועדה להיקרא באופן עצמאי.
ב-Windows, הרצון ‘אם אפשר, להפיץ בקובץ אחד’ הוא בקשה די רגילה. בכלים פנים-ארגוניים, כלי חיבור לציוד, מסופי ניטור, סביבות offline, ובשטח שרוצה להימנע ממתקין (installer) ככל האפשר, ל-איחוד לבינארי יחיד יש קסם רב.
עם זאת, אם לא מפרידים את הנושא הזה מראש, בדרך כלל השיחה מפסיקה להתיישר באמצע. הסיבה היא שברצון ‘(רוצים) בינארי יחיד’ כפי שאומרים אותו ב-Windows, נוטים להתערבב בפועל 4 נושאים נפרדים.
- רוצים לרכז את פריט ההפצה לאחד
- רוצים לבטל את הצורך בהתקנה מראש של ה-runtime של .NET או Visual C++
- רוצים שיפעל רק בהנחת קובץ, בלי מתקין ובלי הרשאות מנהל
- לא רוצים תלות בהבדלים בין גרסאות Windows של היעד
ארבעת אלה אינם זהים. מבחינה מעשית, החשיבה הבאה הכי פחות סוטה:
אפשר לרכז את פריט ההפצה ל-EXE אחד, במידה רבה. אבל, אי אפשר לאפס את התלות ב-Windows היעד.
המאמר הזה מסדר את קו הגבול הזה, מכוון לעבודה מעשית עם יישומי Windows.
flowchart TB
accTitle: איחוד ל-EXE אחד אפשרי, אבל התלות ב-OS לא נעלמת
accDescr: תרשים המראה שבבקשה לאחד לבינארי יחיד נוטים להתערבב נושא ההפצה ונושא התלות, ושלמרות שאפשר במידה רבה לרכז את פריט ההפצה ל-EXE אחד, אי אפשר לאפס את התלות ב-Windows היעד.
a0["'רוצים בינארי יחיד'"] --> a1["נושא ההפצה: רוצים פריט אחד, רוצים שרק יונח"]
a0 --> a2["נושא התלות: בלי runtime, בלי תלות ב-OS"]
a1 --> a3["איחוד ל-EXE אחד אפשרי במידה רבה"]
a2 --> a4["אי אפשר לאפס את התלות ב-OS"]
איור 1: ב’רוצים להפיץ בקובץ אחד’ מתערבבים דבר שאפשר ודבר שאי אפשר.
1. קודם כל, המסקנה
אם מסכמים קודם רק את המסקנה, זה כך:
- ליישום שולחני רגיל, אפשר לאחד לבינארי יחיד עד רמה גבוהה למדי
- עם זאת, היכולת לרכז ל-EXE אחד ו-היעדר תלות ב-Windows היעד הם שני דברים שונים
- הרחבת מעטפת, שירות Windows, מנהל התקן, WebView2, וחלק מ-WinUI 3 - עבורם, לרוב מה נרשם למערכת ההפעלה ומה מונח כתנאי מוקדם חשוב יותר ממספר הקבצים
- החשוב ביותר בעבודה מעשית הוא להחליט בנפרד אם רוצים איחוד לבינארי יחיד, רוצים לבטל את הצורך במתקין, או רוצים להקטין את התלות ב-OS
במילים אחרות, קו הגבול ב-Windows הוא כך:
- ריכוז פריט ההפצה לאחד: אפשרי במידה רבה
- נשיאת runtime נוסף: אפשרי במידה רבה
- התאמה להפצת xcopy: תלוי בסוג היישום
- ביטול התלות בצד Windows היעד: בלתי אפשרי
flowchart TB
accTitle: קו הגבול ב-Windows
accDescr: תרשים המראה שריכוז פריט ההפצה לאחד ונשיאת runtime נוסף אפשריים במידה רבה, שההתאמה להפצת xcopy תלויה בסוג היישום, ושביטול התלות בצד Windows בלתי אפשרי.
b1["ריכוז פריט ההפצה לאחד"] --> b2["אפשרי במידה רבה"]
b3["צירוף runtime נוסף"] --> b2
b4["התאמה להפצת xcopy"] --> b5["תלוי בסוג היישום"]
b6["ביטול התלות בצד ה-OS"] --> b7["בלתי אפשרי"]
איור 2: קו הגבול בין מה שאפשר, מה שתלוי בסוג, ומה שאי אפשר.
1.1 מונחים שבהם משתמש המאמר הזה
לפני הכול, מסדרים כאן רק את המילים שיחזרו הרבה בהמשך.
| מונח | קריאה/הרחבה | המשמעות במאמר הזה |
|---|---|---|
| UCRT | Universal C Runtime | חלק ספריית ה-C הסטנדרטית שנוצר כשה-C runtime פוצל ב-Visual Studio 2015. מ-Windows 10 ואילך הוא מגיע כחלק ממרכיבי מערכת ההפעלה |
| חבילת ההפצה מחדש של Visual C++ | Visual C++ Redistributable | מתקין (installer) להתקנת DLL-ים של ה-runtime של Visual C++ מלבד UCRT, במחשב היעד |
| framework-dependent | הפצה שתלויה ב-.NET שבסביבת היעד | צורת הפצה שמניחה שה-runtime של .NET מותקן במחשב היעד |
| self-contained | צירוף ה-runtime של .NET | צורה שבה האפליקציה נושאת ומפיצה את מלוא ה-runtime של .NET |
| single-file | פרסום כקובץ יחיד | אפשרות פרסום של .NET שמרכזת את ההפצה ל-EXE אחד |
| Native AOT | Native Ahead-Of-Time | צורת הפצה שמקמפלת מראש את ה-IL לקוד נייטיבי בזמן הפרסום. לא משתמשת ב-JIT בזמן ריצה |
| הפצת app-local | הפצה סמוכה לאפליקציה | דרך הפצה שבה מניחים DLL באותה תיקייה כמו ה-EXE, ומשתמשים בו רק מתוך האפליקציה הזו |
| הפצת xcopy | הפצה שכוללת רק העתקה | דרך הפצה שפועלת בהנחת התיקייה כולה, בלי מתקין ובלי הרשאות מנהל |
| SCM | Service Control Manager, מנהל בקרת השירותים | מנגנון של מערכת ההפעלה שמנהל רישום, הפעלה ועצירה של שירותי Windows |
| UAC | User Account Control, בקרת חשבון משתמש | מנגנון אבטחה של Windows ששולט בהעלאת הרשאות למנהל |
| הרחבת מעטפת (Shell) | shell extension | רכיב COM שנטען ופועל בתוך תהליכים כמו Explorer |
| Evergreen | שיטת עדכון מתמיד | מצב הפצה שמפקיד את WebView2 Runtime בעותק משותף אחד שמתעדכן אוטומטית |
| Fixed Version | שיטת גרסה קבועה | מצב הפצה שמצרף גרסה מסוימת של WebView2 Runtime לאפליקציה עצמה |
| arch | architecture, ארכיטקטורת מעבד | x86 / x64 / Arm64 |
מפת הידע של המאמר
עבור יישום Windows, ריכוז ההפצה ל-EXE אחד אפשרי במידה רבה כשלעצמו - אפשר להשתמש ב-self-contained, single-file ו-Native AOT של .NET, ובקישור סטטי (/MT) של C/C++ - אך נשארת תלות בגרסת מערכת ההפעלה, בארכיטקטורה, ב-DLL-ים של המערכת ובמודל האבטחה, ואי אפשר לאפס את התלות ב-Windows היעד. self-contained ו-framework-dependent הן אפשרויות שמוציאות זו את זו, ו-Native AOT פועל בפועל כ-single-file אך במחיר של אי-יכולת להשתמש למשל ב-built-in COM. ב-WebView2 יש לבחור בין Evergreen ל-Fixed Version לאופן אספקת ה-runtime, וב-WinUI 3 האפשרות להשתמש ב-PublishSingleFile משתנה בהתאם ל-packaged(MSIX)לעומת unpackaged. מכיוון שהרחבת מעטפת ומנהלי התקן דורשים בעיקר תכנון של רישום וחתימה, לרוב הפצת app-local קלה יותר לתחזוקה מאשר לדחוס בכוח ל-EXE אחד.
flowchart LR
accTitle: מפת הידע של איחוד יישום Windows לבינארי יחיד
accDescr: תרשים המראה שריכוז ההפצה לפריט אחד וביטול התלות במערכת ההפעלה הם שני צירים נפרדים, את אמצעי האיחוד לקובץ יחיד ב-.NET וב-C/C++ בהתאמה, ואיך אינטגרציה מארחת כמו WebView2 או WinUI 3 מגבילה את שיטת ההפצה עצמה.
single_binary_packaging["איחוד לבינארי יחיד (הפצת קובץ אחד)"]
windows_os_dependency["תלות בגרסת Windows היעד"]
self_contained_deployment["פרסום self-contained"]
framework_dependent_deployment["פרסום framework-dependent (.NET)"]
native_aot["Native AOT"]
builtin_com_interop["built-in COM interop"]
single_file_publish["פרסום single-file (.NET)"]
static_linking_crt["קישור סטטי של ה-CRT (/MT)"]
dynamic_linking_crt["קישור דינמי של ה-CRT (/MD)"]
vc_redistributable["חבילת ההפצה מחדש של Visual C++"]
ucrt["UCRT(Universal C Runtime)"]
msix_packaging["אריזת MSIX (packaged)"]
winui3["WinUI 3 / Windows App SDK"]
webview2["Microsoft Edge WebView2"]
webview2_runtime["WebView2 Runtime"]
webview2_evergreen["מצב הפצה Evergreen (WebView2)"]
webview2_fixed_version["זמן ריצה Fixed Version"]
shell_extension["הרחבת מעטפת (הרחבת סייר הקבצים)"]
com["COM (Component Object Model)"]
driver_signing["חתימת מנהל התקן"]
driver_package["חבילת מנהל התקן"]
app_local_deployment["הפצה app-local (לצד קובץ ההרצה)"]
single_binary_packaging -->|"מחייב"| windows_os_dependency
self_contained_deployment -->|"אינו מתיישב עם"| framework_dependent_deployment
native_aot -->|"אינו מתיישב עם"| builtin_com_interop
single_file_publish -.->|"מחייב"| windows_os_dependency
native_aot -.->|"מחייב"| windows_os_dependency
static_linking_crt -->|"אינו מתיישב עם"| dynamic_linking_crt
dynamic_linking_crt -->|"מחייב"| vc_redistributable
static_linking_crt -->|"משתמש ב"| ucrt
dynamic_linking_crt -->|"משתמש ב"| ucrt
msix_packaging -.->|"אינו מתיישב עם"| single_file_publish
winui3 -.->|"משתמש ב"| msix_packaging
webview2 -->|"מחייב"| webview2_runtime
webview2_runtime -->|"מוגדר באמצעות"| webview2_evergreen
webview2_runtime -->|"מוגדר באמצעות"| webview2_fixed_version
webview2_evergreen -->|"אינו מתיישב עם"| webview2_fixed_version
shell_extension -->|"משתמש ב"| com
driver_signing -->|"מחייב"| driver_package
driver_package -->|"שימוש לא מומלץ ל"| single_binary_packaging
app_local_deployment -->|"מענה מומלץ ל"| single_binary_packaging
self_contained_deployment -.->|"מחייב"| windows_os_dependency
single_binary_packaging -.->|"משתמש ב"| static_linking_crt
webview2 -.->|"מחייב"| windows_os_dependency
בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 22, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle
2. חושבים על ‘בינארי יחיד’ בארבע רמות
אם מציירים את הרמות קודם, זה נראה כך. הקושי עולה מלמטה למעלה, והרמה העליונה ביותר לא ניתנת להשגה ב-Windows.
flowchart BT
accTitle: ארבע הרמות של בינארי יחיד
accDescr: תרשים המראה את ארבע הרמות של בינארי יחיד, מלמטה למעלה - פריט הפצה אחד, ביטול התקנה מראש של runtime, ביטול התקנה או רישום, והיעדר תלות ב-Windows היעד שלא ניתן להשיג.
A["רמה A - פריט הפצה אחד - אפשרי במידה רבה"]
B["רמה B - ביטול התקנה מראש של runtime השפה - אפשרי במידה רבה"]
C["רמה C - ביטול התקנה או רישום - תלוי בסוג היישום"]
D["רמה D - היעדר תלות ב-Windows היעד - לא ניתן להשיג ב-Windows"]
A --> B
B --> C
C --> D
איור 3: ארבע הרמות של בינארי יחיד. הקושי עולה מלמטה למעלה, ואי אפשר להגיע לרמה D.
השיחה על ‘רוצים בינארי יחיד’ משתנה לגמרי בעבודה הנדרשת, בהתאם לאיזו משארבע הרמות היא מתכוונת.
2.1 רמה A: פריט הפצה אחד
זו הרמה השטחית ביותר.
- אפשר לשלוח פריט אחד באימייל
- מספיק להניח פריט אחד ב-USB
- להניח רק את
app.exeביעד הפריסה
זהו סיפור של יחידת ההפצה החזותית. בפועל, גם אם יש פריסה זמנית בעת ההפעלה, וגם אם יש תלות ב-DLL-ים בצד ה-OS, אפשר לעמוד בתנאי הזה בלבד.
2.2 רמה B: אין צורך בהתקנה מראש של runtime השפה
הבא הוא מצב שבו האפליקציה פועלת גם בלי להתקין מראש במחשב היעד את ה-runtime של .NET או את חבילת ההפצה מחדש של VC++.
- קישור סטטי ב-C/C++
- self-contained ב-.NET
- single-file ב-.NET
- Native AOT ב-.NET
ברמה הזו, התחושה של ‘אפשר לקחת לבד’ מתחזקת מאוד.
2.3 רמה C: אין צורך בהתקנה או רישום
מכאן זה נעשה קשה בפתאומיות.
אם זה EXE רגיל, לפעמים די להניח אותו כדי שיפעל. אבל הדברים הבאים שונים.
- הרחבת מעטפת (Shell)
- שירות Windows
- תבנית URL מותאמת אישית או שיוך קבצים
- מנהל התקן
- רכיבים שנטענים לתהליכים אחרים, כמו Explorer או Office
בתחום הזה לא מספיק להניח קובץ. יש צורך ברישום בצד ה-OS ובחיבור אל הצד המארח (host).
flowchart TB
accTitle: תחומים שבהם לא מספיק להניח קובץ
accDescr: תרשים המראה שהרחבת מעטפת, שירות Windows, שיוך קבצים, מנהל התקן ורכיבים שנטענים לתהליכים אחרים אינם מסתפקים בהנחת קובץ, ודורשים רישום בצד ה-OS וחיבור אל הצד המארח.
c1["הרחבת מעטפת, שירות"] --> c4["לא מספיק להניח קובץ"]
c2["שיוך, מנהל התקן"] --> c4
c3["דברים שנטענים לתהליכים אחרים"] --> c4
c4 --> c5["דרוש רישום בצד ה-OS וחיבור אל המארח"]
איור 4: הקושי הפתאומי ברמה C נובע מכך שהתחום הזה דורש רישום ל-OS.
2.4 רמה D: היעדר תלות ב-Windows היעד
זה בלתי אפשרי ב-Windows.
הסיבה היא שיישום Windows פועל בסופו של דבר מעל ה-API, הטוען (loader), מודל האבטחה ומחסנית ההתקנים של Windows. איחוד לבינארי יחיד אפשרי רק עד גבול תחום האחריות של האפליקציה. הוא לא לוקח איתו את מערכת ההפעלה עצמה.
3. תחומים שקל יחסית לרכז ל-EXE אחד
גם ב-Windows יש יישומים שקל יחסית לרכז ל-EXE אחד.
- כלי שולחני שמופעל באופן עצמאי
- יישום עסקי שבו ה-EXE עצמו מכיל את ה-UI ואת העיבוד
- כלים כמו תקשורת, טיפול בקבצים, איסוף לוגים, ניטור ובקרת ציוד
- דברים שלא זקוקים לאינטגרציה מארחת עם Explorer או Office
- UI שלא מניח runtime של אינטרנט
בסוג הזה, יש הרבה דברים שקל לכלול בגוף האפליקציה.
- קוד עצמי
- משאבים
- מניפסט
- הגדרות ברירת מחדל
- נתוני תבנית
- חלק מספריות צד שלישי
- גוף ה-runtime של השפה
בנוסף, גם בלי להטמיע DLL לגמרי בתוך ה-EXE, הפצת app-local, שבה מניחים DLL לצד ה-EXE, היא אפשרות חזקה ורגילה ב-Windows. בעבודה מעשית,
app.exeאחד- או
app.exe+ כמה DLL-ים סמוכים - אבל בלי צורך במתקין, בלי הרשאות מנהל, ואפשר להפיץ ב-xcopy
לא נדיר שהצורה הזו קלה יותר לתחזוקה מאשר לדחוס בכוח ל-EXE אחד.
flowchart TB
accTitle: הפצת app-local כפתרון ביניים
accDescr: תרשים המראה שגם בלי להטמיע DLL לגמרי ב-EXE, אם מניחים DLL לצד ה-EXE ואפשר להפיץ ב-xcopy בלי מתקין ובלי הרשאות מנהל, לא נדיר שזה קל יותר לתחזוקה מאשר לדחוס בכוח ל-EXE אחד.
d1["app.exe + כמה DLL-ים סמוכים"] --> d2["בלי מתקין, בלי הרשאות מנהל"]
d2 --> d3["אפשר להפיץ ב-xcopy"]
d3 -.-> d4["לפעמים קל יותר לתחזוקה מדחיסה בכוח ל-EXE אחד"]
איור 5: גם בלי להתעקש על EXE אחד, אפשר לרוב להשיג את המטרה בעזרת הפצת app-local.
4. תלות ב-Windows שלא נעלמת גם עם EXE אחד
אם חושבים ש’EXE אחד אומר שאין תלות ב-Windows היעד’, כאן קורית התאונה. בפועל, גם אם מרכזים ל-EXE אחד, נשארת תלות.
4.1 תלות בגרסת מערכת ההפעלה
לכל API של Windows יש מערכת הפעלה מינימלית נתמכת. יש גם הבדל בין x64 ל-Arm64. כלומר, גם עם EXE יחיד,
- האם זה פועל עד Windows 10
- האם זה מניח Windows 11
- האם צריך שזה יפעל גם ב-Windows Server
- באיזה מבין x86/x64/Arm64 מכוונים
יש לקבע מראש.
4.2 תלות ב-DLL-ים של המערכת
גם אם התכוונו לרכז ל-EXE אחד, בזמן ריצה משתמשים כמובן ברכיבים שמספקת מערכת ההפעלה.
kernel32.dlluser32.dlladvapi32.dll- תשתית COM
- תשתית בקרת שירותים
אלה בתחום האחריות של Windows.
4.3 תלות במודל האבטחה
- UAC
- ACL של קבצים
- מנהל בקרת השירותים
- Registry
- מדיניות חתימת מנהלי התקן
דברים כאלה, האפליקציה לא יכולה לשאת לבדה.
flowchart TB
accTitle: תלות שנשארת גם עם EXE אחד
accDescr: תרשים המראה שגם עם EXE אחד, נשארת תלות במערכת ההפעלה המינימלית הנתמכת ובארכיטקטורה של ה-API, ב-DLL-ים של המערכת ותשתית COM כמו kernel32.dll, ובמודל האבטחה כמו UAC, ACL ומדיניות חתימת מנהלי התקן.
e0["אפליקציית EXE אחד"] --> e1["גרסת OS ו-arch"]
e0 --> e2["DLL-ים של המערכת ותשתית COM"]
e0 --> e3["מודל האבטחה"]
e1 --> e4["אף אחד מהם לא ניתן לשאת בצד האפליקציה"]
e2 --> e4
e3 --> e4
איור 6: גם כשמרכזים את ההפצה לפריט אחד, נשארת תלות בתחום האחריות של Windows.
4.4 תלות במארח (host) וב-runtime
אם התכנון אינו EXE שמופעל באופן עצמאי אלא נטען על גבי מארח (host) כלשהו, התלות גדלה בבת אחת.
- משתמשים ב-WebView2: דרוש WebView2 Runtime
- משתמשים ב-WinUI 3/Windows App SDK: דרוש סידור של מצב ההפצה
- יוצרים הרחבת מעטפת: דרוש רישום בצד Explorer
כלומר, לרוב הבחירה ב-UI ובאינטגרציה הופכת ישירות לקושי ההפצה.
flowchart TB
accTitle: בחירת UI ואינטגרציה הופכת לקושי הפצה
accDescr: תרשים המראה שהשימוש ב-WebView2 דורש WebView2 Runtime, השימוש ב-WinUI 3 דורש סידור מצב הפצה, ויצירת הרחבת מעטפת דורשת רישום בצד Explorer - כך שבחירת המארח וה-runtime הופכת ישירות לקושי ההפצה.
f1["משתמשים ב-WebView2"] --> f2["דרוש Runtime"]
f3["משתמשים ב-WinUI 3"] --> f4["דרוש סידור מצב הפצה"]
f5["יוצרים הרחבת מעטפת"] --> f6["דרוש רישום ב-Explorer"]
איור 7: ככל שמתרחקים מ-EXE שמופעל באופן עצמאי, התלות גדלה בבת אחת.
5. פתרונות ביניים מעשיים לפי טכנולוגיה
5.1 C/C++ נייטיבי
C/C++ נייטיבי נמצא בצד עם חופש גבוה לאיחוד לבינארי יחיד. יש מרווח לבחור בקישור סטטי, וב-EXE שמופעל באופן עצמאי קל למדי לרכז.
עם זאת, יותר מלדחוף הכול לקובץ אחד,
- מה לעשות עם UCRT או ה-runtime של VC++
- האם להניח DLL-ים של צד שלישי ב-app-local
- עד כמה לצמצם את ה-CPU/OS היעד
אלה חשובים יותר בעבודה מעשית.
נקודת הכניסה: ההבדל בין /MT ל-/MD
ב-MSVC, מה שמכריע בפועל אם ‘לשאת runtime או לא’ הוא אפשרות המהדר (compiler) הזו.
| אפשרות | מה מקושר | מה נדרש במחשב היעד |
|---|---|---|
/MD |
ספריות ייבוא ל-DLL בשם ucrt.lib ו-vcruntime.lib. בזמן ריצה נעשה שימוש ב-ucrtbase.dll וב-vcruntime<גרסה>.dll |
UCRT וה-runtime של VC++ |
/MT |
קישור סטטי של libucrt.lib, libvcruntime.lib, libcmt.lib |
בלי runtime נוסף |
הדרך המינימלית לנסות היא רק זו, ב-Developer Command Prompt.
cl /std:c++17 /EHsc /O2 /MT app.cpp /Fe:app.exe
יש כאן שלוש נקודות שכדאי לשים לב אליהן.
- UCRT הפך ל-מרכיב של Windows כשה-C runtime פוצל ב-Visual Studio 2015, ומ-Windows 10 ואילך הוא מגיע כחלק ממערכת ההפעלה. אם מכוונים ל-Windows ישן יותר, דרושה הפצה מחדש באמצעות
vcredist. - לא מומלץ לבנות DLL עם
/MT. ה-CRT המקושר סטטית שומר את המצב שלו רק בתוך ה-DLL עצמו, ולכן הקצאת זיכרון, locale, ואופן הפעולה של_set_se_translatorסוטים בין ה-EXE ל-DLL. אם מיישרים בגסות “גם ה-EXE וגם ה-DLL המצורף ב-/MT”, קורית תאונה בהקצאה/שחרור שחוצה את הגבול. - אי אפשר לשלב
/clrעם/MT. אם משלבים C++/CLI, יש ללכת לצד/MD.
flowchart TB
accTitle: שלוש נקודות זהירות בקישור סטטי
accDescr: תרשים המראה שUCRT מגיע כחלק מ-Windows 10 ואילך אך ב-Windows ישן יותר דרושה הפצה מחדש עם vcredist, שבניית DLL עם /MT סוגרת את מצב ה-CRT בתוך ה-DLL וגורמת לתאונה בהקצאה/שחרור שחוצה גבול, ושאי אפשר לשלב /clr עם /MT.
g0["/MTבקישור סטטי"] --> g1["ב-Windows ישן דרוש vcredist"]
g0 --> g2["/MT ב-DLL לא מומלץ"]
g0 --> g3["אי אפשר לשלב עם /clr"]
g2 -.-> g4["תאונה בהקצאה/שחרור שחוצה גבול"]
איור 8: /MT חזק, אבל יש להיזהר משלוש הנקודות - הנחת UCRT, גבול ה-DLL, ו-C++/CLI.
5.2 .NET
ל-.NET יש single-file, self-contained ו-Native AOT, ולכן אפשר להקטין משמעותית את יחידת ההפצה החזותית.
עם זאת, יש צורך להבחין.
- framework-dependent: תלוי ב-.NET שבסביבת היעד
- self-contained: נושא את ה-runtime של .NET
- single-file: מרכז את ההפצה לפריט אחד
- Native AOT: מקטין עוד יותר את התלות בזמן ההפעלה, אך יש גם מגבלות תפקוד
אין זה ש’בגלל single-file, התלות ב-OS קטנה’. מה שקטן הוא בעיקר ריכוז פריט ההפצה של האפליקציה.
flowchart TB
accTitle: מה ש-single-file מקטין הוא ריכוז ההפצה
accDescr: תרשים המראה ש-framework-dependent תלוי ב-.NET שבסביבת היעד, ש-self-contained נושא את ה-runtime, וש-single-file רק מרכז את ההפצה לפריט אחד - ושלא בגלל single-file התלות ב-OS קטנה.
h1["framework-dependent"] --> h2["תלוי ב-.NET שבסביבת היעד"]
h3["self-contained"] --> h4["נושא את ה-runtime של .NET"]
h5["single-file"] --> h6["מרכז את ההפצה לפריט אחד"]
h6 -.-> h7["לא אומר שהתלות ב-OS קטנה"]
איור 9: בכל צורת פרסום של .NET, מה שקטן ומה שנשאר שונים.
נקודת הכניסה: שלוש התבניות של dotnet publish
אם רוצים קודם להפעיל ולראות את ההבדל, שלוש הפקודות האלה מספיקות.
rem 1. framework-dependent + single-file: דרוש runtime של .NET במחשב היעד
dotnet publish -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true
rem 2. self-contained + single-file: נושא את ה-runtime של .NET
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
rem 3. Native AOT: לאחר כתיבת PublishAot ב-csproj, מפרסמים
dotnet publish -c Release -r win-x64
ההמלצה הרשמית היא לכתוב את PublishSingleFile ואת PublishAot ב-csproj ולא בשורת הפקודה. כך מופעל ניתוח תאימות במהלך ה-build, וזה מקטין תאונות שמתגלות רק אחרי הפרסום.
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<PublishAot>true</PublishAot>
</PropertyGroup>
בנוסף, אם מציינים RuntimeIdentifier, SelfContained הופך ל-true כברירת מחדל. כשרוצים framework-dependent, יש לציין במפורש --self-contained false. כדי לפרסם Native AOT ב-Windows, דרוש עומס העבודה (workload) פיתוח שולחני עם C++ של Visual Studio.
פשרה: ‘לרכז לאחד’ אינו בחינם
אם כותבים כאן רק ‘אפשר’, קורית תאונה, ולכן מסדרים גם את המחיר.
פריסה בזמן ההפעלה של single-file
- כברירת מחדל, מה שנארז (bundled) הוא רק DLL-ים מנוהלים (managed). הם נטענים לזיכרון בזמן ההפעלה ולא נפרסים לתיקייה. הבינארי הנייטיבי של גוף ה-runtime נשאר כקובץ נפרד.
- אם רוצים לכלול גם את הבינארי הנייטיבי בפריט אחד, משתמשים ב-
IncludeNativeLibrariesForSelfExtract; אם רוצים לפרוס הכול לפני ההרצה, משתמשים ב-IncludeAllContentForSelfExtract. בשני המקרים ההתנהגות היא “פורסים ואז מפעילים”, וב-Windows הפריסה היא תחת%TEMP%\.net. אפשר לשנות את המיקום עםDOTNET_BUNDLE_EXTRACT_BASE_DIR, אבל אין להפוך אותו למיקום שניתן לכתיבה על ידי משתמשים או שירותים בעלי הרשאות שונות. - אם מפעילים
EnableCompressionInSingleFile, ה-EXE נעשה קטן משמעותית, אבל ההפעלה מואטת בגלל הפענוח (decompression) בזיכרון בזמן ההפעלה. גם המסמכים הרשמיים כותבים “למדדו גם גודל וגם עלות הפעלה לפני השימוש”. ההשפעה משתנה מאוד בין אפליקציות.
אי-תאימות API ב-single-file
כשמרכזים את ההפצה לפריט אחד, קוד שמניח נתיב קובץ נשבר בשקט.
| API | ההתנהגות ב-single-file |
|---|---|
Assembly.Location |
מחזיר מחרוזת ריקה |
Assembly.CodeBase |
PlatformNotSupportedException |
Assembly.GetFile |
IOException |
Module.Name |
מחזיר את המחרוזת <Unknown> |
אם נוגעים בקובץ שלצד ה-EXE, יש לעבור ל-AppContext.BaseDirectory; אם דרוש נתיב קובץ ההרצה, ל-Environment.ProcessPath. מכיוון שספריות צד שלישי משתמשות בהם לפעמים גם באופן פנימי, אחרי ההמרה ל-single-file דרושה בדיקת הפעלה על מכשיר אמיתי לפחות פעם אחת.
flowchart TB
accTitle: קוד שמניח נתיב נשבר בשקט
accDescr: תרשים המראה שכשמרכזים את ההפצה לפריט אחד, התנהגות API-ים שמניחים נתיב קובץ משתנה - כמו Assembly.Location שמחזיר מחרוזת ריקה - ולכן יש לעבור אל AppContext.BaseDirectory לקובץ הסמוך ואל Environment.ProcessPath לנתיב קובץ ההרצה, ולבדוק הפעלה על מכשיר אמיתי לפחות פעם אחת.
i1["המרה ל-single-file"] --> i2["התנהגות API-ים שמניחים נתיב משתנה"]
i2 --> i3["קובץ סמוך: AppContext.BaseDirectory"]
i2 --> i4["קובץ הרצה: Environment.ProcessPath"]
i3 --> i5["בדיקת הפעלה על מכשיר אמיתי"]
i4 --> i5
איור 10: אחרי ההמרה ל-single-file, מחליפים את אופן קבלת הנתיב ובודקים על מכשיר אמיתי.
מגבלות התפקוד של Native AOT
במקום ‘הפעלה מהירה, בלי runtime’, הדברים הבאים הופכים לבלתי זמינים.
- טעינה דינמית, כמו
Assembly.LoadFile - יצירת קוד בזמן ריצה, כמו
System.Reflection.Emit - C++/CLI
- ב-Windows, built-in COM
- trimming הוא חובה, ולכן גם מגבלות ה-trimming חלות במלואן
- בפועל זה single-file, ולכן גם אי-התאימות ה-API שלמעלה חלה במלואה
System.Linq.Expressionsתמיד פועל כמפרש (interpreter), ואיטי יותר מקוד שנוצר בזמן ריצה
גם הפלטפורמה היעד קבועה מראש. ב-.NET 8, ב-Windows נתמכים x64 ו-Arm64, ומ-.NET 9 ואילך נוסף גם x86. הרעיון של ‘פריט אחד ל-AnyCPU’ לא מתקיים מלכתחילה.
flowchart TB
accTitle: המחיר של Native AOT
accDescr: תרשים המראה ש-Native AOT מקטין את התלות בזמן ההפעלה, אך במחיר של אי-אפשרות לטעינה דינמית, יצירת קוד בזמן ריצה, C++/CLI ו-built-in COM ב-Windows, שגם מגבלות ה-trimming וה-single-file חלות, ושהפלטפורמה היעד קבועה מראש.
j0["Native AOT"] --> j1["מקטין תלות בזמן ההפעלה"]
j0 --> j2["טעינה דינמית ויצירת קוד בזמן ריצה - בלתי אפשריים"]
j0 --> j3["C++/CLI ו-built-in COM - בלתי אפשריים"]
j2 -.-> j4["חלות גם מגבלות ה-trimming וה-single-file"]
j3 -.-> j5["OS ו-arch היעד קבועים מראש"]
איור 11: יש לראות את היתרונות של Native AOT יחד עם הפונקציות שהופכות לבלתי זמינות.
5.3 WebView2
כשמאמצים את WebView2, קושי ה-single binary משתנה לגמרי. הנושא המרכזי כאן הוא לא מספר ה-EXE-ים, אלא איך לטפל ב-WebView2 Runtime.
לפני ‘האם אפשר לרכז ל-EXE אחד’, יש שאלה שכדאי לחשוב עליה.
- האם להניח שה-runtime כבר קיים בסביבה
- האם להשתמש ב-Evergreen
- האם לצרף Fixed Version
- עד כמה לקחת אחריות בהפצת offline
בגדול, ההבדל בין Evergreen ל-Fixed Version הוא כך.
| Evergreen | Fixed Version | |
|---|---|---|
| מי מעדכן | Microsoft. מתעדכן אוטומטית | אני. מוחלף בהתאם לעדכון האפליקציה |
| הישות במחשב | עותק אחד משותף לכל האפליקציות | מצורף לכל אפליקציה בנפרד |
| גודל ההפצה | כמעט לא גדל | עולה על 250 MB |
| דרך ההתקנה | ה-Bootstrapper הוא כ-2 MB ומוריד את מה שדרוש. ב-offline מצרפים את ה-Standalone Installer | מפיצים את מלוא הבינארי הפרוס יחד עם האפליקציה |
| מגבלה קיימת | יש לבדוק לפני ההפעלה אם זה מותקן במחשב | אי אפשר להריץ מנתיב רשת או UNC |
Evergreen Runtime מותקן מראש כחלק ממערכת ההפעלה ב-Windows 11. אבל מכיוון שיש עדיין מחשבי Windows 10 שבהם הוא לא מותקן, גם Microsoft עצמה כותבת ש’גם אם בוחרים ב-Evergreen, כדאי בכל זאת להפיץ את ה-Runtime’. כלומר, בכל מקרה דרוש נוהל שבו האפליקציה בודקת אם הוא קיים, ואם לא מספיק - מתקינה אותו. Fixed Version מאפשר ‘לאחוז בעצמך בתזמון העדכון’, אבל במחיר של גידול הפצה של מאות MB. יש להחליט על כך מראש, בנפרד מהשאלה של EXE אחד.
flowchart TB
accTitle: מחליטים מראש על אופן ההפצה של WebView2
accDescr: תרשים המראה ש-Evergreen הוא עותק משותף אחד שמעדכנת Microsoft אוטומטית אך לפעמים לא מותקן במחשב, ש-Fixed Version מאפשר לאחוז בעדכון בעצמך אך מגדיל את ההפצה במאות MB, ושבשני המקרים דרוש נוהל שבודק קיום ומתקין במידת הצורך.
k0["מאמצים את WebView2"] --> k1["Evergreen: עותק משותף שמתעדכן אוטומטית"]
k0 --> k2["Fixed Version: מצרפים ומעדכנים בעצמנו"]
k1 --> k3["בודקים קיום, מתקינים אם חסר"]
k2 -.-> k4["ההפצה גדלה במאות MB"]
איור 12: הנושא המרכזי של WebView2 אינו מספר ה-EXE-ים, אלא הטיפול ב-Runtime.
5.4 WinUI 3 / Windows App SDK
גם WinUI 3 - ברגע שמאמצים אותו, דרישות ההפצה משתנות. הבחירה בטכנולוגיית UI הופכת ישירות לבחירת שיטת ההפצה.
באופן ספציפי, יש שני צירים להחליט עליהם.
- packaging: packaged(MSIX), packaged שמצביע למיקום חיצוני, או unpackaged
- runtime: framework-dependent או self-contained
ומה שמשפיע מנקודת המבט של single binary היא מגבלת השילוב הזו.
PublishSingleFileניתן לשימוש רק ביישום WinUI 3 שהוא unpackaged וגם self-contained, ודרוש Windows App SDK 1.5 ואילך.- ביישום packaged, וכן ביישום packaged שמצביע למיקום חיצוני, אי אפשר להשתמש ב-
PublishSingleFile. - אם עוברים ל-unpackaged, מאבדים את package identity, ולכן פונקציות Windows שמניחות package identity - כמו התראות, משימות רקע, שיוך קבצים והרחבת תפריט הקשר - הופכות לבלתי זמינות.
כלומר, ב-WinUI 3, ‘לרכז ל-EXE אחד’ ו’להשתמש בפונקציות ההרחבה של Windows’ מתנגשים חזיתית. אם מעדיפים single binary מעל הכול, לרוב מהיר יותר לבחון מחדש מלכתחילה את ההנחות של טכנולוגיית ה-UI.
flowchart TB
accTitle: ההתנגשות בין EXE אחד ל-package identity
accDescr: תרשים המראה שב-WinUI 3, PublishSingleFile ניתן לשימוש רק בתצורת unpackaged וגם self-contained, ושהמעבר ל-unpackaged גורם לאובדן package identity ולביטול פונקציות כמו התראות ושיוך קבצים, כך ש-EXE אחד ופונקציות ההרחבה של Windows מתנגשים חזיתית.
m1["רוצים להשתמש ב-PublishSingleFile"] --> m2["רק unpackaged + self-contained"]
m2 --> m3["איבוד package identity"]
m3 --> m4["התראות, שיוך וכו' לא זמינים"]
m4 -.-> m5["EXE אחד מתנגש חזיתית עם פונקציות ההרחבה"]
איור 13: ב-WinUI 3, הבחירה ב-EXE אחד פירושה לוותר על package identity.
6. תחומים שבהם רישום ותלות דרושים מיסודם
6.1 הרחבת מעטפת (Shell)
הרחבת מעטפת שנטענת ל-Explorer שונה לחלוטין מ-‘EXE שרק מניחים’. כאן, יותר ממספר הקבצים, הנושא המרכזי הוא איך רושמים ל-Explorer.
6.2 שירות Windows
גם אם אפשר לרכז את ה-exe של גוף השירות עצמו לקובץ אחד, ההפצה היא בעיה נפרדת.
- רישום ב-SCM
- הרשאות
- חשבון ההפעלה
- הגדרות התאוששות
יש לחשוב עליהם. כלומר, שירות הוא תחום שבו יותר מ-‘לרכז ל-EXE אחד’, יש לגבש את ‘איך מתקינים’.
6.3 מנהל התקן
מנהל התקן ברור אף יותר. הוא מתקיים רק כולל INF, חתימה ותהליך התקנה, ולכן מלכתחילה קשה לו לעלות על זירת ה-single binary.
flowchart TB
accTitle: תחומים שבהם רישום וחתימה הם הנושא המרכזי
accDescr: תרשים המראה שהרחבת מעטפת מתקיימת עם רישום ל-Explorer, שירות Windows מתקיים עם רישום ב-SCM והרשאות וחשבון הפעלה, ומנהל התקן מתקיים עם INF וחתימה - כך שיותר ממספר הקבצים, תכנון הרישום והתלות הוא הנושא המרכזי.
n1["הרחבת מעטפת"] --> n2["רישום ב-Explorer"]
n3["שירות"] --> n4["רישום ב-SCM, הרשאות, חשבון"]
n5["מנהל התקן"] --> n6["INF וחתימה"]
n4 -.-> n7["תכנון הרישום חשוב יותר ממספר הקבצים"]
איור 14: בשלושת התחומים האלה, יותר מ-‘לרכז ל-EXE אחד’, מגבשים ‘איך רושמים’.
7. טבלת החלטה מעשית
לצורך הכרעה גסה, הטבלה הזו נוחה לשימוש.
| מה רוצים לבנות | מידת המימוש של EXE אחד | מה כדאי לשקול מראש |
|---|---|---|
| כלי Win32/C++ שמופעל באופן עצמאי | גבוהה | קישור סטטי, OS/arch יעד |
| כלי WinForms/WPF שמופעל באופן עצמאי | גבוהה | התאמת self-contained, single-file, Native AOT |
| יישום WinUI 3/Windows App SDK | בינונית | מצב הפצה, תלות נוספת |
| UI שולחני מבוסס WebView2 | נמוכה עד בינונית | שיטת הפצת ה-Runtime |
| הרחבת קליק ימני או תצוגה מקדימה ב-Explorer | נמוכה | רישום COM/Registry |
| שירות Windows | בינונית | רישום SCM, הרשאות, נוהל עדכון |
| יישום עם מנהל התקן מצורף | נמוכה | INF, חתימה, התקנה |
החשוב ביותר בטבלה הזו הוא להבין ש-‘מספר הבינאריים’ ו-‘תחום האחריות של ההפצה’ הם שני דברים שונים.
8. מה כדאי להחליט מראש בתכנון ההפצה
אם רוצים להצליח באיחוד לבינארי יחיד, יש דברים שכדאי להחליט לפני היישום.
8.1 מחליטים מה רוצים לרכז לאחד
- האם רוצים לרכז את פריט ההפצה לאחד
- האם רוצים לבטל התקנה מראש של runtime
- האם רוצים לבטל צורך במתקין
- האם רוצים להקל על עדכון offline
בהתאם לתשובה, הטכנולוגיה שבוחרים משתנה.
8.2 מקבעים מראש את Windows המינימלי הנתמך ואת ה-arch
גם single-file וגם Native AOT הם ביסודם OS/architecture specific. אם ממשיכים ב-‘בכל מקרה, בקובץ אחד’ בלי להבהיר את זה, בסוף נתקעים בגלל חוסר API או אי-התאמת runtime.
8.3 מנסחים במפורש ‘מה מצרפים’ ו’מה משאירים ל-Windows’
בעבודה מעשית, רק כתיבת הרשימה הזו מקטינה משמעותית תאונות.
- מה שמצרפים לאפליקציה
- ה-exe עצמו
- DLL עצמי
- תבנית הגדרות
- self-contained runtime
- מה שמשאירים ל-Windows
- DLL-ים של המערכת
- OS API
- SCM/Registry/Explorer
- תשתית מנהלי התקן
- מה שמניחים כתנאי מוקדם בנפרד
- WebView2 Runtime
- VC++ Redistributable
- Office / Excel
- מנהל התקן ייעודי
flowchart TB
accTitle: מנסחים במפורש שלוש קטגוריות של תחום אחריות
accDescr: תרשים המראה שרק כתיבת שלוש הקטגוריות - מה שמצרפים לאפליקציה, מה שמשאירים ל-Windows, ומה שמניחים בנפרד - מקטינה משמעותית תאונות הפצה.
p0["תחום האחריות של ההפצה"] --> p1["דברים שמצרפים לאפליקציה"]
p0 --> p2["דברים שמשאירים ל-Windows"]
p0 --> p3["דברים שמניחים בנפרד"]
p3 -.-> p4["רק כתיבתם מקטינה תאונות"]
איור 15: מנסחים מראש את שלוש הקטגוריות - צירוף, השארה ל-OS, והנחה בנפרד.
8.4 אם מעדיפים single binary, מקטינים אינטגרציה מארחת
זה משפיע משמעותית.
- מוותרים על הרחבת מעטפת והופכים ל-EXE רגיל
- לא הופכים לשירות, ומסתפקים ב-Task Scheduler או הפעלה מפורשת
- משתמשים ב-UI נייטיבי במקום WebView2
- סוגרים את ה-COM בתוך התהליך העצמי
בקיצור, ככל שמקטינים תכנון של ‘לגרום ל-OS לטעון’ או ‘לרשום ל-OS’, מתקרבים ל-single binary.
flowchart TB
accTitle: ככל שמקטינים אינטגרציה מארחת, מתקרבים
accDescr: תרשים המראה שוויתור על הרחבת מעטפת לטובת EXE רגיל, אי-הפיכה לשירות והסתפקות ב-Task Scheduler או הפעלה מפורשת, ושימוש ב-UI נייטיבי במקום WebView2 - כל אלה מקטינים תכנון של טעינה ורישום ל-OS, ומתקרבים ל-single binary.
q1["מוותרים על הרחבת מעטפת, EXE רגיל"] --> q4["מקטינים תכנון רישום ל-OS"]
q2["לא הופכים לשירות, הפעלה מפורשת"] --> q4
q3["UI נייטיבי במקום WebView2"] --> q4
q4 --> q5["מתקרבים ל-single binary"]
איור 16: אם מעדיפים single binary, מקטינים את האינטגרציה המארחת עצמה.
9. סיכום
איחוד לבינארי יחיד ב-Windows אפשרי עד רמה גבוהה למדי. אבל למה זה מגיע, זה במשפט הזה.
אפשר להפוך את האפליקציה ל-EXE אחד. אבל אי אפשר להפוך גם את Windows שבו האפליקציה תלויה ל-EXE אחד.
חמש נקודות שכדאי לזכור במיוחד:
- ל-EXE רגיל שמופעל באופן עצמאי, אפשר לרכז לרמה משמעותית להפצה בקובץ אחד
- קישור סטטי ב-C/C++, single-file ב-.NET, ו-Native AOT הם אפשרויות חזקות
- אבל התלות בגרסת OS, arch, DLL-ים של המערכת, ומודל האבטחה לא נעלמת
- הרחבת מעטפת, שירות, מנהל התקן, WebView2, וחלק מ-WinUI 3 - עבורם, נושא הרישום ל-OS או ה-runtime הנוסף הוא גוף העניין
- ההצלחה או הכישלון של single binary נקבעים בהפרדה מראש של ‘מה רוצים לרכז לאחד’
אם מעדיפים single binary בתוקף, קל בהרבה להצליח אם מתכננים כבר בשלב בחירת הטכנולוגיה בכיוון של הורדת מידת הצימוד ל-OS.
flowchart TB
accTitle: המפתח להצלחה הוא מידת הצימוד ל-OS
accDescr: תרשים המראה שאפשר להפוך אפליקציה ל-EXE אחד, אך אי אפשר להפוך גם את Windows שהיא תלויה בו ל-EXE אחד, ולכן אם מעדיפים single binary בתוקף, כדאי להוריד את מידת הצימוד ל-OS כבר בשלב בחירת הטכנולוגיה.
r1["הופכים את האפליקציה ל-EXE אחד"] --> r2["אפשרי"]
r3["הופכים גם את Windows התלוי ל-EXE אחד"] --> r4["בלתי אפשרי"]
r4 -.-> r5["לכן מורידים את הצימוד ל-OS בבחירת הטכנולוגיה"]
איור 17: ההצלחה של איחוד ל-EXE אחד נקבעת בתכנון מידת הצימוד ל-OS מלכתחילה.
מקורות
- Microsoft Learn, Create a single file for application deployment
- Microsoft Learn, Native AOT deployment overview
- Microsoft Learn, C runtime (CRT) and C++ standard library (STL) lib files
- Microsoft Learn, Dynamic-link library search order
- Microsoft Learn, Targeting your application for Windows
- Microsoft Learn, Creating Registration-Free COM Objects
- Microsoft Learn, Registering Shell Extension Handlers
- Microsoft Learn, CreateServiceW function
- Microsoft Learn, Overview of INF Files
- Microsoft Learn, Windows driver signing tutorial
- Microsoft Learn, Distribute your app and the WebView2 Runtime
- Microsoft Learn, Windows App SDK deployment guide for self-contained apps
- Microsoft Learn, Package and deploy Windows apps overview
- Microsoft Learn, Packaging overview - Windows apps
- Microsoft Learn, Evergreen vs. fixed version of the WebView2 Runtime
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
רשימת בדיקה לטיפול בטוח בתהליכי ילד ביישום Windows
כדי לטפל בבטחה בתהליכי ילד ביישום Windows, תכנון הבעלות על עץ התהליכים ונוהל הסיום חשוב יותר מבחירת ה-API להפעלה. המאמר מסדר את Job Objec...
איך בוחרים שיטת הפצה ליישום Windows - MSI/MSIX/ClickOnce/xcopy/עדכון עצמי
בחירת שיטת ההפצה ליישום Windows אינה עניין של טעם בצורת המתקין, אלא בחירה של מידת הצימוד ל-OS ושל מי נושא באחריות העדכון. המאמר מסדר את M...
איך קוראים ל-DLL של C# Native AOT מ-C/C++
פרסום ספריית מחלקות C# כ-DLL נייטיבי באמצעות Native AOT, וקריאה לנקודות הכניסה מסוג UnmanagedCallersOnly מ-C/C++ — סקירה לפי מקום השימוש...
כללי הנחיה שמפחיתים תקלות קריאה משובשת של Codex ב-Windows
המאמר מסדר כללי הנחיה מעשיים לגרום ל-Codex לטפל בקבצים ביפנית ב-Windows בבטחה - הימנעות משמירה על סמך ניחוש, שימור ה-encoding הקיים, ואימ...
איך בוחרים בין WinForms, WPF ו-WinUI - טבלת החלטה מהשטח
המאמר מסדר את הבחירה בין WinForms, WPF ו-WinUI מנקודות המבט של פיתוח חדש, נכסים קיימים, הפצה, ביטוי ה-UI ומבנה הצוות.
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
בהפצת יישום Windows, כדאי לתכנן כולל single-file, צירוף runtime, החלטה על אימוץ WebView2 או WinUI, ושאלת ההפיכה לשירות - כדי לצמצם חזרות אחורה.
ייעוץ טכני וסקירת תכנון
בקשה כמו 'רוצים EXE אחד' קל יותר להכריע לגביה כשמפרידים בין יחידת ההפצה, התלות במערכת ההפעלה, הצורך ברישום ואחריות העדכון.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- האם אפשר להפיץ יישום Windows בקובץ EXE אחד בלבד?
- עבור כלי שולחני שמופעל באופן עצמאי, אפשר להגיע לרמה גבוהה למדי. באמצעות קישור סטטי ב-C/C++, self-contained או single-file ב-.NET, או Native AOT, אפשר לרכז את ההפצה ל-EXE אחד. עם זאת, היכולת לרכז ל-EXE אחד והיעדר תלות ב-Windows היעד הם שני דברים שונים, והתלות בגרסת מערכת ההפעלה, הארכיטקטורה, DLL-ים של המערכת ומודל האבטחה לא נעלמת.
- האם המרה ל-single-file ב-.NET מבטלת את התלות במערכת ההפעלה?
- לא. מה ש-single-file מקטין הוא בעיקר את הריכוז של פריט ההפצה של האפליקציה, ולא את התלות במערכת ההפעלה. framework-dependent תלוי ב-.NET שבסביבת היעד, self-contained נושא איתו את ה-runtime של .NET, ו-Native AOT יכול להקטין עוד יותר את התלות בזמן ההפעלה אך יש לו גם מגבלות תפקוד. גם single-file וגם Native AOT הם ביסודם ספציפיים למערכת הפעלה וארכיטקטורה, ולכן יש לקבע מראש את גרסת Windows המינימלית הנתמכת ואת ה-arch.
- אילו יישומים קשה לרכז ל-EXE אחד?
- הרחבות מעטפת (Shell), שירותי Windows, מנהלי התקן, ממשקי משתמש מבוססי WebView2, וחלק מ-WinUI 3. עבור אלה, לא מספר הקבצים הוא הנושא המרכזי, אלא הרישום למערכת ההפעלה והטיפול ב-runtime נוסף. לדוגמה, הרחבת מעטפת דורשת רישום ב-Explorer, שירות דורש תכנון של רישום ב-SCM, הרשאות וחשבון ההפעלה, ומנהל התקן נשלם רק כולל INF וחתימה - ולכן הפצה של 'רק להניח קובץ' לא מתקיימת. עבור WebView2 יש להחליט מראש על אופן ההפצה של WebView2 Runtime.
- האם יש שיטת הפצה טובה יותר מאשר להכריח הטמעה ל-EXE אחד?
- הפצת app-local, שבה מניחים DLL לצד ה-EXE, היא אפשרות חזקה. גם בצורה של app.exe וכמה DLL-ים סמוכים, אם אפשר להפיץ ב-xcopy בלי צורך בהתקנה ובלי הרשאות מנהל, לא נדיר שקל יותר לתחזק מאשר לדחוס בכוח ל-EXE אחד. החשוב הוא להפריד מראש בין הרצון לרכז את ההפצה לפריט אחד, הרצון לבטל התקנה מראש של runtime, והרצון לבטל צורך במתקין (installer).