שימוש ב-DLL של .NET 8 מ-VBA עם טיפוסים - חשיפת COM ו-TLB ב-dscom

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

מצבים שבהם רוצים לקרוא מ-VBA לעיבוד של .NET 8 עדיין נפוצים. בפרט, כאשר רוצים לשמר את הנכסים הקיימים של Excel או Access כמות שהם, ולהעביר ל-C# רק את החלקים הכבדים - עיבוד מחרוזות, HTTP, הצפנה, לוגיקה עסקית.

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

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

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

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

מפת הידע של שימוש ב-DLL של .NET 8 מ-VBA עם טיפוסיםתרשים שמראה שקישור מוקדם מ-VBA ל-COM עם טיפוסים דורש ספריית טיפוסים, ש-dscom אחראי ליצירתה ולרישומה, שצד .NET 8 נחשף כ-COM host, ואיך ClassInterfaceType ואופן הטיפול ב-IID וב-CLSID משפיעים על תאימות הפניית ה-VBA.מחייבמחייבמשתמש במשתמש בשימוש לא מומלץ למממש אתיורש אתמוגדר באמצעותמוגדר באמצעותמחייבמממש אתמשתמש במחייבמחייבעלול לגרום לעלול לגרום לעלול לגרום לשימוש לא מומלץ למענה מומלץ למצמצםמענה מומלץ למשתמש בנבדק באמצעותמוגדר באמצעותמחייבמחייבמחייבVBA(Visual Basic for Applications)dscomספריית טיפוסים (TLB)קישור מוקדם (VBA)קישור מאוחר (CreateObject)tlbexp.exe / RegAsm.exeCOM host(*.comhost.dll)regsvr32‏.NET (מ-Core ואילך)‏COM (Component Object Model)‏IID (מזהה ממשק)CLSID(Class ID)שבירת ההפניה או הרישום ב-VBAClassInterfaceType.AutoDualClassInterfaceType.NoneDispIdAttribute‏InterfaceIsDual (ממשק דואלי)HRESULTחריגת ‎.NETComVisibleAttributeדרישת התאמת bitness

בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 27, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle

2. מבט כללי על ההרכב

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

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

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

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

החלל ש-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 ביט

אם רוצים רק ליצור 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.

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

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

8. מגדירים הפניה ב-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 ביט לא רואים TLB שנרשם בגרסת 64 ביט.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

שאלות נפוצות

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

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

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג