היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 19 Mar 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173668)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). הפצת אפליקציית Windows כקובץ אחד: מה single binary כן מכסה, ומה נשאר תלוי ב-OS. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173668 https://comcomponent.com/he/blog/windows-single-binary-and-os-dependencies/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22173668
- DOI (הגרסה הזו)
- 10.5281/zenodo.22173669
המאמר הזה התחיל משיחת מסדרון: עד כמה מותר לקרוא לזה single binary ב-Windows? הפוסט שהתחיל את זה למטה. אפשר לדלג עליו; מה שאחריו עומד לבד.
ב-Windows, “אם אפשר, נפיץ כקובץ אחד” היא בקשה די רגילה. בכלים פנימיים, כלי חיבור לציוד, תחנות ניטור, סביבות offline, ובשטח שרוצה להימנע מ-installer ככל האפשר, single binary מפתה מאוד.
אבל אם לא מפרידים את הנושא מראש, השיחה בדרך כלל מתחילה להתפזר באמצע. כי כשאומרים ב-Windows “רוצים single binary”, מתערבבים בקלות ארבעה נושאים שונים.
- רוצים שהחבילה תהיה קובץ אחד
- רוצים בלי התקנת runtime מראש של .NET או Visual C++
- רוצים שזה ירוץ אחרי ששמים תיקייה, בלי installer ובלי הרשאות admin
- לא רוצים תלות בהבדלים בין גרסאות Windows של היעד
ארבעת אלה אינם אותו דבר. בפועל, החשיבה הכי פחות סוטה היא זו:
אפשר לארוז הרבה דברים ל-EXE אחד. אבל אי אפשר לאפס את התלות ב-Windows של היעד.
המאמר הזה מסמן את הגבול הזה מנקודת מבט מעשית של אפליקציות Windows.
flowchart TB
accTitle: אפשר EXE אחד, התלות ב-OS לא נעלמת
accDescr: בבקשה ל-single binary מתערבבים שיח על הפצה ושיח על תלות; אפשר לארוז לחבילת EXE אחת, אבל אי אפשר לאפס תלות ב-Windows של היעד.
a0["רוצים single binary"] --> a1["שיח הפצה: קובץ אחד, לשים ולרוץ"]
a0 --> a2["שיח תלות: בלי runtime, בלי תלות ב-OS"]
a1 --> a3["אריזה ל-EXE אחד אפשרית בהרבה מקרים"]
a2 --> a4["אי אפשר לאפס תלות ב-OS"]
איור 1: ב”להפיץ כקובץ אחד” מעורבבים מה שאפשר ומה שאי אפשר.
1. קודם המסקנה
אם מסכמים קודם:
- ל-EXE שולחן עבודה רגיל אפשר להגיע רחוק עם single binary
- אבל היכולת לארוז ל-EXE אחד והיעדר תלות ב-Windows של היעד הם שני דברים שונים
- ב-Shell extension, שירות Windows, מנהל התקן, WebView2, וחלק מ-WinUI 3, הנושא הוא פחות מספר קבצים ויותר מה רושמים ב-OS ומה מניחים מראש
- בפועל מה שחשוב ביותר הוא להפריד: רוצים single binary, או בלי installer, או פחות תלות ב-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 הוא מגיע כחלק מה-OS |
| VC++ Redistributable | Visual C++ Redistributable | installer שמכניס למכונת היעד את DLL של Visual C++ runtime שאינם UCRT |
| framework-dependent | הפצה שתלויה ב-.NET של היעד | צורת הפצה שמניחה ש-runtime של .NET כבר מותקן במכונת היעד |
| self-contained | .NET runtime מצורף | האפליקציה נושאת איתה את כל ה-runtime של .NET |
| single-file | פרסום כקובץ יחיד | אפשרות פרסום של .NET שמאגדת את החבילה ל-EXE אחד |
| Native AOT | Native Ahead-Of-Time | צורת הפצה שמקמפלת IL מראש לקוד native בזמן הפרסום. אין JIT בזמן ריצה |
| app-local | הפצה ליד ה-EXE | שמים DLL באותה תיקייה כמו ה-EXE, לשימוש האפליקציה הזו בלבד |
| xcopy | הפצה בהעתקה | בלי installer ובלי הרשאות admin: שמים תיקייה והיא רצה |
| SCM | Service Control Manager | מנגנון ה-OS שמנהל רישום, הפעלה ועצירה של שירותי Windows |
| UAC | User Account Control | מנגנון האבטחה של Windows ששולט בהעלאה להרשאות admin |
| Shell extension | הרחבת Explorer | רכיב COM שנטען לתוך תהליך כמו Explorer ורץ שם |
| Evergreen | מצב עדכון שוטף | מצב הפצה של WebView2 Runtime: עותק משותף אחד שמתעדכן אוטומטית |
| Fixed Version | מצב גרסה קבועה | מצרפים גרסה מסוימת של WebView2 Runtime לאפליקציה |
| arch | architecture, ארכיטקטורת CPU | x86 / x64 / Arm64 |
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 22, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
2. חושבים על single binary בארבע רמות
אם מציירים את הרמות קודם, זה נראה כך. הקושי עולה מלמטה למעלה, ואת הרמה העליונה אי אפשר להשיג ב-Windows.
flowchart BT
A["רמה A: החבילה היא קובץ אחד<br/>אפשרי בהרבה מקרים"]
B["רמה B: בלי התקנת runtime של השפה מראש<br/>אפשרי בהרבה מקרים"]
C["רמה C: בלי התקנה או רישום<br/>תלוי בסוג האפליקציה"]
D["רמה D: בלי תלות ב-Windows של היעד<br/>ב-Windows אי אפשר להגיע לכאן"]
A --> B
B --> C
C --> D
איור 3: ארבע הרמות של single binary. הקושי עולה מלמטה למעלה, ולרמה D אי אפשר להגיע.
כשאומרים “רוצים single binary”, העבודה משתנה לגמרי לפי איזו מארבע הרמות מתכוונים.
2.1 רמה A: החבילה היא קובץ אחד
השכבה הכי חיצונית:
- אפשר לשלוח קובץ אחד במייל
- מספיק לשים קובץ אחד על USB
- ביעד שמים רק
app.exe
זה שיח על יחידת ההפצה הנראית. גם אם בזמן הפעלה יש extract זמני, וגם אם יש תלות ב-DLL של ה-OS, התנאי הזה לבדו יכול להתקיים.
2.2 רמה B: בלי התקנת runtime של השפה מראש
השלב הבא: המכונה רצה בלי .NET runtime או VC++ Redistributable מראש.
- static linking ב-C/C++
- self-contained ב-.NET
- single-file ב-.NET
- Native AOT ב-.NET
ברמה הזו תחושת “אפשר לקחת את זה כמו שהוא” מתחזקת מאוד.
2.3 רמה C: בלי התקנה או רישום
מכאן זה נהיה קשה בבת אחת.
EXE רגיל לפעמים באמת רץ אחרי ששמים אותו. אלה לא:
- Shell extension
- שירות Windows
- custom URL scheme או file association
- מנהל התקן
- רכיב שנטען לתהליך אחר, כמו Explorer או Office
באזור הזה לא מספיק לשים קבצים. צריך רישום בצד ה-OS, וחיבור למארח.
flowchart TB
accTitle: אזורים שבהם לשים קובץ לא מספיק
accDescr: Shell extension, שירות Windows, file association, מנהל התקן, ורכיב שנטען לתהליך אחר לא מסתפקים בהנחת קבצים; צריך רישום ב-OS וחיבור למארח.
c1["Shell extension ושירות"] --> c4["לשים קובץ לא מספיק"]
c2["association ומנהל התקן"] --> c4
c3["רכיב שנטען לתהליך אחר"] --> c4
c4 --> c5["צריך רישום ב-OS וחיבור למארח"]
איור 4: רמה C נהיית קשה כי נכנס רישום ב-OS.
2.4 רמה D: בלי תלות ב-Windows של היעד
ב-Windows זה בלתי אפשרי.
אפליקציית Windows רצה בסוף מעל API של Windows, ה-loader, מודל האבטחה ומחסנית ההתקנים. single binary מכסה את תחום האחריות של האפליקציה. הוא לא לוקח איתו את ה-OS.
3. איפה קל יחסית להגיע ל-EXE אחד
גם ב-Windows יש אפליקציות שקל יחסית לארוז ל-EXE אחד.
- כלי שולחן עבודה שרץ לבד
- אפליקציה עסקית שבה ה-EXE עצמו מחזיק UI ועיבוד
- כלים לתקשורת, עיבוד קבצים, איסוף לוגים, ניטור, שליטה בציוד
- בלי אינטגרציה למארח כמו Explorer או Office
- UI שלא מניח web runtime
בסוג כזה אפשר לכלול באפליקציה עצמה הרבה דברים.
- קוד שלכם
- resources
- manifest
- הגדרות ברירת מחדל
- נתוני תבנית
- חלק מספריות צד שלישי
- runtime של השפה עצמו
ועוד: גם בלי לדחוף DLL לגמרי לתוך ה-EXE, הפצת app-local עם DLL ליד ה-EXE היא אפשרות חזקה ורגילה ב-Windows. בפועל,
app.exeאחד- או
app.exeועוד כמה DLL לידו - אבל בלי installer, בלי הרשאות admin, ואפשר להפיץ ב-xcopy
לא נדיר שזה קל יותר לתחזוקה מדחיסה בכוח ל-EXE אחד.
flowchart TB
accTitle: app-local כנקודת נחיתה
accDescr: גם בלי לדחוף DLL לגמרי לתוך ה-EXE, אם שמים DLL ליד ה-EXE ואפשר להפיץ ב-xcopy בלי installer ובלי הרשאות admin, לא נדיר שזה קל יותר לתחזוקה מדחיסה בכוח ל-EXE אחד.
d1["app.exe ועוד כמה DLL לידו"] --> d2["בלי installer ובלי הרשאות admin"]
d2 --> d3["אפשר להפיץ ב-xcopy"]
d3 -.-> d4["לפעמים קל יותר לתחזוקה מדחיסת EXE אחת"]
איור 5: גם בלי להתעקש על EXE אחד, app-local לעיתים קרובות ממלא את המטרה.
4. תלות ב-Windows שנשארת גם עם EXE אחד
אם חושבים “EXE אחד אומר שאין תלות ב-Windows של היעד”, כאן נופלים. גם אחרי אריזה ל-EXE אחד נשארות תלויות.
4.1 תלות בגרסת OS
לכל Windows API יש גרסת OS מינימלית נתמכת. יש גם הבדל בין x64 ל-Arm64. כלומר גם עם EXE יחיד צריך לקבע מראש:
- האם זה רץ עד Windows 10
- האם זה מניח Windows 11
- האם מריצים גם על Windows Server
- איזה arch: x86 / x64 / Arm64
4.2 תלות ב-DLL של המערכת
גם אם חשבתם שזה EXE אחד, בזמן ריצה אתם כמובן משתמשים ברכיבים שה-OS מספק.
kernel32.dlluser32.dlladvapi32.dll- תשתית COM
- תשתית בקרת שירותים
זה תחום האחריות של Windows.
4.3 תלות במודל האבטחה
- UAC
- ACL של קבצים
- Service Control Manager
- Registry
- מדיניות חתימת מנהלי התקן
את אלה האפליקציה לא יכולה לקחת על עצמה לבד.
flowchart TB
accTitle: תלויות שנשארות גם עם EXE אחד
accDescr: גם אחרי אריזה ל-EXE אחד נשארות תלות בגרסת OS מינימלית וב-arch, ב-DLL מערכת כמו kernel32.dll ובתשתית COM, ובמודל האבטחה כמו UAC, ACL ומדיניות חתימת מנהלי התקן.
e0["אפליקציה כ-EXE אחד"] --> e1["גרסת OS ו-arch"]
e0 --> e2["DLL מערכת ותשתית COM"]
e0 --> e3["מודל האבטחה"]
e1 --> e4["אף אחד מאלה האפליקציה לא יכולה לקחת על עצמה"]
e2 --> e4
e3 --> e4
איור 6: גם כשהחבילה היא קובץ אחד, התלות בתחום האחריות של Windows נשארת.
4.4 תלות במארח או ב-runtime
אם זה לא EXE שרץ לבד אלא עיצוב שיושב על מארח, התלות קופצת בבת אחת.
- WebView2: צריך WebView2 Runtime
- WinUI 3 / Windows App SDK: צריך לסדר את מצב ההפצה
- Shell extension: צריך רישום בצד Explorer
כלומר בחירת UI או אינטגרציה הופכת ישר לקושי בהפצה.
flowchart TB
accTitle: בחירת UI ואינטגרציה הופכת לקושי בהפצה
accDescr: WebView2 דורש Runtime, WinUI 3 דורש סידור מצב הפצה, ו-Shell extension דורש רישום ב-Explorer; בחירת מארח או runtime הופכת ישר לקושי בהפצה.
f1["משתמשים ב-WebView2"] --> f2["צריך Runtime"]
f3["משתמשים ב-WinUI 3"] --> f4["צריך לסדר מצב הפצה"]
f5["בונים Shell extension"] --> f6["צריך רישום ב-Explorer"]
איור 7: ככל שמתרחקים מ-EXE שרץ לבד, התלות קופצת.
5. נקודת נחיתה לפי טכנולוגיה
5.1 native C/C++
native C/C++ נמצא בצד עם יותר חופש ל-single binary. יש מקום לבחור static linking, ול-EXE שרץ לבד קל יחסית להתקרב לשם.
אבל יותר חשוב מלדחוף הכול לקובץ אחד:
- מה עושים עם UCRT ו-VC++ runtime
- האם שמים DLL של צד שלישי כ-app-local
- עד כמה מצמצמים CPU / OS יעד
נקודת הכניסה: ההבדל בין /MT ל-/MD
ב-MSVC, מה שמכריע בפועל אם נושאים runtime הוא אפשרות הקומפיילר הזו.
| אפשרות | מה מקושר | מה צריך במכונת היעד |
|---|---|---|
/MD |
import libraries ל-DLL: ucrt.lib ו-vcruntime.lib. בזמן ריצה משתמשים ב-ucrtbase.dll וב-vcruntime<גרסה>.dll |
UCRT ו-VC++ runtime |
/MT |
static link של 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 הוא מגיע כחלק מה-OS. אם היעד ישן יותר, צריך redistribute עם
vcredist. - לא מומלץ לבנות DLL ב-
/MT. CRT ב-static link סוגר את המצב בתוך אותו DLL, ולכן הקצאת זיכרון, locale, וההשפעה של_set_se_translatorיכולים לסטות בין EXE ל-DLL. אם מיישרים בגסות “גם ה-EXE וגם ה-DLL ב-/MT”, מקבלים תקלות בהקצאה ושחרור שחוצים גבול. - אי אפשר לשלב
/clrעם/MT. אם יש C++/CLI, הולכים ל-/MD.
flowchart TB
accTitle: שלוש נקודות על static linking
accDescr: UCRT מגיע עם Windows 10 ומעלה אבל ב-Windows ישן יותר צריך vcredist; /MT בצד DLL לא מומלץ כי מצב ה-CRT נסגר בתוך ה-DLL והקצאה/שחרור שחוצים גבול נשברים; /clr ו-/MT לא משתלבים.
g0["static link עם /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 רק אוסף את החבילה לקובץ אחד; זה לא מקטין תלות ב-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: צריך .NET runtime במכונת היעד
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 צריך את עומס העבודה Desktop development with C++ ב-Visual Studio.
המחיר: “קובץ אחד” הוא לא בחינם
אם כותבים רק “אפשר”, נופלים. הנה גם המחיר.
extract בזמן הפעלה של single-file
- כברירת מחדל נארזים רק managed DLL. בזמן הפעלה הם נטענים לזיכרון ולא נפרסים לתיקייה. הבינארי native של ה-runtime עצמו נשאר כקבצים נפרדים.
- אם רוצים לכלול גם native binaries בקובץ אחד משתמשים ב-
IncludeNativeLibrariesForSelfExtract; אם רוצים לפרוס הכול לפני ההפעלה,IncludeAllContentForSelfExtract. שניהם עובדים כ”פורסים ואז מפעילים”, וב-Windows זה נפרס תחת%TEMP%\.net. אפשר לשנות מיקום עםDOTNET_BUNDLE_EXTRACT_BASE_DIR, אבל אל תשימו מקום שמשתמשים עם הרשאות שונות או שירותים יכולים לכתוב אליו. EnableCompressionInSingleFileמקטין מאוד את ה-EXE, אבל בזמן הפעלה יש decompress בזיכרון וההפעלה איטית יותר. גם התיעוד הרשמי אומר למדוד גם גודל וגם עלות הפעלה לפני שמשתמשים. ההשפעה משתנה מאוד בין אפליקציות.
אי-תאימות 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: כשהחבילה היא קובץ אחד, APIs שמניחים נתיב קובץ משנים התנהגות, למשל Assembly.Location מחזיר מחרוזת ריקה; לקבצים ליד ה-EXE משתמשים ב-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["בלי טעינה דינמית ובלי codegen בזמן ריצה"]
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 לחבילה; בשני המקרים האפליקציה צריכה לבדוק אם Runtime קיים ולהתקין אם חסר.
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 (התראות, background tasks, file association, הרחבת תפריט הקשר) לא זמינות.
כלומר ב-WinUI 3, “EXE אחד” ו”להשתמש ביכולות הרחבה של Windows” מתנגשים חזיתית. אם single binary בראש הסולם, לעיתים קרובות מהיר יותר לבדוק קודם את הנחות טכנולוגיית ה-UI.
flowchart TB
accTitle: ההתנגשות בין EXE אחד ל-package identity
accDescr: ב-WinUI 3, PublishSingleFile זמין רק ב-unpackaged+self-contained; unpackaged מאבד package identity, ואז התראות ו-file association לא זמינות, כך ש-EXE אחד ויכולות הרחבה של Windows מתנגשים.
m1["רוצים PublishSingleFile"] --> m2["רק unpackaged+self-contained"]
m2 --> m3["מאבדים package identity"]
m3 --> m4["התראות, association וכו' לא זמינים"]
m4 -.-> m5["EXE אחד ויכולות הרחבה מתנגשים"]
איור 13: ב-WinUI 3, בחירה ב-EXE אחד היא בחירה לוותר על package identity.
6. אזורים שבהם רישום ותלות הם הנושא עצמו
6.1 Shell extension
Shell extension שנטען ל-Explorer הוא לא “EXE ששמים ומריצים”. כאן הנושא הוא לא מספר קבצים, אלא איך רושמים ב-Explorer.
6.2 שירות Windows
אפשר לארוז את ה-exe של השירות עצמו לקובץ אחד, אבל ההפצה היא סיפור אחר.
- רישום ב-SCM
- הרשאות
- חשבון הפעלה
- הגדרות שחזור
כלומר שירות הוא אזור שבו מחדדים איך מתקינים, לא איך מגיעים ל-EXE אחד.
6.3 מנהל התקן
כאן זה עוד יותר ברור. זה נשלם רק עם INF, חתימה ותהליך התקנה, ולכן מלכתחילה קשה להיכנס לזירת single binary.
flowchart TB
accTitle: אזורים שבהם רישום וחתימה הם הנושא
accDescr: Shell extension הוא רישום ב-Explorer, שירות Windows הוא רישום ב-SCM עם הרשאות וחשבון הפעלה, ומנהל התקן נשלם עם INF וחתימה; לכן הנושא הוא תכנון רישום ותלות, לא מספר קבצים.
n1["Shell extension"] --> n2["רישום ב-Explorer"]
n3["שירות"] --> n4["רישום ב-SCM, הרשאות, חשבון"]
n5["מנהל התקן"] --> n6["INF וחתימה"]
n4 -.-> n7["הנושא הוא תכנון רישום, לא מספר קבצים"]
איור 14: בשלושת האזורים האלה מחדדים איך רושמים, לא איך מגיעים ל-EXE אחד.
7. טבלת החלטה מעשית
להחלטה גסה, הטבלה הזו שימושית.
| מה בונים | עד כמה EXE אחד ריאלי | מה לחשוב קודם |
|---|---|---|
| כלי Win32 / C++ שרץ לבד | גבוה | static linking, OS / arch יעד |
| כלי WinForms / WPF שרץ לבד | גבוה | התאמה ל-self-contained, single-file, Native AOT |
| אפליקציית WinUI 3 / Windows App SDK | בינוני | מצב הפצה, תלויות נוספות |
| UI שולחן עבודה מבוסס WebView2 | נמוך עד בינוני | שיטת הפצת Runtime |
| הרחבת קליק ימני או preview ב-Explorer | נמוך | רישום COM / Registry |
| שירות Windows | בינוני | רישום SCM, הרשאות, תהליך עדכון |
| אפליקציה עם מנהל התקן מצורף | נמוך | INF, חתימה, התקנה |
מה שחשוב בטבלה הוא להבין שמספר הבינאריים ותחום האחריות של ההפצה הם שני דברים שונים.
8. מה לקבע לפני תכנון ההפצה
אם רוצים ש-single binary יצליח, יש דברים להחליט לפני המימוש.
8.1 להחליט מה רוצים שיהיה “אחד”
- שהחבילה תהיה קובץ אחד
- לבטל התקנת runtime מראש
- לבטל צורך ב-installer
- להקל על עדכון offline
התשובה משנה את הטכנולוגיה שבוחרים.
8.2 לקבע בהתחלה Windows מינימלי ו-arch
גם single-file וגם Native AOT הם ביסודם ספציפיים ל-OS / architecture. אם ממשיכים עם “סתם קובץ אחד” בלי לקבע את זה, בסוף נתקעים על 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 בעדיפות, מורידים אינטגרציה למארח
זה עובד חזק.
- במקום Shell extension, EXE רגיל
- במקום שירות, Task Scheduler או הפעלה מפורשת
- במקום WebView2, UI native
- COM נשאר בתוך התהליך שלכם
בקיצור, ככל שמורידים עיצוב שבו ה-OS טוען אתכם או שאתם נרשמים אליו, מתקרבים ל-single binary.
flowchart TB
accTitle: ככל שמורידים אינטגרציה למארח, מתקרבים
accDescr: במקום Shell extension עוברים ל-EXE רגיל, במקום שירות להפעלה מפורשת או Task Scheduler, במקום WebView2 ל-UI native; ככל שמורידים רישום וטעינה מצד ה-OS, מתקרבים ל-single binary.
q1["במקום Shell extension, EXE רגיל"] --> q4["מורידים עיצוב שרושם ב-OS"]
q2["במקום שירות, הפעלה מפורשת"] --> q4
q3["במקום WebView2, UI native"] --> q4
q4 --> q5["מתקרבים ל-single binary"]
איור 16: אם single binary בעדיפות, מורידים את האינטגרציה למארח עצמה.
9. סיכום
single binary ב-Windows אפשרי עד רחוק. אבל הנקודה שמגיעים אליה היא המשפט הזה.
אפשר לארוז אפליקציה ל-EXE אחד. אי אפשר לארוז ל-EXE אחד גם את Windows שהאפליקציה תלויה בו.
חמש נקודות שכדאי לזכור:
- ל-EXE רגיל שרץ לבד אפשר להתקרב רחוק להפצה כקובץ אחד
- static linking ב-C/C++, single-file ב-.NET, ו-Native AOT הם כלים חזקים
- אבל תלות בגרסת OS, arch, DLL מערכת ומודל אבטחה לא נעלמת
- ב-Shell extension, שירות, מנהל התקן, WebView2 וחלק מ-WinUI 3, הסיפור הוא רישום ב-OS או runtime נוסף
- הצלחת single binary נקבעת לפי זה שמפרידים בהתחלה מה רוצים שיהיה “אחד”
אם single binary בעדיפות גבוהה, הרבה יותר קל להצליח אם כבר בבחירת הטכנולוגיה מורידים את רמת הצימוד ל-OS.
flowchart TB
accTitle: מפתח ההצלחה הוא רמת הצימוד ל-OS
accDescr: אפשר לארוז אפליקציה ל-EXE אחד, אבל אי אפשר לארוז גם את Windows שהיא תלויה בו; אם single binary בעדיפות גבוהה, מצליחים יותר כשמורידים צימוד ל-OS כבר בבחירת הטכנולוגיה.
r1["לארוז אפליקציה ל-EXE אחד"] --> r2["אפשר"]
r3["לארוז גם את Windows שהיא תלויה בו"] --> r4["אי אפשר"]
r4 -.-> r5["לכן בבחירת טכנולוגיה מורידים צימוד ל-OS"]
איור 17: הצלחת EXE אחד נקבעת בתכנון הצימוד ל-OS בהתחלה.
10. מקורות
- 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
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
Time Travel Debugging — להקליט ולהריץ אחורה באגים שלא משתחררים באפליקציות ארוכות-ריצה
באג פעם בחודש משאיר ב-crash dump רק את התוצאה. מקליטים ומריצים אחורה את הביצוע עם WinDbg Time Travel Debugging (TTD): TTD.exe, ring buffe...
למה arguments נשברים — כללי command-line arguments ב-Windows
Windows מעביר ל-CreateProcess מחרוזת אחת שהמקבל מפצל. מכסה את כללי CommandLineToArgvW, CRT ו-.NET, ArgumentList, ובניה ב-C++.
רשימת בדיקה לניהול בטוח של child processes באפליקציית Windows
בניהול בטוח של child processes באפליקציית Windows, בעלות על עץ התהליכים ותהליך הסיום חשובים יותר מבחירת API להפעלה. המאמר עובר על Job Obj...
סיום התמיכה ב-printer drivers של Windows — איך אפליקציות עסקיות מתכוננות להדפסת דוחות ותוויות
Microsoft מפסיקה בהדרגה printer drivers מסוג v3/v4. מה Windows protected print mode מסיר, ואיך עושים inventory ומכינים הדפסת דוחות ותוויו...
WinRT הוא COM — IInspectable, .winmd, language projections, ולמה WinUI עדיין יושב על חוזה בינארי
WinRT אינו managed runtime אלא ABI שנבנה על COM ועליו metadata של .winmd ו-language projections. מכסה IUnknown מול IInspectable, הגדרת HW...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
בהפצת אפליקציית Windows כדאי לתכנן מראש גם single-file, גם צירוף runtime, גם האם לאמץ WebView2 או WinUI, וגם האם להפוך לשירות. כך פחות חוזרים אחורה.
ייעוץ טכני וסקירת תכנון
בקשה כמו 'רוצים EXE אחד' קל יותר להכריע כשמפרידים בין יחידת ההפצה, תלות ב-OS, צורך ברישום, ואחריות על עדכונים.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- אפשר להפיץ אפליקציית Windows כקובץ EXE אחד בלבד?
- לכלי שולחן עבודה שרץ לבד, אפשר להגיע רחוק. static linking ב-C/C++, self-contained או single-file ב-.NET, ו-Native AOT מאפשרים לארוז את חבילת ההפצה ל-EXE אחד. אבל היכולת לארוז ל-EXE אחד והיעדר תלות ב-Windows של היעד הם שני דברים שונים. תלות בגרסת OS, בארכיטקטורה, ב-DLL של המערכת ובמודל האבטחה לא נעלמת.
- single-file ב-.NET מבטל את התלות ב-OS?
- לא. single-file מקטין בעיקר את מספר הקבצים בחבילה, לא את התלות ב-OS. framework-dependent תלוי ב-.NET שמותקן במכונת היעד, self-contained נושא את ה-runtime של .NET, ו-Native AOT יכול להקטין עוד תלות בזמן ההפעלה אבל עם מגבלות יכולת. גם single-file וגם Native AOT הם ביסודם ספציפיים ל-OS ולארכיטקטורה, ולכן מקבעים מראש את גרסת Windows המינימלית ואת ה-arch.
- אילו אפליקציות קשה לארוז ל-EXE אחד?
- Shell extensions, שירותי Windows, מנהלי התקן, UI מבוסס WebView2, וחלק מ-WinUI 3. אצל אלה הנושא הוא לא מספר הקבצים, אלא רישום ב-OS וטיפול ב-runtime נוסף. Shell extension דורש רישום ב-Explorer; שירות דורש תכנון של רישום ב-SCM, הרשאות וחשבון הפעלה; מנהל התקן נשלם רק עם INF וחתימה. לכן הפצה של 'רק לשים קובץ' לא עובדת. ב-WebView2 צריך להחליט קודם איך מפיצים את WebView2 Runtime.
- יש שיטת הפצה טובה יותר מלדחוף הכול לתוך EXE אחד?
- הפצת app-local, שבה שמים DLL ליד ה-EXE, היא אפשרות חזקה. גם בצורה של app.exe ועוד כמה DLL לידו, אם אפשר להפיץ ב-xcopy בלי installer ובלי הרשאות admin, לא נדיר שזה קל יותר לתחזוקה מדחיסה בכוח ל-EXE אחד. מה שחשוב הוא להפריד מראש: רוצים קובץ אחד, או לבטל התקנת runtime מראש, או לבטל צורך ב-installer.