מצבים שבהם רוצים לקרוא מ-VBA לעיבוד של .NET 8 עדיין נפוצים. בפרט, כאשר רוצים לשמר את הנכסים הקיימים של Excel או Access כמות שהם, ולהעביר ל-C# רק את החלקים הכבדים - עיבוד מחרוזות, HTTP, הצפנה, לוגיקה עסקית.
אבל אם נשארים עם CreateObject וקישור מאוחר, בצד ה-VBA הכול הופך ל-Object. ה-IntelliSense נחלש, טעויות הקלדה בשמות שיטות מתגלות רק בזמן ריצה, ובהדרגה שוקעים בביצה שמבוססת על מחרוזות.
flowchart TB
accTitle: הביצה של קישור מאוחר
accDescr: תרשים שמראה שכשנשארים עם CreateObject וקישור מאוחר, בצד ה-VBA הכול הופך ל-Object, ה-IntelliSense נחלש, וטעויות הקלדה בשם השיטה מתגלות רק בזמן ריצה.
lb1["CreateObject עם קישור מאוחר"] --> lb2["בצד ה-VBA הכול הופך ל-Object"]
lb2 --> lb3["ה-IntelliSense נחלש"]
lb2 --> lb4["טעות הקלדה מתגלה רק בזמן ריצה"]
איור 1: ככל שנשארים עם קישור מאוחר, שוקעים יותר בביצה שמבוססת על מחרוזות.
לכן המאמר הזה מתמקד רק בחשיפת DLL של .NET 8 כ-COM, יצירת ספריית טיפוסים (TLB) עם dscom, ושימוש בה מ-VBA עם קישור מוקדם וטיפוסים.
הפעם נשאיר בצד את הסיפור הישן של .NET Framework + RegAsm, כתיבת IDL ידנית וקימוע עם MIDL, ואת נושא ה-Reg-Free COM. כאן נעסוק רק במסלול האחד של .NET 8 / COM host / dscom / קישור מוקדם ב-VBA.
הקוד שמופיע במאמר הזה זמין כערכת דוגמאות מלאה (ספריית חשיפת COM, סקריפטים ליצירה ורישום של TLB, מודול VBA, בדיקות יחידה) שפרסמנו ב-GitHub וניתן לבנות ולבדוק.
dotnet8-dll-typed-vba-com-dscom-tlb - komurasoft-blog-samples (GitHub)
הסביבה הנדרשת
| פריט | מה נדרש |
|---|---|
| מערכת הפעלה | Windows. מכיוון שמבצעים רישום COM, צריך שיהיה אפשר להריץ את regsvr32 בהרשאת מנהל |
| .NET SDK | .NET 8 SDK. EnableComHosting הוא פיצ’ר מ-.NET 5 ואילך |
| Office | Excel או Access. קודם כל בודקים אם מדובר בגרסת 32 ביט או 64 ביט (פרק 3) |
| כלי ליצירת TLB | dscom. אופן ההשגה שונה בין 64 ביט ל-32 ביט (פרק 6) |
| מחשב הלקוח | runtime של .NET 8 באותו bitness כמו Office (פרק 9) |
לפני שמתחילים בשלבים, מומלץ מאוד לתעד את גרסאות הסביבה שלכם. אחר כך, כשמתגלה “אותם שלבים אבל לא עובד”, המידע הזה הוא הבסיס היחיד להשוואה.
# רשימת ה-.NET SDK וה-runtime (גם אפשר לראות אם מותקן x64 או x86)
dotnet --info
# מספר הבנייה של Windows
winver
את גרסת Office ואת ה-bit שלו אפשר לבדוק ב-Excel דרך קובץ > חשבון > אודות Excel. בסוף שורת הכותרת של הדיאלוג מופיע 32 סיביות או 64 סיביות.
1. קודם כל - המסקנה
אם מסדרים רק את המסקנה, הזרימה נראית כך:
- בונים את ספריית המחלקות של .NET 8 עם
EnableComHosting=true - יוצרים ממשק מפורש ו-מחלקה שנחשפים ל-COM
- הופכים את המחלקה ל-
ClassInterfaceType.None, בלי לברוח אלAutoDual - הופכים את הממשק שבו VBA משתמש ל-
InterfaceIsDual - מתוך ה-
*.dllשנוצר אחרי הבנייה, יוצרים*.tlbעםdscom tlbexport - רושמים את
*.comhost.dllעםregsvr32 - רושמים את
*.tlbעםdscom tlbregister - מוסיפים הגדרת הפניה ב-VBA, ומשתמשים עם טיפוסים בצורה
Dim x As שם_הספרייה.IYourInterface
בקיצור, ההרכב הוא: הכניסה ל-COM היא *.comhost.dll שיוצר .NET SDK, מידע הטיפוסים הוא *.tlb שיוצר dscom, ו-VBA מבצע קישור מוקדם לפי אותו TLB.
flowchart TB
accTitle: המסלול האחד עד לשימוש עם טיפוסים
accDescr: תרשים שמראה את הזרימה - בונים עם EnableComHosting, יוצרים TLB עם dscom tlbexport, רושמים את ה-comhost עם regsvr32, רושמים את ה-TLB עם dscom tlbregister, ואז משתמשים עם טיפוסים מהגדרת ההפניה ב-VBA.
st1["בנייה עם EnableComHosting"] --> st2["יצירת TLB עם dscom tlbexport"]
st2 --> st3["רישום ה-comhost עם regsvr32"]
st3 --> st4["רישום ה-TLB עם dscom tlbregister"]
st4 --> st5["הגדרת הפניה ב-VBA ושימוש עם טיפוסים"]
איור 2: אם מתקדמים בסדר של בנייה, יצירת TLB, שני רישומים, והגדרת הפניה - אפשר לקרוא עם טיפוסים.
מפת הידע של המאמר
כדי להשתמש בספריית מחלקות של .NET 8 מ-VBA עם טיפוסים, נדרשת ספריית טיפוסים (TLB) שהיא מידע הטיפוסים של COM, וכלי בשם dscom יוצר ורושם אותה כממשיך של tlbexp.exe ו-RegAsm.exe שהוצאו משימוש ב-.NET Framework. צד .NET בונים עם EnableComHosting שיוצר COM host, ורישום עם regsvr32 והתאמת bitness ל-Office הם תנאי מוקדם. צד VBA טוען את ספריית הטיפוסים דרך הגדרת הפניה, מה שמאפשר קישור מוקדם, ובטוח יותר מבחינת טיפוסים מקישור מאוחר עם CreateObject. תאימות אחרי הפרסום תלויה בטיפול ב-IID וב-CLSID ובבחירת ClassInterfaceType, כאשר השילוב של ClassInterfaceType.None, InterfaceIsDual, ו-DispId הוא הדרך המקובלת למנוע שבירת הפניית VBA.
flowchart LR
accTitle: מפת הידע של שימוש ב-DLL של .NET 8 מ-VBA עם טיפוסים
accDescr: תרשים שמראה שקישור מוקדם מ-VBA ל-COM עם טיפוסים דורש ספריית טיפוסים, ש-dscom אחראי ליצירתה ולרישומה, שצד .NET 8 נחשף כ-COM host, ואיך ClassInterfaceType ואופן הטיפול ב-IID וב-CLSID משפיעים על תאימות הפניית ה-VBA.
vba["VBA(Visual Basic for Applications)"]
dscom["dscom"]
type_library["ספריית טיפוסים (TLB)"]
com_early_binding["קישור מוקדם (VBA)"]
com_late_binding["קישור מאוחר (CreateObject)"]
tlbexp_regasm["tlbexp.exe / RegAsm.exe"]
comhost["COM host(*.comhost.dll)"]
regsvr32["regsvr32"]
dotnet[".NET (מ-Core ואילך)"]
com["COM (Component Object Model)"]
iid["IID (מזהה ממשק)"]
clsid["CLSID(Class ID)"]
vba_reference_break["שבירת ההפניה או הרישום ב-VBA"]
classinterfacetype_autodual["ClassInterfaceType.AutoDual"]
classinterfacetype_none["ClassInterfaceType.None"]
dispid_attribute["DispIdAttribute"]
interface_is_dual["InterfaceIsDual (ממשק דואלי)"]
hresult["HRESULT"]
dotnet_exception["חריגת .NET"]
com_visible_attribute["ComVisibleAttribute"]
bitness_match_requirement["דרישת התאמת bitness"]
vba -.->|"מחייב"| type_library
com_early_binding -->|"מחייב"| type_library
vba -->|"משתמש ב"| com_early_binding
vba -->|"משתמש ב"| com_late_binding
com_late_binding -.->|"שימוש לא מומלץ ל"| vba
dscom -->|"מממש את"| type_library
dscom -->|"יורש את"| tlbexp_regasm
type_library -.->|"מוגדר באמצעות"| dscom
comhost -->|"מוגדר באמצעות"| regsvr32
comhost -->|"מחייב"| dotnet
comhost -->|"מממש את"| com
vba -.->|"משתמש ב"| com
com -->|"מחייב"| iid
com -->|"מחייב"| clsid
iid -.->|"עלול לגרום ל"| vba_reference_break
clsid -.->|"עלול לגרום ל"| vba_reference_break
classinterfacetype_autodual -.->|"עלול לגרום ל"| vba_reference_break
classinterfacetype_autodual -->|"שימוש לא מומלץ ל"| vba
classinterfacetype_none -->|"מענה מומלץ ל"| vba
dispid_attribute -->|"מצמצם"| vba_reference_break
interface_is_dual -->|"מענה מומלץ ל"| vba
com -->|"משתמש ב"| hresult
dotnet_exception -.->|"נבדק באמצעות"| hresult
dotnet -->|"מוגדר באמצעות"| com_visible_attribute
comhost -->|"מחייב"| bitness_match_requirement
dscom -.->|"מחייב"| bitness_match_requirement
regsvr32 -->|"מחייב"| bitness_match_requirement
בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 27, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle
2. מבט כללי על ההרכב
קודם כל, נראה בתמונה אחת מה תפקידו של מה.
flowchart LR
accTitle: מבנה החיבור בין VBA ל-.NET 8
accDescr: תרשים שמראה ש-VBA מקבל מידע טיפוסים מה-TLB שהוגדר כהפניה, קורא דרך ה-comhost לגוף המימוש של .NET 8, וזה רץ על ה-runtime של .NET 8.
VBA["VBA / Excel / Access"] -->|"מקבל מידע טיפוסים מה-TLB שהוגדר כהפניה"| 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 ביט / 64 ביט
אם מפספסים את זה, יש סיכוי גבוה למאוד להגיע ל-ActiveX component can't create object..
חשוב להתאים את ה-bitness בין Office/VBA לבין שרת ה-COM.
| צד השימוש | קנה מידה בצד .NET | יצירת TLB | פקודת רישום |
|---|---|---|---|
| Office בגרסת 64 ביט | x64 / win-x64 |
dscom |
C:\Windows\System32\regsvr32.exe |
| Office בגרסת 32 ביט (על Windows 64 ביט) | x86 / win-x86 |
dscom32.exe |
C:\Windows\SysWOW64\regsvr32.exe |
ב-COM host של .NET 5+, השארת AnyCPU נוטה לגרום ל-*.comhost.dll להיווצר בצד 64 ביט, מה שעלול לא להתאים ל-Office בגרסת 32 ביט. לכן, בטוח יותר לציין x86 / x64 באופן מפורש בהתאם ל-Office.
flowchart TB
accTitle: אי-ההתאמה שנגרמת מהשארת AnyCPU
accDescr: תרשים שמראה שהשארת AnyCPU גורמת ל-comhost להיווצר בדרך כלל בצד 64 ביט, מה שעלול לא להתאים ל-Office בגרסת 32 ביט, ולכן בטוח יותר לציין x86 או x64 באופן מפורש בהתאם ל-Office.
b1["נשארים עם AnyCPU"] --> b2["comhost נוטה להיווצר ב-64 ביט"]
b2 --> b3["לא מתאים ל-Office בגרסת 32 ביט"]
b3 -.-> b4["מציינים bit בהתאם ל-Office"]
איור 4: פער ב-bitness הוא קיצור דרך ישיר להודעת “לא ניתן ליצור אובייקט”.
הקוד במאמר הזה יתמקד ב-Office בגרסת 64 ביט. עבור Office בגרסת 32 ביט, יש להחליף בהמשך 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 כותבים את הממשק והמחלקה שנחשפים
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בנפרד לממשק ולמחלקה - הופכים ל-
ClassInterfaceType.None, כלומר לא מסתמכים על class interface שנוצר אוטומטית - הופכים ל-
InterfaceIsDualכדי שיהיה נוח לעבוד ב-VBA - הקצאת
DispIdמפחיתה תקלות כשמשנים בעתיד את סדר השיטות - מכיוון ש-COM יוצר את האובייקט עם
New, יש להכין בנאי ציבורי ללא ארגומנטים
flowchart TB
accTitle: איך בונים את הטיפוס שנחשף
accDescr: תרשים שמראה שהממשק המפורש ICalculator מסומן ב-InterfaceIsDual וב-DispId, המחלקה Calculator ממומשת עם ClassInterfaceType.None, וה-Guid מוקצה בנפרד לממשק ולמחלקה.
if1["ICalculator (ממשק מפורש)"] --> d1["InterfaceIsDual ו-DispId"]
cl1["Calculator (מחלקה)"] -->|"מממש"| if1
cl1 --> d2["ClassInterfaceType.None"]
if1 -.-> g1["ה-Guid מוקצה בנפרד לכל אחד"]
cl1 -.-> g1
איור 5: עמוד השדרה של חשיפה עם טיפוסים הוא שילוב הממשק המפורש עם המחלקה שמסומנת ב-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 הוא כלי שורת פקודה בקוד פתוח, ליצירה ולרישום של ספריות טיפוסים (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 ביט
אם רוצים רק ליצור TLB בגרסת 64 ביט, מספיק להתקין עם 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 ביט - מאיפה משיגים את dscom32.exe
זו הנקודה שהכי קל להיתקע בה כשתומכים ב-Office בגרסת 32 ביט.
ה-dscom שמתקבל דרך dotnet tool install יודע לעבוד רק עם assembly מסוג AnyCPU או 64 ביט, ויכול ליצור רק TLB בגרסת 64 ביט. כדי ליצור TLB בגרסת 32 ביט, נדרש קובץ הרצה נפרד בשם dscom32.exe, שלא מתקבל דרך NuGet אלא מורידים אותו מדף ה-releases ב-GitHub.
- מקור ההורדה: https://github.com/dspace-group/dscom/releases
-
dscom.exe - יוצר TLB בגרסת 64 ביט מ-assembly מסוג AnyCPU או 64 ביט -
dscom32.exe - יוצר TLB בגרסת 32 ביט מ-assembly מסוג AnyCPU או 32 ביט
בדוגמה הזו, ה-dscom32.exe שהורדנו ממוקם בתיקיית tools ישירות מתחת לפרויקט. מיקום האחסון גמיש, אבל אין להפיץ אותו יחד עם פלט הבנייה. זהו כלי לזמן פיתוח, ולא נחוץ בזמן ריצה.
יש עוד הנחת יסוד שקל לפספס. כדי להריץ את dscom32.exe, צריך שיהיה מותקן runtime של .NET בגרסת x86. הסיבה היא ש-dscom טוען את hostfxr.dll, ובסביבה שבה מותקן רק x64 הוא לא יעבוד. בדקו ברשימה שמופיעה בפלט של dotnet --info אם יש runtime של x86.
flowchart TB
accTitle: ההכנה ליצירת TLB בגרסת 32 ביט
accDescr: תרשים שמראה שה-dscom שמותקן דרך dotnet tool יוצר רק TLB בגרסת 64 ביט, ולכן ל-32 ביט צריך dscom32.exe מדף ה-releases ב-GitHub, וגם runtime בגרסת x86 של .NET כדי להריץ אותו.
p1["צריך TLB בגרסת 32 ביט"] --> p2["השגת dscom32.exe מדף ה-releases"]
p2 --> p3["בדיקה שקיים runtime בגרסת x86"]
p3 --> p4["tlbexport עם dscom32.exe"]
p1 -.-> p5["הגרסה ב-dotnet tool מיועדת רק ל-64 ביט"]
איור 7: התמיכה ב-32 ביט שונה כבר מנתיב השגת הכלי, ולכן קל להיתקע כאן.
.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
יש לציין שגם התיעוד של dscom עצמו ממליץ, שאם רוצים לתמוך ב-32 ביט, מומלץ לקמפל את ה-assembly עצמו בגרסת 32 ביט, מכיוון שהשארתו כ-AnyCPU גורמת ל-*.comhost.dll להיווצר כ-64 ביט. זו אותה מסקנה כמו בפרק 3.
אם מציק לבצע את זה ידנית בכל בנייה, אפשר להוסיף את החבילה dSPACE.Runtime.InteropServices.BuildTasks, שמאפשרת יצירה אוטומטית של TLB בזמן קומפילציה.
7. רושמים את ה-COM host ואת ה-TLB
יש לבצע את זה מ-Command Prompt / PowerShell בהרשאת מנהל.
7.1 עבור Office ו-COM בגרסת 64 ביט
$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 ביט (על Windows 64 ביט)
$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כספריית טיפוסים
flowchart TB
accTitle: הרישום מורכב משני חלקים
accDescr: תרשים שמראה שבהרשאת מנהל, regsvr32 רושם את ה-comhost כשרת COM, ו-dscom tlbregister רושם את ה-TLB כספריית טיפוסים, ושני הרישומים האלה שונים במהותם.
adm["מריצים בהרשאת מנהל"] --> r1["regsvr32 רושם את ה-comhost"]
adm --> r2["tlbregister רושם את ה-TLB"]
r1 -.-> m1["רישום הכניסה להפעלת COM"]
r2 -.-> m2["רישום מידע הטיפוסים שרואה VBA"]
איור 8: הכניסה להפעלה ומידע הטיפוסים הם שני דברים שונים, ולכן הרישום מורכב משני חלקים.
8. מגדירים הפניה ב-VBA ומשתמשים עם טיפוסים
- פותחים Excel או Access
- פותחים את עורך ה-VBA (VBE) עם
Alt+F11. אם פותחים מהרצועה (ribbon), זה תחתפיתוח>Visual Basic. אם הכרטיסייהפיתוחלא מוצגת, מסמנים אותה תחתקובץ>אפשרויות>התאמה אישית של הרצועה - בתפריט ה-VBE:
כלים>הפניות - הרשימה
ספריות זמינות להפניהמסודרת אלפביתית. אם הרישום הצליח, שם הספרייה (בברירת מחדל זהה לשם ה-assembly,VbaTypedComSample) יופיע ברשימה; מסמנים את תיבת הסימון שמשמאל ולוחציםאישור - אם הספרייה לא מופיעה ברשימה, לוחצים על הכפתור
עיון...ובוחרים ישירות אתVbaTypedComSample.tlb
אם הספרייה לא מופיעה ברשימה, הסיבה היא בדרך כלל אחת משתי אלה - אי-התאמת bitness מפרק 3, או ש-tlbregister מפרק 7 לא הצליח. מ-Office בגרסת 32 ביט לא רואים TLB שנרשם בגרסת 64 ביט.
flowchart TB
accTitle: איך מזהים מדוע הספרייה לא מופיעה בהגדרת ההפניה
accDescr: תרשים שמראה שכאשר ספרייה לא מופיעה ברשימת ההפניות, הגורם הוא בדרך כלל אי-התאמת bitness מפרק 3 או כישלון tlbregister מפרק 7, ושמ-Office בגרסת 32 ביט לא רואים TLB שנרשם בגרסת 64 ביט.
q1["הספרייה לא מופיעה ברשימה"] --> a1["חושדים באי-התאמת bitness (פרק 3)"]
q1 --> a2["חושדים בכישלון tlbregister (פרק 7)"]
a1 -.-> nt1["מ-32 ביט לא רואים TLB של 64 ביט"]
איור 9: כשספרייה לא מופיעה ברשימה, כמעט תמיד אפשר לצמצם לאחת משתי הסיבות - bitness או רישום.
אפשר לבדוק אם הגדרת ההפניה הצליחה על ידי פתיחת תצוגה > דפדפן אובייקטים (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
אם התוצאה תואמת את הצפוי, סימן ש-הגדרת ההפניה, רישום ה-COM, הפעלת ה-runtime, ומרשלינג הארגומנטים והערך המוחזר - הכול עובד. לעומת זאת, אם הערך לא תואם, כדאי לחשוד במימוש בצד .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 חריגה הופכת בצד ה-VBA לשגיאת COM
לדוגמה, אם בצד .NET נזרקת חריגה כמו ב-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 התואם לחריגה של .NET, כ-Long עם סימן. קשה לקרוא אותו במספר עשרוני, לכן ממירים ל-16-הרי (hex) עם Hex$(Err.Number) |
Err.Description |
דרך IErrorInfo של COM, נכנסת הודעת החריגה של .NET כמות שהיא. בקוד למעלה, זו מחרוזת שמכילה את 0 では割れません。 |
ערך ה-HRESULT קבוע לכל סוג חריגה. עבור ArgumentOutOfRangeException, הערך התואם הוא COR_E_ARGUMENTOUTOFRANGE, בערך 0x80131502. כלומר, אם Hex$(Err.Number) שווה ל-80131502, סימן ש-כפי שציפינו, הגיעה חריגת ArgumentOutOfRangeException מצד .NET.
הנפוצים ביותר מרוכזים כאן.
| חריגת .NET | קבוע HRESULT | ערך |
|---|---|---|
ArgumentException |
COR_E_ARGUMENT |
0x80070057 |
ArgumentOutOfRangeException |
COR_E_ARGUMENTOUTOFRANGE |
0x80131502 |
InvalidOperationException |
COR_E_INVALIDOPERATION |
0x80131509 |
NotSupportedException |
COR_E_NOTSUPPORTED |
0x80131515 |
| חריגות כלליות אחרות | COR_E_EXCEPTION |
0x80131500 |
אם רוצים בצד ה-VBA לפצל את הטיפול לפי סוג החריגה, זה נעשה על ידי הסתעפות לפי ה-HRESULT הזה. עם זאת, תכנון שמסתעף לפי HRESULT רגיש לשינויים בטיפוס החריגה בצד .NET, ולכן עדיף שכישלון עסקי יחזור כערך מוחזר או קוד שגיאה, לא כחריגה - זה יציב יותר כגבול.
flowchart TB
accTitle: איך חריגת .NET מגיעה ל-VBA
accDescr: תרשים שמראה שחריגה שנזרקת בצד .NET הופכת ב-COM boundary ל-HRESULT, ובצד ה-VBA נכנס ה-HRESULT הזה ל-Err.Number כ-Long עם סימן, וההודעה מ-IErrorInfo נכנסת ל-Err.Description.
x1["חריגה נזרקת בצד .NET"] --> x2["הופכת ל-HRESULT בגבול ה-COM"]
x2 --> x3["נכנסת עם סימן ל-Err.Number"]
x2 --> x4["ההודעה נכנסת ל-Err.Description"]
x3 -.-> h1["ממירים ל-16-הרי עם Hex$ כדי לקרוא"]
איור 11: החריגה משנה צורה ל-HRESULT בגבול ה-COM, ומגיעה ל-Err של VBA.
9. איך חושבים על ההפצה
בזמן ההפצה, חשוב להעביר את כל קבוצת קבצי הפלט, ולא DLL בודד.
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(ואם נדרש, גם כל ה-DLL-ים התלויים)
בנוסף, במחשב הלקוח נדרש runtime מתאים של .NET 8. ה-COM host לא מופץ כ-self-contained, אלא בדרך כלל בתפעול framework-dependent.
אלה הפרטים המדויקים:
- דף ההורדה הוא הורדות .NET 8
- לא צריך את ה-SDK, אלא את ה-runtime בלבד. הדוגמה במאמר הזה היא ספריית מחלקות בלי מסך, ולכן
.NET Runtimeמספיק. אם משתמשים בטיפוסי WPF או Windows Forms, נדרש.NET Desktop Runtime - ה-bitness צריך להתאים ל-Office. עבור Office בגרסת 64 ביט - x64, עבור 32 ביט - 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 ביט -
x64/win-x64 - Office בגרסת 32 ביט -
x86/win-x86
10.2 לא להשתמש ב-ClassInterfaceType.AutoDual
נראה נוח לכאורה, אבל קל לשבור אותו אם משנים את סדר החברים או ההרכב אחרי הפרסום.
אם רוצים שימוש יציב וטיפוסי מ-VBA, הדרך המקובלת היא להגדיר ממשק מפורש ולהפוך את המחלקה ל-ClassInterfaceType.None.
10.3 לא ליצור GUID מחדש בפזיזות
ב-COM ה-GUID הוא החוזה עצמו. אם מחליפים IID או CLSID בפזיזות אחרי הפרסום, זה שובר הפניות ורישומים קיימים ב-VBA.
10.4 לא לשבור ממשק שכבר פורסם
ב-COM, גם “רק הוספתי שיטה אחת” עלולה שלא לעבור בשלום.
- משאירים את
ICalculator - אם השינוי גדול, יוצרים
ICalculator2חדש - המחלקה יכולה לממש את שניהם
flowchart TB
accTitle: איך שומרים על ממשק שכבר פורסם
accDescr: תרשים שמראה שממשק שכבר פורסם, ICalculator, נשאר כמות שהוא, ואם רוצים שינוי גדול יוצרים ICalculator2 חדש, כאשר המחלקה יכולה לממש את שניהם.
k1["ICalculator שכבר פורסם"] --> k2["נשאר כמות שהוא"]
k3["רוצים שינוי גדול"] --> k4["יוצרים ICalculator2 חדש"]
k2 --> k5["המחלקה יכולה לממש את שניהם"]
k4 --> k5
איור 13: משאירים את החוזה הקיים כמות שהוא, ומוסיפים חוזה חדש לצדו - כך עובדים ב-COM.
10.5 בוחרים טיפוסים פשוטים
בגבול שנחשף ל-VBA, עדיף לא להתחכם.
הטיפוסים הבטוחים ביותר להתחיל בהם הם אלה.
intdoubleboolstringDateTimedecimalenum
10.6 לא לעדכן כשה-Office פתוח
לפעמים Excel או Access ממשיכים להחזיק את ה-DLL, וזה יוצר בעיות בבנייה או ברישום מחדש.
- סוגרים את ה-Office
- מבטלים רישום אם צריך
- בונים מחדש
- רושמים שוב
ביטול הרישום נעשה בסדר הפוך מהרישום, ועם אותה פקודה באותו bitness ששימשה לרישום. גם כאן נדרשת הרשאת מנהל, כמו ברישום.
# עבור Office ו-COM בגרסת 64 ביט
$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 ביט (על Windows 64 ביט)
$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 ביט לא ניתן לביטול דרך 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 ומכינים ממשק מפורש (המחלקה - ClassInterfaceType.None, הממשק שנועד ל-VBA - InterfaceIsDual), יוצרים TLB עם dscom tlbexport, רושמים את *.comhost.dll עם regsvr32 ואת *.tlb עם dscom tlbregister. אחר כך רק צריך להגדיר הפניה ב-VBA ולבצע קישור מוקדם.
כשמתלבטים, הטריק הוא לחשוב בנפרד על ה-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 ביט)
- דף ה-releases של dscom (מקור ההורדה של
dscom32.exe) - HRESULT と例外を対応付ける方法 - Microsoft Learn
- How to use the Regsvr32 tool and troubleshoot Regsvr32 error messages - Microsoft Support
- .NET 8 downloads
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
בניית פלט דוחות Excel - COM, Open XML, תבנית
פלט דוחות Excel משתנה מהותית לפי השאלה אם מפעילים את Excel אוטומטית, יוצרים xlsx ישירות, או משמרים VBA קיים. המאמר מסדר את קריטריוני הבחי...
המלכודות של יישום תקשורת טורית - עד לתכנון חיבור מחדש ויומן
המאמר מסדר, מנקודת מבט מעשית, את המלכודות שכדאי להימנע מהן ביישום תקשורת טורית לחיבור ציוד ובקרת מכשירי מדידה - מבניית מסגרות, timeout, ...
איך בוחרים בין WinForms, WPF ו-WinUI - טבלת החלטה מהשטח
המאמר מסדר את הבחירה בין WinForms, WPF ו-WinUI מנקודות המבט של פיתוח חדש, נכסים קיימים, הפצה, ביטוי ה-UI ומבנה הצוות.
המלכודות של הזיכרון המשותף ושיטות העבודה המומלצות בפועל
המאמר מסכם את המלכודות בשימוש בזיכרון משותף בעבודה בפועל, ואת התכנון שמוריד את שיעור התקלות - כולל סנכרון, נראות (visibility), אורך חיים,...
השוואה הוגנת של מהירות ריצה - C#, C++, Java, Go
המאמר מסדר איך להשוות בהוגנת את מהירות הריצה של C#, C++, Java ו-Go - תכנון המדידה, warm-up, קיבוע הסביבה, אופן קריאת הסטטיסטיקה, ופריטי...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
תכנון ממשק החיבור שכולל VBA, COM, Office, .NET 8 ויצירת ספריית טיפוסים קשור קשר הדוק לפיתוח אפליקציות Windows, ולכן מתאים לנושא הזה.
ייעוץ טכני וסקירת תכנון
אם רוצים לסדר את תכנון הגבול בין נכסי VBA קיימים ל-.NET 8, כולל bitness, רישום, יצירת TLB ואופן ההפצה, זה נושא שקל להתקדם בו כייעוץ טכני וסקירת תכנון.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה צריך כדי להשתמש ב-DLL של .NET 8 מ-VBA עם טיפוסים (קישור מוקדם)?
- בונים את ספריית המחלקות של .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 ביט, בונים עם x64/win-x64 ורושמים עם regsvr32 מ-System32; עבור Office בגרסת 32 ביט (על Windows 64 ביט), בונים עם x86/win-x86 ורושמים עם regsvr32 מ-SysWOW64, ויוצרים TLB עם dscom32.exe. ב-COM host של .NET 5+, השארת AnyCPU נוטה לגרום ל-*.comhost.dll להיווצר בצד 64 ביט, מה שעלול לא להתאים ל-Office בגרסת 32 ביט - לכן בטוח יותר לציין x86/x64 באופן מפורש בהתאם ל-Office.
- אסור להשתמש ב-ClassInterfaceType.AutoDual?
- נראה נוח לכאורה, אבל עדיף להימנע ממנו כי קל לשבור אותו אם משנים את סדר החברים או ההרכב אחרי הפרסום. אם רוצים שימוש יציב וטיפוסי מ-VBA, הדרך המקובלת היא להגדיר ממשק מפורש ולהפוך את המחלקה ל-ClassInterfaceType.None, ואת הממשק שבו VBA משתמש ל-InterfaceIsDual. הקצאת DispId מפחיתה תקלות כשמשנים בעתיד את סדר השיטות. חשוב גם לזכור שב-COM ה-GUID הוא החוזה עצמו, ולכן יצירה מחדש לא זהירה של IID או CLSID אחרי הפרסום שוברת הפניות ורישומים קיימים ב-VBA.
- בהפצה מספיק להעביר את ה-DLL בלבד?
- לא, DLL בודד לא יעבוד. צריך למקם יחד את *.dll (גוף המימוש), *.comhost.dll, *.deps.json, *.runtimeconfig.json, *.tlb, ואם צריך גם את ה-DLL-ים התלויים. בנוסף, במחשב הלקוח נדרש runtime מתאים של .NET 8, וה-COM host לא מופץ כ-self-contained אלא בדרך כלל בתפעול framework-dependent. כדאי לשים לב שאם משנים בהמשך את מיקום ההתקנה, צריך לבצע רישום מחדש.