كيف تستخدم DLL الخاصّة بـ .NET 8 من VBA بـ early binding عبر COM و dscom

· · C#, .NET 8, VBA, COM, Office, dscom

لا تزال هناك حالات اعتياديّة كثيرة تحتاج فيها VBA إلى استدعاء كود .NET 8.

ينطبق ذلك خاصّةً عندما يبقى حلّ Excel أو Access في مكانه، لكنّ الفريق يريد نقل المنطق الأثقل إلى C#:

  • المعالجة الباهظة
  • معالجة النصوص
  • عمل HTTP
  • التشفير
  • منطق العمل الذي لم يعد ينتمي إلى كود الماكرو

إن بقي كلّ شيء late-bound عبر CreateObject، فإنّ VBA تتحوّل بسرعة إلى مستنقع من متغيّرات Object وافتراضات قائمة على النصوص. تضعف IntelliSense، وتبقى أخطاء أسماء الـ methods حتى وقت التنفيذ، ويصبح الكود أصعب في الوثوق به.

لذا فإنّ هذا المقال يُضيّق المشكلة عن قصد:

كشف DLL الخاصّة بـ .NET 8 عبر COM، وتوليد type library باستخدام dscom، واستخدامها من VBA بـ early binding.

هذا ليس عن المسار الأقدم .NET Framework + RegAsm، ولا عن IDL مكتوب يدويّاً مع MIDL، ولا عن Reg-Free COM. التركيز هنا على مسار محدّد واحد:

.NET 8 / COM host / dscom / VBA early binding

1. النسخة المختصرة

التدفّق العمليّ هو:

  • بناء class library الخاصّة بـ .NET 8 مع EnableComHosting=true
  • تعريف interfaces وclasses صريحة مرئيّة لـ COM
  • إبقاء classes على ClassInterfaceType.None بدلاً من الاعتماد على AutoDual
  • استخدام InterfaceIsDual للـ interfaces التي ستستهلكها VBA
  • تشغيل dscom tlbexport على الـ assembly المبنيّ لإنتاج *.tlb
  • تسجيل *.comhost.dll المولّد بـ regsvr32
  • تسجيل *.tlb المولّد بـ dscom tlbregister
  • إضافة المرجع في VBA واستخدام المكتبة عبر early binding

النموذج الذهنيّ النظيف هو:

نقطة دخول COM هي *.comhost.dll الذي يولّده الـ SDK، بينما تعيش معلومات النوع المرئيّة لـ VBA في *.tlb الذي يولّده dscom.

2. الشكل الكامل للحلّ

ها هي الصورة الكاملة أوّلاً.

flowchart LR
    VBA["VBA / Excel / Access"] -->|Reads type information from referenced TLB| TLB["VbaTypedComSample.tlb"]
    VBA -->|COM calls| COMHOST["VbaTypedComSample.comhost.dll"]
    COMHOST --> DOTNET["VbaTypedComSample.dll (.NET 8)"]
    DOTNET --> RUNTIME[".NET 8 Runtime"]

كلّ ملفّ له وظيفة مختلفة:

الملفّ الدور
VbaTypedComSample.dll تنفيذ .NET 8 الفعليّ
VbaTypedComSample.comhost.dll نقطة دخول تفعيل COM
VbaTypedComSample.tlb معلومات النوع التي تقرأها VBA لـ early binding
VbaTypedComSample.deps.json بيانات حلّ التبعيّات
VbaTypedComSample.runtimeconfig.json إعداد إقلاع .NET runtime

ذلك التمييز يهمّ كثيراً.

VBA لا تحصل على معلومات النوع الودودة من DLL التنفيذ بسحر.
بل تحصل عليها من type library.
وتفعيل COM نفسه يمرّ عبر COM host، لا عبر assembly التنفيذ مباشرةً.

3. اقرّر bitness أوّلاً

إن أُجّل هذا الجزء، فإنّ المشروع غالباً ما ينحرف مباشرةً نحو:

«ActiveX component can’t create object.»

ينبغي أن يتطابق جانب Office / VBA مع جانب COM server في bitness.

المستهلك هدف جانب .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 hosting الحديث في .NET، فإنّ ترك المشروع على AnyCPU غالباً ما لا يكون الخيار الأكثر أماناً للتشغيل البينيّ مع Office. إن كان تثبيت Office المستهدف معروفاً، فإنّ التصريح بـ x86 أو x64 يكون عادةً أهدأ.

تفترض الأمثلة أدناه Office 64-bit.
لـ Office 32-bit، استبدل أمثلة x64 بـ x86، و win-x64 بـ win-x86.

4. ابنِ جانب .NET 8

للمثال، تخيّل مكتبة صغيرة تكشف ثلاث methods لـ VBA:

  • Add
  • Divide
  • Hello

4.1 ملفّ المشروع

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <!-- Generate the COM host -->
    <EnableComHosting>true</EnableComHosting>

    <!-- 64-bit Office example -->
    <PlatformTarget>x64</PlatformTarget>
    <NETCoreSdkRuntimeIdentifier>win-x64</NETCoreSdkRuntimeIdentifier>
  </PropertyGroup>
</Project>

الخاصيّة الأساسيّة هي EnableComHosting.
ذلك ما يجعل الـ SDK يُنتج *.comhost.dll.

4.2 أبقِ الـ assembly مغلقاً افتراضيّاً

عادةً ما يكون أنظف إبقاء الـ assembly غير مرئيّ لـ COM افتراضيّاً وفتح الأنواع المحدّدة فقط التي تريد كشفها.

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), "Cannot divide by zero.");
        }

        return x / y;
    }

    public string Hello(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return "Hello";
        }

        return $"Hello, {name}";
    }
}

النقاط العمليّة هنا هي:

  • أعطِ الـ interface و الـ class GUIDs خاصّة بكلّ منهما
  • أبقِ الـ class على ClassInterfaceType.None
  • استخدم InterfaceIsDual حتى تستهلك VBA الـ interface براحة
  • اضبط قيم DispId لتقليل الكسر القابل للتجنّب
  • وفّر public parameterless constructor لتفعيل COM

قد يبدو AutoDual مغرياً لأنّه يبدو سريعاً ومريحاً.
لكنّه أيضاً طريقة سهلة لجعل الشكل المكشوف هشّاً لاحقاً. العقود الصريحة تتقدّم في العمر بشكل أفضل.

5. ابنِ المشروع

استخدم Release build:

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 لـ Office 64-bit

ثبّت dscom أوّلاً:

dotnet tool install --global dscom

ثمّ صدّر type library من الـ assembly المبنيّ:

dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb

6.2 لـ Office 32-bit

هذا فخّ صغير لكنّه مهمّ.

إن كان الهدف هو Office 32-bit، فإنّ استخدام dscom32.exe هو المسار الأكثر أماناً لـ workflow الخاصّ بـ TLB.

.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb

7. سجّل COM host و TLB

ينبغي تشغيل هذه الأوامر من command prompt أو PowerShell برفع صلاحيّات (elevated).

7.1 حالة Office 64-bit / 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 يسجّل نقطة دخول COM server
  • tlbregister يسجّل type library

تحتاج كليهما إن كنت تريد:

  • تفعيل COM
  • بالإضافة إلى early binding مع types في VBA

7.3 إلغاء التسجيل أثناء التطوير

أثناء التكرار، غالباً ما يكون من المفيد إلغاء التسجيل قبل إعادة البناء أو تغيير تخطيط الـ binary.

للحالة 64-bit:

$out = Resolve-Path .\bin\Release\net8.0-windows

C:\Windows\System32\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"
dscom tlbunregister "$out\VbaTypedComSample.tlb"

لـ Office 32-bit، استبدل System32 بـ SysWOW64 واستخدم dscom32.exe.

8. أضف المرجع في VBA واستخدمه مع types

بمجرّد تسجيل type library:

  1. افتح Excel أو Access
  2. افتح VBA editor
  3. اذهب إلى Tools -> References
  4. ضع علامة على المكتبة المسجّلة إن ظهرت في القائمة
  5. عند الحاجة، تصفّح للوصول إلى VbaTypedComSample.tlb المولّد

عندئذ تستطيع VBA استخدام الأنواع مباشرةً:

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

هذا يُعيد الأشياء التي يفقدها late binding:

  • IntelliSense
  • اكتشاف أسهل للأخطاء الإملائيّة
  • سطح API قابل للتصفّح
  • كود يُقرأ كعقد بدلاً من كومة من Object

8.1 الاستثناءات تظهر كأخطاء COM في VBA

إن رمى جانب .NET، فإنّ 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 Err.Description
End Sub

ذلك سبب يجعل من المفيد إبقاء رسائل الاستثناءات على جانب .NET مفهومة.

9. التفكير في النشر

لا تفكّر في النشر على أنّه «انسخ DLL واحدة».

وحدة النشر العمليّة أقرب إلى:

VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(and any dependency DLLs)

تحتاج الجهة المستهدفة أيضاً إلى .NET 8 runtime المطابق.
هذا المسار الخاصّ بـ COM hosting عادةً ما يكون framework-dependent بدلاً من self-contained.

ترتيب معقول هو:

  1. اقرّر مجلّد الهدف
  2. ضع مجموعة الإخراج الكاملة هناك
  3. تأكّد من تثبيت .NET 8 runtime
  4. سجّل COM host و TLB
  5. أضف مرجع VBA

