איך קוראים ל-DLL של .NET 8 מ-VBA עם טיפוסים: COM host ו-dscom

· עודכן בתאריך: · · C#, .NET 8, VBA, COM, Office, dscom

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 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 נחלש, טעויות הקלדה בשמות שיטות מתגלות רק בזמן ריצה, ובהדרגה שוקעים בקוד שמבוסס על מחרוזות.

הבעיה של late bindingכשנשארים עם CreateObject ו-late binding, בצד ה-VBA הכול הופך ל-Object, ה-IntelliSense נחלש, וטעויות הקלדה בשם השיטה מתגלות רק בזמן ריצה.CreateObject עם late bindingבצד ה-VBA הכול הופך ל-Objectה-IntelliSense נחלשטעות הקלדה מתגלה רק בזמן ריצה

איור 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.

המסלול האחד עד לשימוש עם טיפוסיםהזרימה: בונים עם EnableComHosting, יוצרים TLB עם dscom tlbexport, רושמים את ה-comhost עם regsvr32, רושמים את ה-TLB עם dscom tlbregister, ואז משתמשים עם טיפוסים מה-reference ב-VBA.בנייה עם EnableComHostingיצירת TLB עם dscom tlbexportרישום ה-comhost עם regsvr32רישום ה-TLB עם dscom tlbregisterreference ב-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. מבט כללי על ההרכב

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

מבנה החיבור בין VBA ל-.NET 8VBA מקבל מידע טיפוסים מה-TLB שהוגדר כ-reference, קורא דרך ה-comhost לגוף המימוש של .NET 8, וזה רץ על ה-runtime של .NET 8.מקבל מידע טיפוסים מה-TLB שהוגדר כ-referenceקריאת COMVBA / Excel / AccessVbaTypedComSample.tlbVbaTypedComSample.comhost.dllVbaTypedComSample.dll (.NET 8).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.

אי-ההתאמה שנגרמת מהשארת AnyCPUהשארת AnyCPU גורמת ל-comhost להיווצר בדרך כלל בצד 64-bit, מה שעלול לא להתאים ל-Office בגרסת 32-bit, ולכן בטוח יותר לציין x86 או x64 באופן מפורש בהתאם ל-Office.נשארים עם AnyCPUcomhost נוטה להיווצר ב-64-bitלא מתאים ל-Office בגרסת 32-bitמציינים 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 ציבורי בלי ארגומנטים
איך בונים את הטיפוס שנחשףה-interface המפורש ICalculator מסומן ב-InterfaceIsDual וב-DispId, ה-class Calculator ממומש עם ClassInterfaceType.None, וה-Guid מוקצה בנפרד ל-interface ול-class.מממשICalculator (interface מפורש)InterfaceIsDual ו-DispIdCalculator (class)ClassInterfaceType.Noneה-Guid מוקצה בנפרד לכל אחד

איור 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 נבנה כדי למלא את החלל הזה.

החלל ש-dscom ממלאבתקופת .NET Framework יצירת TLB ורישום assembly נעשו עם tlbexp.exe ו-RegAsm.exe, אבל מ-.NET 5 ואילך שניהם הוצאו משימוש בלי ממשיך מובנה, ולכן dscom ממלא את החלל הזה.תקופת .NET Frameworktlbexp.exe ו-RegAsm.exe.NET 5 ואילךשניהם הוצאו משימוש, אין ממשיך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-bit
  • dscom32.exe — יוצר TLB בגרסת 32-bit מ-assembly מסוג AnyCPU או 32-bit

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

יש עוד הנחת יסוד שקל לפספס. כדי להריץ את dscom32.exe, צריך שיהיה מותקן runtime של .NET בגרסת x86. הסיבה היא ש-dscom טוען את hostfxr.dll, ובסביבה שבה מותקן רק x64 הוא לא יעבוד. בדקו ברשימה שמופיעה בפלט של dotnet --info אם יש runtime של x86.

ההכנה ליצירת TLB בגרסת 32-bitה-dscom שמותקן דרך dotnet tool יוצר רק TLB בגרסת 64-bit, ולכן ל-32-bit צריך dscom32.exe מדף ה-releases ב-GitHub, וגם runtime בגרסת x86 של .NET כדי להריץ אותו.צריך TLB בגרסת 32-bitהשגת dscom32.exe מדף ה-releasesבדיקה שקיים runtime בגרסת x86tlbexport עם dscom32.exeהגרסה ב-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 כשרת COM
  • tlbregister רושם את *.tlb כ-type library
הרישום מורכב משני חלקיםבהרשאת Administrator, regsvr32 רושם את ה-comhost כשרת COM, ו-dscom tlbregister רושם את ה-TLB כ-type library, ושני הרישומים האלה שונים במהותם.מריצים בהרשאת Administratorregsvr32 רושם את ה-comhosttlbregister רושם את ה-TLBרישום הכניסה להפעלת COMרישום מידע הטיפוסים שרואה VBA

איור 8: הכניסה להפעלה ומידע הטיפוסים הם שני דברים שונים, ולכן הרישום מורכב משני חלקים.

