היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 16 Mar 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173556)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). איך קוראים ל-DLL של .NET 8 מ-VBA עם טיפוסים: COM host ו-dscom. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173556 https://comcomponent.com/he/blog/dotnet8-dll-typed-vba-com-dscom-tlb/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22173556
- DOI (הגרסה הזו)
- 10.5281/zenodo.22173557
מצבים שבהם רוצים לקרוא מ-VBA לעיבוד של .NET 8 עדיין נפוצים. בפרט, כאשר רוצים לשמר את הנכסים הקיימים של Excel או Access כמות שהם, ולהעביר ל-C# רק את החלקים הכבדים — עיבוד מחרוזות, HTTP, הצפנה, לוגיקה עסקית.
אבל אם נשארים עם CreateObject ו-late binding, בצד ה-VBA הכול הופך ל-Object. ה-IntelliSense נחלש, טעויות הקלדה בשמות שיטות מתגלות רק בזמן ריצה, ובהדרגה שוקעים בקוד שמבוסס על מחרוזות.
flowchart TB
accTitle: הבעיה של late binding
accDescr: כשנשארים עם CreateObject ו-late binding, בצד ה-VBA הכול הופך ל-Object, ה-IntelliSense נחלש, וטעויות הקלדה בשם השיטה מתגלות רק בזמן ריצה.
lb1["CreateObject עם late binding"] --> lb2["בצד ה-VBA הכול הופך ל-Object"]
lb2 --> lb3["ה-IntelliSense נחלש"]
lb2 --> lb4["טעות הקלדה מתגלה רק בזמן ריצה"]
איור 1: ככל שנשארים עם late binding, שוקעים יותר בקוד שמבוסס על מחרוזות.
לכן המאמר הזה מתמקד רק בחשיפת DLL של .NET 8 כ-COM, יצירת type library (TLB) עם dscom, ושימוש בה מ-VBA עם early binding וטיפוסים.
הפעם נשאיר בצד את הסיפור הישן של .NET Framework + RegAsm, כתיבת IDL ידנית וקימוע עם MIDL, ואת נושא ה-Registration-Free COM. כאן נעסוק רק במסלול האחד של .NET 8 / COM host / dscom / early binding ב-VBA.
הקוד שמופיע במאמר הזה זמין כערכת דוגמאות מלאה (ספריית חשיפת COM, סקריפטים ליצירה ורישום של TLB, מודול VBA, unit tests) שפרסמנו ב-GitHub וניתן לבנות ולבדוק.
dotnet8-dll-typed-vba-com-dscom-tlb - komurasoft-blog-samples (GitHub)
הסביבה הנדרשת
| פריט | מה נדרש |
|---|---|
| מערכת הפעלה | Windows. מכיוון שמבצעים רישום COM, צריך שיהיה אפשר להריץ את regsvr32 בהרשאת Administrator |
| .NET SDK | .NET 8 SDK. EnableComHosting הוא פיצ’ר מ-.NET 5 ואילך |
| Office | Excel או Access. קודם כל בודקים אם מדובר בגרסת 32-bit או 64-bit (פרק 3) |
| כלי ליצירת TLB | dscom. אופן ההשגה שונה בין 64-bit ל-32-bit (פרק 6) |
| מחשב הלקוח | runtime של .NET 8 באותו bitness כמו Office (פרק 9) |
לפני שמתחילים בשלבים, מומלץ מאוד לתעד את גרסאות הסביבה שלכם. אחר כך, כשמתגלה “אותם שלבים אבל לא עובד”, המידע הזה הוא הבסיס היחיד להשוואה.
# רשימת ה-.NET SDK וה-runtime (גם אפשר לראות אם מותקן x64 או x86)
dotnet --info
# מספר הבנייה של Windows
winver
את גרסת Office ואת ה-bit שלו אפשר לבדוק ב-Excel דרך קובץ > חשבון > אודות Excel. בסוף שורת הכותרת של הדיאלוג מופיע 32 סיביות או 64 סיביות.
1. המסקנה בקצרה
אם מסדרים רק את המסקנה, הזרימה נראית כך:
- בונים את ה-class library של .NET 8 עם
EnableComHosting=true - יוצרים interface מפורש ו-class שנחשפים ל-COM
- הופכים את ה-class ל-
ClassInterfaceType.None, בלי לברוח אלAutoDual - הופכים את ה-interface שבו VBA משתמש ל-
InterfaceIsDual - מתוך ה-
*.dllשנוצר אחרי הבנייה, יוצרים*.tlbעםdscom tlbexport - רושמים את
*.comhost.dllעםregsvr32 - רושמים את
*.tlbעםdscom tlbregister - מוסיפים reference ב-VBA, ומשתמשים עם טיפוסים בצורה
Dim x As שם_הספרייה.IYourInterface
בקיצור, ההרכב הוא: הכניסה ל-COM היא *.comhost.dll שיוצר .NET SDK, מידע הטיפוסים הוא *.tlb שיוצר dscom, ו-VBA מבצע early binding לפי אותו TLB.
flowchart TB
accTitle: המסלול האחד עד לשימוש עם טיפוסים
accDescr: הזרימה: בונים עם EnableComHosting, יוצרים TLB עם dscom tlbexport, רושמים את ה-comhost עם regsvr32, רושמים את ה-TLB עם dscom tlbregister, ואז משתמשים עם טיפוסים מה-reference ב-VBA.
st1["בנייה עם EnableComHosting"] --> st2["יצירת TLB עם dscom tlbexport"]
st2 --> st3["רישום ה-comhost עם regsvr32"]
st3 --> st4["רישום ה-TLB עם dscom tlbregister"]
st4 --> st5["reference ב-VBA ושימוש עם טיפוסים"]
איור 2: אם מתקדמים בסדר של בנייה, יצירת TLB, שני רישומים, ו-reference — אפשר לקרוא עם טיפוסים.
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 27, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
2. מבט כללי על ההרכב
קודם כל, נראה בתמונה אחת מה תפקידו של מה.
flowchart LR
accTitle: מבנה החיבור בין VBA ל-.NET 8
accDescr: VBA מקבל מידע טיפוסים מה-TLB שהוגדר כ-reference, קורא דרך ה-comhost לגוף המימוש של .NET 8, וזה רץ על ה-runtime של .NET 8.
VBA["VBA / Excel / Access"] -->|"מקבל מידע טיפוסים מה-TLB שהוגדר כ-reference"| TLB["VbaTypedComSample.tlb"]
VBA -->|"קריאת COM"| COMHOST["VbaTypedComSample.comhost.dll"]
COMHOST --> DOTNET["VbaTypedComSample.dll (.NET 8)"]
DOTNET --> RUNTIME[".NET 8 Runtime"]
איור 3: VBA מקבל מידע טיפוסים מה-TLB, וקורא דרך ה-comhost לגוף המימוש של .NET 8.
התפקיד של כל אחד הוא:
| קובץ | תפקיד |
|---|---|
VbaTypedComSample.dll |
גוף המימוש של .NET 8 |
VbaTypedComSample.comhost.dll |
הכניסה שנקראת מ-COM |
VbaTypedComSample.tlb |
מידע הטיפוסים שרואה VBA |
VbaTypedComSample.deps.json |
מידע לפתרון תלויות |
VbaTypedComSample.runtimeconfig.json |
מידע להפעלת ה-runtime של .NET |
הנקודה החשובה כאן: מה ש-VBA צריך כדי לדעת את הטיפוס הוא ה-TLB, ו-מה שנדרש ככניסה להפעלת COM הוא ה-comhost.
זה שאי אפשר פשוט להעביר .dll בודד ולסיים בזה, הוא החלק הלא-אינטואיטיבי של עולם ה-COM.
3. הדבר הראשון שקובעים — התאמת 32-bit / 64-bit
אם מפספסים את זה, יש סיכוי גבוה מאוד להגיע ל-ActiveX component can't create object..
חשוב להתאים את ה-bitness בין Office/VBA לבין שרת ה-COM.
| צד השימוש | קנה מידה בצד .NET | יצירת TLB | פקודת רישום |
|---|---|---|---|
| Office בגרסת 64-bit | x64 / win-x64 |
dscom |
C:\Windows\System32\regsvr32.exe |
| Office בגרסת 32-bit (על Windows 64-bit) | x86 / win-x86 |
dscom32.exe |
C:\Windows\SysWOW64\regsvr32.exe |
ב-COM host של .NET 5+, השארת AnyCPU נוטה לגרום ל-*.comhost.dll להיווצר בצד 64-bit, מה שעלול לא להתאים ל-Office בגרסת 32-bit. לכן, בטוח יותר לציין x86 / x64 באופן מפורש בהתאם ל-Office.
flowchart TB
accTitle: אי-ההתאמה שנגרמת מהשארת AnyCPU
accDescr: השארת AnyCPU גורמת ל-comhost להיווצר בדרך כלל בצד 64-bit, מה שעלול לא להתאים ל-Office בגרסת 32-bit, ולכן בטוח יותר לציין x86 או x64 באופן מפורש בהתאם ל-Office.
b1["נשארים עם AnyCPU"] --> b2["comhost נוטה להיווצר ב-64-bit"]
b2 --> b3["לא מתאים ל-Office בגרסת 32-bit"]
b3 -.-> b4["מציינים bit בהתאם ל-Office"]
איור 4: פער ב-bitness הוא קיצור דרך ישיר להודעת “can’t create object”.
הקוד במאמר הזה יתמקד ב-Office בגרסת 64-bit. עבור Office בגרסת 32-bit, יש להחליף בהמשך x64 ב-x86 ו-win-x64 ב-win-x86.
4. בונים את הצד של .NET 8
כאן ניצור דוגמה מינימלית שמאפשרת ל-VBA לקרוא ל-Add, Divide ו-Hello.
4.1 .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableComHosting>true</EnableComHosting>
<PlatformTarget>x64</PlatformTarget>
<NETCoreSdkRuntimeIdentifier>win-x64</NETCoreSdkRuntimeIdentifier>
</PropertyGroup>
</Project>
הנקודה החשובה היא EnableComHosting. הוספת האפשרות הזו גורמת ליצירת VbaTypedComSample.comhost.dll בזמן הבנייה.
4.2 ברירת המחדל היא שכל ה-assembly לא נחשף ל-COM
מכיוון שרוצים לחשוף ל-COM רק את הטיפוסים הנחוצים, נוח להשאיר את כל ה-assembly כ-false ולסמן ComVisible(true) רק על מה שרוצים לחשוף.
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
4.3 כותבים את ה-interface וה-class שנחשפים
using System.Runtime.InteropServices;
namespace VbaTypedComSample;
[ComVisible(true)]
[Guid("2A1BBEDE-DE6E-4C34-AD60-2E9E0E33E999")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICalculator
{
[DispId(1)]
int Add(int x, int y);
[DispId(2)]
double Divide(double x, double y);
[DispId(3)]
string Hello(string name);
}
[ComVisible(true)]
[Guid("FAD1C752-0BB6-4DDD-889F-FE446350847A")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ICalculator))]
public class Calculator : ICalculator
{
public Calculator()
{
}
public int Add(int x, int y) => checked(x + y);
public double Divide(double x, double y)
{
if (y == 0)
{
throw new ArgumentOutOfRangeException(nameof(y), "0 では割れません。");
}
return x / y;
}
public string Hello(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return "Hello";
}
return $"Hello, {name}";
}
}
הנקודות שכדאי לשים לב אליהן בקוד הזה הן אלה.
- מקצים
Guidבנפרד ל-interface ול-class - הופכים ל-
ClassInterfaceType.None, כלומר לא מסתמכים על class interface שנוצר אוטומטית - הופכים ל-
InterfaceIsDualכדי שיהיה נוח לעבוד ב-VBA - הקצאת
DispIdמפחיתה תקלות כשמשנים בעתיד את סדר השיטות - מכיוון ש-COM יוצר את האובייקט עם
New, יש להכין constructor ציבורי בלי ארגומנטים
flowchart TB
accTitle: איך בונים את הטיפוס שנחשף
accDescr: ה-interface המפורש ICalculator מסומן ב-InterfaceIsDual וב-DispId, ה-class Calculator ממומש עם ClassInterfaceType.None, וה-Guid מוקצה בנפרד ל-interface ול-class.
if1["ICalculator (interface מפורש)"] --> d1["InterfaceIsDual ו-DispId"]
cl1["Calculator (class)"] -->|"מממש"| if1
cl1 --> d2["ClassInterfaceType.None"]
if1 -.-> g1["ה-Guid מוקצה בנפרד לכל אחד"]
cl1 -.-> g1
איור 5: עמוד השדרה של חשיפה עם טיפוסים הוא שילוב ה-interface המפורש עם ה-class שמסומן ב-None.
5. בונים
בונים גרסת Release.
dotnet build -c Release
אחרי הבנייה, בתיקיית הפלט אמורים להיות לפחות הקבצים האלה.
bin/
Release/
net8.0-windows/
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
זו התיקייה שמשמשת להפצה ולרישום. אם משנים בהמשך את מיקום ההתקנה, צריך לבצע רישום מחדש.
6. יוצרים TLB עם dscom
6.1 מה זה dscom
dscom הוא כלי שורת פקודה בקוד פתוח, ליצירה ולרישום של type libraries (TLB) של COM מתוך assembly של .NET. הוא מפורסם על ידי חברת dSPACE, ורישיונו Apache-2.0.
הסיבה שהוא נדרש היא שהחל מ-.NET 5, tlbexp.exe ו-RegAsm.exe הוצאו משימוש. בתקופת .NET Framework, שני הכלים האלה יכלו ליצור TLB ולרשום assembly, אבל ב-.NET 5+ אין להם ממשיך שמובנה בברירת המחדל. dscom נבנה כדי למלא את החלל הזה.
flowchart TB
accTitle: החלל ש-dscom ממלא
accDescr: בתקופת .NET Framework יצירת TLB ורישום assembly נעשו עם tlbexp.exe ו-RegAsm.exe, אבל מ-.NET 5 ואילך שניהם הוצאו משימוש בלי ממשיך מובנה, ולכן dscom ממלא את החלל הזה.
old1["תקופת .NET Framework"] --> old2["tlbexp.exe ו-RegAsm.exe"]
new1[".NET 5 ואילך"] --> new2["שניהם הוצאו משימוש, אין ממשיך"]
new2 --> ds1["dscom ממלא את החלל"]
איור 6: החל מ-.NET 5, הכלי ליצירת TLB הוחלף ב-dscom.
הפקודות המשניות העיקריות שמספיק לזכור הן אלה בלבד.
| פקודה משנית | תפקיד |
|---|---|
tlbexport |
כותב TLB מתוך assembly |
tlbregister |
רושם TLB במערכת |
tlbunregister |
מבטל רישום TLB |
tlbdump |
מציג את תוכן ה-TLB לבדיקה |
tlbembed |
משבץ TLB לתוך קובץ |
tlbdump שימושי כדי לוודא, לפני פתיחת VBA, שה-TLB שנוצר מכיל את הטיפוסים שהתכוונתם אליהם.
6.2 עבור 64-bit
אם רוצים רק ליצור TLB בגרסת 64-bit, מספיק להתקין עם dotnet tool.
dotnet tool install --global dscom
לאחר מכן, יוצרים TLB מה-assembly שנבנה.
dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
6.3 עבור Office בגרסת 32-bit — מאיפה משיגים את dscom32.exe
זו הנקודה שהכי קל להיתקע בה כשתומכים ב-Office בגרסת 32-bit.
ה-dscom שמתקבל דרך dotnet tool install יודע לעבוד רק עם assembly מסוג AnyCPU או 64-bit, ויכול ליצור רק TLB בגרסת 64-bit. כדי ליצור TLB בגרסת 32-bit, נדרש קובץ הרצה נפרד בשם dscom32.exe, שלא מתקבל דרך NuGet אלא מורידים אותו מדף ה-releases ב-GitHub.
- מקור ההורדה: https://github.com/dspace-group/dscom/releases
dscom.exe— יוצר TLB בגרסת 64-bit מ-assembly מסוג AnyCPU או 64-bitdscom32.exe— יוצר TLB בגרסת 32-bit מ-assembly מסוג AnyCPU או 32-bit
בדוגמה הזו, ה-dscom32.exe שהורדנו ממוקם בתיקיית tools ישירות מתחת לפרויקט. מיקום האחסון גמיש, אבל אין להפיץ אותו יחד עם פלט הבנייה. זהו כלי לזמן פיתוח, ולא נחוץ בזמן ריצה.
יש עוד הנחת יסוד שקל לפספס. כדי להריץ את dscom32.exe, צריך שיהיה מותקן runtime של .NET בגרסת x86. הסיבה היא ש-dscom טוען את hostfxr.dll, ובסביבה שבה מותקן רק x64 הוא לא יעבוד. בדקו ברשימה שמופיעה בפלט של dotnet --info אם יש runtime של x86.
flowchart TB
accTitle: ההכנה ליצירת TLB בגרסת 32-bit
accDescr: ה-dscom שמותקן דרך dotnet tool יוצר רק TLB בגרסת 64-bit, ולכן ל-32-bit צריך dscom32.exe מדף ה-releases ב-GitHub, וגם runtime בגרסת x86 של .NET כדי להריץ אותו.
p1["צריך TLB בגרסת 32-bit"] --> p2["השגת dscom32.exe מדף ה-releases"]
p2 --> p3["בדיקה שקיים runtime בגרסת x86"]
p3 --> p4["tlbexport עם dscom32.exe"]
p1 -.-> p5["הגרסה ב-dotnet tool מיועדת רק ל-64-bit"]
איור 7: התמיכה ב-32-bit שונה כבר מנתיב השגת הכלי, ולכן קל להיתקע כאן.
.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
יש לציין שגם התיעוד של dscom עצמו ממליץ, שאם רוצים לתמוך ב-32-bit, מומלץ לקמפל את ה-assembly עצמו בגרסת 32-bit, מכיוון שהשארתו כ-AnyCPU גורמת ל-*.comhost.dll להיווצר כ-64-bit. זו אותה מסקנה כמו בפרק 3.
אם מציק לבצע את זה ידנית בכל בנייה, אפשר להוסיף את החבילה dSPACE.Runtime.InteropServices.BuildTasks, שמאפשרת יצירה אוטומטית של TLB בזמן קומפילציה.
7. רושמים את ה-COM host ואת ה-TLB
יש לבצע את זה מ-Command Prompt / PowerShell בהרשאת Administrator.
7.1 עבור Office ו-COM בגרסת 64-bit
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\System32\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
dscom tlbregister "$out\VbaTypedComSample.tlb"
7.2 עבור Office בגרסת 32-bit (על Windows 64-bit)
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\SysWOW64\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
.\tools\dscom32.exe tlbregister "$out\VbaTypedComSample.tlb"
יש כאן שני דברים שמתבצעים.
regsvr32רושם את*.comhost.dllכשרת COMtlbregisterרושם את*.tlbכ-type library
flowchart TB
accTitle: הרישום מורכב משני חלקים
accDescr: בהרשאת Administrator, regsvr32 רושם את ה-comhost כשרת COM, ו-dscom tlbregister רושם את ה-TLB כ-type library, ושני הרישומים האלה שונים במהותם.
adm["מריצים בהרשאת Administrator"] --> r1["regsvr32 רושם את ה-comhost"]
adm --> r2["tlbregister רושם את ה-TLB"]
r1 -.-> m1["רישום הכניסה להפעלת COM"]
r2 -.-> m2["רישום מידע הטיפוסים שרואה VBA"]
איור 8: הכניסה להפעלה ומידע הטיפוסים הם שני דברים שונים, ולכן הרישום מורכב משני חלקים.
8. מגדירים reference ב-VBA ומשתמשים עם טיפוסים
- פותחים Excel או Access
- פותחים את עורך ה-VBA (VBE) עם
Alt+F11. אם פותחים מהרצועה (ribbon), זה תחתפיתוח>Visual Basic. אם הכרטיסייהפיתוחלא מוצגת, מסמנים אותה תחתקובץ>אפשרויות>התאמה אישית של הרצועה - בתפריט ה-VBE:
כלים>הפניות - הרשימה
ספריות זמינות להפניהמסודרת אלפביתית. אם הרישום הצליח, שם הספרייה (בברירת מחדל זהה לשם ה-assembly,VbaTypedComSample) יופיע ברשימה; מסמנים את תיבת הסימון שמשמאל ולוחציםאישור - אם הספרייה לא מופיעה ברשימה, לוחצים על הכפתור
עיון...ובוחרים ישירות אתVbaTypedComSample.tlb
אם הספרייה לא מופיעה ברשימה, הסיבה היא בדרך כלל אחת משתי אלה — אי-התאמת bitness מפרק 3, או ש-tlbregister מפרק 7 לא הצליח. מ-Office בגרסת 32-bit לא רואים TLB שנרשם בגרסת 64-bit.
flowchart TB
accTitle: איך מזהים מדוע הספרייה לא מופיעה בהגדרת ה-reference
accDescr: כאשר ספרייה לא מופיעה ברשימת ה-references, הגורם הוא בדרך כלל אי-התאמת bitness מפרק 3 או כישלון tlbregister מפרק 7, ומ-Office בגרסת 32-bit לא רואים TLB שנרשם בגרסת 64-bit.
q1["הספרייה לא מופיעה ברשימה"] --> a1["חושדים באי-התאמת bitness (פרק 3)"]
q1 --> a2["חושדים בכישלון tlbregister (פרק 7)"]
a1 -.-> nt1["מ-32-bit לא רואים TLB של 64-bit"]
איור 9: כשספרייה לא מופיעה ברשימה, כמעט תמיד אפשר לצמצם לאחת משתי הסיבות — bitness או רישום.
אפשר לבדוק אם הגדרת ה-reference הצליחה על ידי פתיחת תצוגה > דפדפן אובייקטים (F2), ובחירת VbaTypedComSample מרשימת בחירת הספרייה בפינה השמאלית העליונה. אם רואים שם את ICalculator ו-Calculator, וכן את Add / Divide / Hello, סימן שה-TLB נוצר כראוי.
Option Explicit
Public Sub UseCalculator()
Dim calc As VbaTypedComSample.ICalculator
Set calc = New VbaTypedComSample.Calculator
Debug.Print calc.Add(10, 20)
Debug.Print calc.Divide(10, 4)
Debug.Print calc.Hello("VBA")
End Sub
אם ממקמים את הסמן בפרוצדורה הזו ומריצים עם F5, ופותחים את חלון ה-Immediate עם Ctrl + G, יופיעו 3 השורות בהתאם למימוש בפרק 4.
30
2.5
Hello, VBA
אם התוצאה תואמת את הצפוי, סימן ש-הגדרת ה-reference, רישום ה-COM, הפעלת ה-runtime, ו-marshaling של הארגומנטים והערך המוחזר — הכול עובד. לעומת זאת, אם הערך לא תואם, כדאי לחשוד במימוש בצד .NET, ואם לא ניתן להריץ בכלל, כדאי לחשוד בפרקים 3 ו-7.
flowchart TB
accTitle: איתור תקלה לפי תוצאת ההרצה
accDescr: אם תוצאת דוגמת ה-VBA תואמת את הצפוי, כל המסלול עובד; אם הערך לא תואם, חושדים במימוש בצד .NET; ואם לא ניתן להריץ בכלל, חושדים ב-bitness מפרק 3 וברישום מפרק 7.
r1["מריצים את דוגמת ה-VBA"] --> q1{"מה התוצאה?"}
q1 -->|"כצפוי"| ok1["כל המסלול עובד"]
q1 -->|"הערך לא תואם"| ng1["חושדים במימוש בצד .NET"]
q1 -->|"לא ניתן להריץ"| ng2["חושדים ב-bitness וברישום"]
איור 10: מתוך 3 שורות פלט בלבד, אפשר לדעת באיזו שכבה לחשוד.
עם זה, בצד ה-VBA מתקבלים היתרונות הבאים.
- ה-IntelliSense עובד
- קל יותר לגלות טעויות הקלדה בשמות שיטות עוד לפני ההרצה
- אפשר לבדוק את ה-API החשוף דרך ה-Object Browser
- קריא יותר מכתיבה גולמית עם
Object
8.1 exception הופך בצד ה-VBA לשגיאת COM
לדוגמה, אם בצד .NET נזרק exception כמו ב-Divide(10, 0), בצד ה-VBA היא נראית כשגיאת COM.
Option Explicit
Public Sub UseCalculatorWithErrorHandling()
On Error GoTo EH
Dim calc As VbaTypedComSample.ICalculator
Set calc = New VbaTypedComSample.Calculator
Debug.Print calc.Divide(10, 0)
Exit Sub
EH:
Debug.Print Err.Number
Debug.Print Hex$(Err.Number)
Debug.Print Err.Description
End Sub
כדאי להבין איך לקרוא את הערכים שמתקבלים כאן, זה מזרז את איתור התקלה.
| פריט | מה נכנס |
|---|---|
Err.Number |
ה-HRESULT התואם ל-exception של .NET, כ-Long עם סימן. קשה לקרוא אותו במספר עשרוני, לכן ממירים ל-hex עם Hex$(Err.Number) |
Err.Description |
דרך IErrorInfo של COM, נכנסת הודעת ה-exception של .NET כמות שהיא. בקוד למעלה, זו מחרוזת שמכילה את 0 では割れません。 |
ערך ה-HRESULT קבוע לכל סוג exception. עבור ArgumentOutOfRangeException, הערך התואם הוא COR_E_ARGUMENTOUTOFRANGE, בערך 0x80131502. כלומר, אם Hex$(Err.Number) שווה ל-80131502, סימן ש-כפי שציפינו, הגיע ArgumentOutOfRangeException מצד .NET.
הנפוצים ביותר מרוכזים כאן.
| exception של .NET | קבוע HRESULT | ערך |
|---|---|---|
ArgumentException |
COR_E_ARGUMENT |
0x80070057 |
ArgumentOutOfRangeException |
COR_E_ARGUMENTOUTOFRANGE |
0x80131502 |
InvalidOperationException |
COR_E_INVALIDOPERATION |
0x80131509 |
NotSupportedException |
COR_E_NOTSUPPORTED |
0x80131515 |
| exceptions כלליים אחרים | COR_E_EXCEPTION |
0x80131500 |
אם רוצים בצד ה-VBA לפצל את הטיפול לפי סוג ה-exception, זה נעשה על ידי הסתעפות לפי ה-HRESULT הזה. עם זאת, תכנון שמסתעף לפי HRESULT רגיש לשינויים בטיפוס ה-exception בצד .NET, ולכן עדיף שכישלון עסקי יחזור כערך מוחזר או קוד שגיאה, לא כ-exception — זה יציב יותר כגבול.
flowchart TB
accTitle: איך exception של .NET מגיע ל-VBA
accDescr: exception שנזרק בצד .NET הופך ב-COM boundary ל-HRESULT, ובצד ה-VBA נכנס ה-HRESULT הזה ל-Err.Number כ-Long עם סימן, וההודעה מ-IErrorInfo נכנסת ל-Err.Description.
x1["exception נזרק בצד .NET"] --> x2["הופך ל-HRESULT בגבול ה-COM"]
x2 --> x3["נכנס עם סימן ל-Err.Number"]
x2 --> x4["ההודעה נכנסת ל-Err.Description"]
x3 -.-> h1["ממירים ל-hex עם Hex$ כדי לקרוא"]
איור 11: ה-exception משנה צורה ל-HRESULT בגבול ה-COM, ומגיע ל-Err של VBA.
9. איך חושבים על ההפצה
בזמן ההפצה, חשוב להעביר את כל קבוצת קבצי הפלט, ולא DLL בודד.
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(ואם נדרש, גם כל ה-DLLs התלויים)
בנוסף, במחשב הלקוח נדרש runtime מתאים של .NET 8. ה-COM host לא מופץ כ-self-contained, אלא בדרך כלל בתפעול framework-dependent.
אלה הפרטים המדויקים:
- דף ההורדה הוא הורדות .NET 8
- לא צריך את ה-SDK, אלא את ה-runtime בלבד. הדוגמה במאמר הזה היא class library בלי מסך, ולכן
.NET Runtimeמספיק. אם משתמשים בטיפוסי WPF או Windows Forms, נדרש.NET Desktop Runtime - ה-bitness צריך להתאים ל-Office. עבור Office בגרסת 64-bit — x64, עבור 32-bit — x86. מאותה סיבה שבפרק 3 ציינו במפורש
x64/x86, גם כאן חוסר התאמה ימנע הפעלה - אפשר לבדוק אם ה-runtime כבר מותקן על ידי הרצת
dotnet --list-runtimesבמחשב הלקוח, ובדיקה אם קיימת שורהMicrosoft.NETCore.App 8.x
חשוב לכתוב במסמכי ההפצה את “סוג ה-runtime הנדרש, הגרסה וה-bitness”. אם שוכחים לציין את זה בהוראות ההתקנה, יבזבזו זמן במקום כשיראו ActiveX component can't create object. ויתחילו לחשוד ב-bitness.
flowchart TB
accTitle: מה צריך לוודא בזמן ההפצה
accDescr: ההפצה כוללת את כל קבוצת קבצי הפלט ולא DLL בודד, במחשב הלקוח מתקינים runtime של .NET 8 באותו bitness כמו Office, ובמסמכי ההפצה כותבים את סוג ה-runtime, הגרסה וה-bitness.
h1["ממקמים את כל קבוצת הקבצים"] --> u1["פועל אצל הלקוח"]
h2["runtime של .NET 8 באותו bitness"] --> u1
h3["מציינים במסמכי ההפצה את פרטי ה-runtime"] -.-> u1
איור 12: DLL בודד לא יעבוד — צריך את קבוצת הקבצים ואת ה-runtime יחד.
10. מוקשים נפוצים
10.1 לא להשאיר AnyCPU כמו שהוא
אם ה-bitness של VBA/Office לא תואם ל-bitness של ה-COM host, מתקבלים כשלים לא נעימים במיוחד.
- Office בגרסת 64-bit —
x64/win-x64 - Office בגרסת 32-bit —
x86/win-x86
10.2 לא להשתמש ב-ClassInterfaceType.AutoDual
נראה נוח לכאורה, אבל קל לשבור אותו אם משנים את סדר החברים או ההרכב אחרי הפרסום.
אם רוצים שימוש יציב וטיפוסי מ-VBA, הדרך המקובלת היא להגדיר interface מפורש ולהפוך את ה-class ל-ClassInterfaceType.None.
10.3 לא ליצור GUID מחדש בפזיזות
ב-COM ה-GUID הוא החוזה עצמו. אם מחליפים IID או CLSID בפזיזות אחרי הפרסום, זה שובר references ורישומים קיימים ב-VBA.
10.4 לא לשבור interface שכבר פורסם
ב-COM, גם “רק הוספתי שיטה אחת” עלולה שלא לעבור בשלום.
- משאירים את
ICalculator - אם השינוי גדול, יוצרים
ICalculator2חדש - ה-class יכול לממש את שניהם
flowchart TB
accTitle: איך שומרים על interface שכבר פורסם
accDescr: interface שכבר פורסם, ICalculator, נשאר כמות שהוא, ואם רוצים שינוי גדול יוצרים ICalculator2 חדש, כאשר ה-class יכול לממש את שניהם.
k1["ICalculator שכבר פורסם"] --> k2["נשאר כמות שהוא"]
k3["רוצים שינוי גדול"] --> k4["יוצרים ICalculator2 חדש"]
k2 --> k5["ה-class יכול לממש את שניהם"]
k4 --> k5
איור 13: משאירים את החוזה הקיים כמות שהוא, ומוסיפים חוזה חדש לצדו — כך עובדים ב-COM.
10.5 בוחרים טיפוסים פשוטים
בגבול שנחשף ל-VBA, עדיף לא להתחכם.
הטיפוסים הבטוחים ביותר להתחיל בהם הם אלה.
intdoubleboolstringDateTimedecimalenum
10.6 לא לעדכן כשה-Office פתוח
לפעמים Excel או Access ממשיכים להחזיק את ה-DLL, וזה יוצר בעיות בבנייה או ברישום מחדש.
- סוגרים את ה-Office
- מבטלים רישום אם צריך
- בונים מחדש
- רושמים שוב
ביטול הרישום נעשה בסדר הפוך מהרישום, ועם אותה פקודה באותו bitness ששימשה לרישום. גם כאן נדרשת הרשאת Administrator, כמו ברישום.
# עבור Office ו-COM בגרסת 64-bit
$out = Resolve-Path .\bin\Release\net8.0-windows
dscom tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\System32\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"
# עבור Office בגרסת 32-bit (על Windows 64-bit)
$out = Resolve-Path .\bin\Release\net8.0-windows
.\tools\dscom32.exe tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\SysWOW64\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"
האופציה /u של regsvr32 היא ביטול הרישום. אם משתמשים ב-regsvr32 שונה מזה שבו רשמו, לא ניתן לבטל את הרישום (רישום שנעשה ב-64-bit לא ניתן לביטול דרך regsvr32 של SysWOW64). אם מעבירים או מוחקים את התיקייה כולה בלי לבטל את הרישום קודם, נשארת ברישום נתיב שכבר לא קיים.
flowchart TB
accTitle: איך מקפלים את הרישום לפני ואחרי עדכון
accDescr: בזמן עדכון סוגרים את Office, ואם צריך מבטלים רישום בסדר הפוך ובאותו bitness של הרישום המקורי, ואז בונים מחדש ורושמים שוב.
u1["סוגרים את Office"] --> u2["מבטלים רישום בסדר הפוך אם צריך"]
u2 --> u3["בונים מחדש"]
u3 --> u4["רושמים שוב"]
u2 -.-> u5["עם אותה פקודת bitness שבה נרשם"]
איור 14: כדי להימנע מ-DLL תפוס ומרישום שנשאר תלוי, מבצעים ביטול רישום ורישום מחדש כזוג.
11. סיכום
הנושא של “שימוש ב-DLL של .NET 8 מ-VBA עם טיפוסים” הופך להליך לא כל כך מפחיד אם מצמצמים אותו ל-חשיפת COM + יצירת TLB עם dscom. בצד .NET 8, מגדירים EnableComHosting=true ומכינים interface מפורש (ה-class — ClassInterfaceType.None, ה-interface שנועד ל-VBA — InterfaceIsDual), יוצרים TLB עם dscom tlbexport, רושמים את *.comhost.dll עם regsvr32 ואת *.tlb עם dscom tlbregister. אחר כך רק צריך להגדיר reference ב-VBA ולבצע early binding.
כשמתלבטים, הטריק הוא לחשוב בנפרד על ה-COM host ועל ה-TLB.
- הכניסה להפעלה היא
*.comhost.dll - מידע הטיפוסים הוא
*.tlb - גוף המימוש הוא
*.dll
12. מקורות
- ערכת הדוגמאות המלאה של המאמר הזה (ספריית חשיפת COM, סקריפטים, VBA, בדיקות) - komurasoft-blog-samples (GitHub)
- Expose .NET components to COM - Microsoft Learn
- COM 相互運用のために .NET 型を修飾する - Microsoft Learn
- ComInterfaceType 列挙型 - Microsoft Learn
- ClassInterfaceType 列挙型 - Microsoft Learn
- COM 呼び出し可能ラッパー - Microsoft Learn
- DispIdAttribute クラス - Microsoft Learn
- dscom - NuGet Gallery
- dspace-group/dscom - GitHub (גוף הכלי dscom. רשימת פקודות משנה והסבר על תמיכה ב-32-bit)
- דף ה-releases של dscom (מקור ההורדה של
dscom32.exe) - HRESULT と例外を対応付ける方法 - Microsoft Learn
- How to use the Regsvr32 tool and troubleshoot Regsvr32 error messages - Microsoft Support
- .NET 8 downloads
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
מה זה OLE object? — איך embedding ו-linking עובדים, והמלכודות במסמכים עסקיים
OLE object הוא מה שמטמיע טבלת Excel ב-Word. לומדים embedding מול linking, compound files, In-Place Activation, קישורים שבורים, ניפוח ואבטחה.
איך בונים פלט דוחות Excel: COM, Open XML ו-template
העיצוב של פלט דוחות Excel משתנה מאוד לפי השאלה אם מריצים את Excel באוטומציה, בונים xlsx ישירות, או משאירים VBA קיים. על בסיס אפליקציות Wi...
למה arguments נשברים — כללי command-line arguments ב-Windows
Windows מעביר ל-CreateProcess מחרוזת אחת שהמקבל מפצל. מכסה את כללי CommandLineToArgvW, CRT ו-.NET, ArgumentList, ובניה ב-C++.
WinRT הוא COM — IInspectable, .winmd, language projections, ולמה WinUI עדיין יושב על חוזה בינארי
WinRT אינו managed runtime אלא ABI שנבנה על COM ועליו metadata של .winmd ו-language projections. מכסה IUnknown מול IInspectable, הגדרת HW...
מה נשאר אחרי שה-parent מת — מחזיקים child processes ב-Job Object
למה SDK helpers שורדים UI שנהרג ומחזיקים את המצלמה או את ה-COM port? מתכננים משך חיים של child process עם Job Objects, KillOnJobClose ו-c...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
תכנון ממשק החיבור שכולל VBA, COM, Office, .NET 8 ויצירת type library קשור קשר הדוק לפיתוח אפליקציות Windows.
ייעוץ טכני וסקירת תכנון
אם רוצים ליישר קו בין נכסי VBA קיימים ל-.NET 8, כולל bitness, רישום, יצירת TLB ואופן ההפצה, זה נושא שקל להתקדם בו כייעוץ טכני ו-design review.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה צריך כדי להשתמש ב-DLL של .NET 8 מ-VBA עם טיפוסים (early binding)?
- בונים את ה-class library של .NET 8 עם EnableComHosting=true כדי ליצור *.comhost.dll, ואז יוצרים *.tlb עם dscom tlbexport. אחר כך רושמים את *.comhost.dll עם regsvr32 ואת *.tlb עם dscom tlbregister, ומוסיפים את ה-TLB כ-reference ב-VBA. כך אפשר להשתמש בצורה Dim x As שם_הספרייה.IYourInterface עם טיפוסים. חלוקת התפקידים: *.comhost.dll הוא הכניסה שממנה מפעילים את COM, *.tlb הוא מידע הטיפוסים שרואה VBA, ו-*.dll הוא גוף המימוש.
- מה הגורם להודעה 'ActiveX component can't create object'?
- הגורם האופייני הוא אי-התאמת bitness בין Office/VBA לבין שרת ה-COM. עבור Office בגרסת 64-bit, בונים עם x64/win-x64 ורושמים עם regsvr32 מ-System32; עבור Office בגרסת 32-bit (על Windows 64-bit), בונים עם x86/win-x86 ורושמים עם regsvr32 מ-SysWOW64, ויוצרים TLB עם dscom32.exe. ב-COM host של .NET 5+, השארת AnyCPU נוטה לגרום ל-*.comhost.dll להיווצר בצד 64-bit, מה שעלול לא להתאים ל-Office בגרסת 32-bit — לכן בטוח יותר לציין x86/x64 באופן מפורש בהתאם ל-Office.
- אסור להשתמש ב-ClassInterfaceType.AutoDual?
- נראה נוח לכאורה, אבל עדיף להימנע ממנו כי קל לשבור אותו אם משנים את סדר החברים או ההרכב אחרי הפרסום. אם רוצים שימוש יציב וטיפוסי מ-VBA, הדרך המקובלת היא להגדיר interface מפורש ולהפוך את ה-class ל-ClassInterfaceType.None, ואת ה-interface שבו VBA משתמש ל-InterfaceIsDual. הקצאת DispId מפחיתה תקלות כשמשנים בעתיד את סדר השיטות. חשוב גם לזכור שב-COM ה-GUID הוא החוזה עצמו, ולכן יצירה מחדש לא זהירה של IID או CLSID אחרי הפרסום שוברת references ורישומים קיימים ב-VBA.
- בהפצה מספיק להעביר את ה-DLL בלבד?
- לא, DLL בודד לא יעבוד. צריך למקם יחד את *.dll (גוף המימוש), *.comhost.dll, *.deps.json, *.runtimeconfig.json, *.tlb, ואם צריך גם את ה-DLLs התלויים. בנוסף, במחשב הלקוח נדרש runtime מתאים של .NET 8, וה-COM host לא מופץ כ-self-contained אלא בדרך כלל בתפעול framework-dependent. כדאי לשים לב שאם משנים בהמשך את מיקום ההתקנה, צריך לבצע רישום מחדש.