التسجيل أوّلاً ثمّ نقل الملفّات لاحقاً طريقة جيّدة لإنشاء فشل تفعيل مربك.

10. الفخاخ الشائعة

10.1 لا تترك المشروع على AnyCPU

إن لم يتطابق bitness الخاصّ بـ Office مع bitness الخاصّ بـ COM host، فإنّ حالات الفشل تصبح غير سارّة بسرعة.

  • Office 64-bit -> x64 / win-x64
  • Office 32-bit -> x86 / win-x86

10.2 لا تستخدم ClassInterfaceType.AutoDual

يبدو أسهل، لكنّه يجعل العقد المكشوف أسهل في الكسر بكثير لاحقاً.

إن كان الهدف هو استهلاك VBA مستقرّ مع types، فإنّ interfaces الصريحة بالإضافة إلى ClassInterfaceType.None تصميم أكثر صحّة.

10.3 لا تُعد توليد GUIDs بطيش

في COM، الـ GUIDs جزء من العقد:

  • interface IDs
  • class IDs

تغييرها بإهمال بعد وجود مستهلكين بالفعل سيكسر التسجيلات ومراجع VBA.

10.4 لا تكسر الـ interfaces المنشورة بطيش

COM لا يغفر دائماً التغييرات «الصغيرة».

إن احتجت إلى تطوير كبير في الـ interface، فعادةً ما يكون أكثر أماناً التفكير بمصطلحات مثل:

  • إبقاء ICalculator
  • إضافة ICalculator2 للتوسّع غير المتوافق
  • ترك الـ class ينفّذ كليهما عند الحاجة

10.5 أبقِ أنواع الحدّ بسيطة

عند الحدّ المواجه لـ VBA، الأنواع المملّة عادةً ما تكون أصدقاءك.

الخيارات الآمنة الشائعة تتضمّن:

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

أسطح الأنواع شديدة الذكاء تميل إلى التقدّم في العمر بشكل سيّئ عند حدود COM.

10.6 لا تُعد البناء بينما يحمل Office الـ DLL

يستطيع Excel أو Access إبقاء الـ DLL محمّلة، ممّا يجعل إعادة البناء وإعادة التسجيل أكثر إحباطاً ممّا ينبغي.

إيقاع تطوير أهدأ هو:

  • إغلاق Office
  • إلغاء التسجيل عند الضرورة
  • إعادة البناء
  • التسجيل مرّة أخرى

11. الخلاصة

إن قصرت المشكلة على:

كشف COM + TLB مولّد بـ dscom + early binding مع types في VBA

فإنّ قصّة .NET 8 في الواقع منظّمة بشكل لا بأس به.

  • استخدم EnableComHosting=true
  • عرّف interfaces صريحة لـ COM
  • أبقِ classes على ClassInterfaceType.None
  • استخدم InterfaceIsDual للـ interface المواجه لـ VBA
  • ولّد TLB بـ dscom tlbexport
  • سجّل *.comhost.dll
  • سجّل *.tlb
  • أضف مرجع VBA واستخدم المكتبة بأنواع حقيقيّة

التقسيم الذهنيّ الأساسيّ هو:

  • *.comhost.dll -> نقطة دخول تفعيل COM
  • *.tlb -> معلومات النوع لـ VBA
  • *.dll -> التنفيذ

بمجرّد أن يصبح ذلك الفصل واضحاً، يصبح المسار من VBA إلى .NET 8 أسهل بكثير في التفكير فيه.

12. المراجع

مقالات ذات صلة

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ما يجب التحقّق منه عندما لا يعمل ActiveX على Office 2024 / Microsoft 365 - الترتيب العمليّ لتغطية التعطيل الافتراضيّ، 32bit / 64bit، تسجيل COM، DLL التابعة، ووصولًا إلى IE mode

دليل عمليّ لتشخيص توقّف ActiveX على Office 2024 و Microsoft 365، يرتّب الفحوص من التعطيل الافتراضيّ إلى تطابق 32bit / 64bit وتسجيل COM وD...

قراءة المقالة

دليل المراجعة الشاملة لـ VBA و Excel macro والأدوات الداخليّة استعدادًا لإيقاف VBScript - الجرد / الكشف الساكن / سجلّات التشغيل / اختيار البديل / الاختبار / النشر التدريجيّ

ملخّص عمليّ على صفحة واحدة لمسار الاستعداد لإيقاف VBScript تدريجيًّا: جرد VBA و Excel macro والأدوات الداخليّة، الكشف الساكن، تجميع سجلّا...

قراءة المقالة

أين يتصل هذا الموضوع

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

العودة إلى المدونة