8. מגדירים reference ב-VBA ומשתמשים עם טיפוסים

  1. פותחים Excel או Access
  2. פותחים את עורך ה-VBA (VBE) עם Alt + F11. אם פותחים מהרצועה (ribbon), זה תחת פיתוח > Visual Basic. אם הכרטיסייה פיתוח לא מוצגת, מסמנים אותה תחת קובץ > אפשרויות > התאמה אישית של הרצועה
  3. בתפריט ה-VBE: כלים > הפניות
  4. הרשימה ספריות זמינות להפניה מסודרת אלפביתית. אם הרישום הצליח, שם הספרייה (בברירת מחדל זהה לשם ה-assembly, VbaTypedComSample) יופיע ברשימה; מסמנים את תיבת הסימון שמשמאל ולוחצים אישור
  5. אם הספרייה לא מופיעה ברשימה, לוחצים על הכפתור עיון... ובוחרים ישירות את VbaTypedComSample.tlb

אם הספרייה לא מופיעה ברשימה, הסיבה היא בדרך כלל אחת משתי אלה — אי-התאמת bitness מפרק 3, או ש-tlbregister מפרק 7 לא הצליח. מ-Office בגרסת 32-bit לא רואים TLB שנרשם בגרסת 64-bit.

איך מזהים מדוע הספרייה לא מופיעה בהגדרת ה-referenceכאשר ספרייה לא מופיעה ברשימת ה-references, הגורם הוא בדרך כלל אי-התאמת bitness מפרק 3 או כישלון tlbregister מפרק 7, ומ-Office בגרסת 32-bit לא רואים TLB שנרשם בגרסת 64-bit.הספרייה לא מופיעה ברשימהחושדים באי-התאמת bitness (פרק 3)חושדים בכישלון tlbregister (פרק 7)מ-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.

איתור תקלה לפי תוצאת ההרצהאם תוצאת דוגמת ה-VBA תואמת את הצפוי, כל המסלול עובד; אם הערך לא תואם, חושדים במימוש בצד .NET; ואם לא ניתן להריץ בכלל, חושדים ב-bitness מפרק 3 וברישום מפרק 7.כצפויהערך לא תואםלא ניתן להריץמריצים את דוגמת ה-VBAמה התוצאה?כל המסלול עובדחושדים במימוש בצד .NETחושדים ב-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 — זה יציב יותר כגבול.

איך exception של .NET מגיע ל-VBAexception שנזרק בצד .NET הופך ב-COM boundary ל-HRESULT, ובצד ה-VBA נכנס ה-HRESULT הזה ל-Err.Number כ-Long עם סימן, וההודעה מ-IErrorInfo נכנסת ל-Err.Description.exception נזרק בצד .NETהופך ל-HRESULT בגבול ה-COMנכנס עם סימן ל-Err.Numberההודעה נכנסת ל-Err.Descriptionממירים ל-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.

מה צריך לוודא בזמן ההפצהההפצה כוללת את כל קבוצת קבצי הפלט ולא DLL בודד, במחשב הלקוח מתקינים runtime של .NET 8 באותו bitness כמו Office, ובמסמכי ההפצה כותבים את סוג ה-runtime, הגרסה וה-bitness.ממקמים את כל קבוצת הקבציםפועל אצל הלקוחruntime של .NET 8 באותו bitnessמציינים במסמכי ההפצה את פרטי ה-runtime

איור 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 יכול לממש את שניהם
איך שומרים על interface שכבר פורסםinterface שכבר פורסם, ICalculator, נשאר כמות שהוא, ואם רוצים שינוי גדול יוצרים ICalculator2 חדש, כאשר ה-class יכול לממש את שניהם.ICalculator שכבר פורסםנשאר כמות שהוארוצים שינוי גדוליוצרים ICalculator2 חדשה-class יכול לממש את שניהם

איור 13: משאירים את החוזה הקיים כמות שהוא, ומוסיפים חוזה חדש לצדו — כך עובדים ב-COM.

10.5 בוחרים טיפוסים פשוטים

בגבול שנחשף ל-VBA, עדיף לא להתחכם.

הטיפוסים הבטוחים ביותר להתחיל בהם הם אלה.

  • int
  • double
  • bool
  • string
  • DateTime
  • decimal
  • enum

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). אם מעבירים או מוחקים את התיקייה כולה בלי לבטל את הרישום קודם, נשארת ברישום נתיב שכבר לא קיים.

איך מקפלים את הרישום לפני ואחרי עדכוןבזמן עדכון סוגרים את Office, ואם צריך מבטלים רישום בסדר הפוך ובאותו bitness של הרישום המקורי, ואז בונים מחדש ורושמים שוב.סוגרים את Officeמבטלים רישום בסדר הפוך אם צריךבונים מחדשרושמים שובעם אותה פקודת 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. מקורות

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

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

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

שאלות נפוצות

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

מה צריך כדי להשתמש ב-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. כדאי לשים לב שאם משנים בהמשך את מיקום ההתקנה, צריך לבצע רישום מחדש.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג