فخاخ التسجيل وbitness في تطوير COM و OCX / ActiveX

· آخر تحديث: · · COM, ActiveX, OCX, Visual Studio, تطوير Windows, 32bit, 64bit, Interop

سجل التعديلات (3 تحديثات، آخر تحديث 2 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
أضيف شرح لـ DllSurrogate: لماذا لا تستطيع عملية 64-bit تحميل InprocServer32 بـ 32-bit حتى بعد نجاح regsvr32، وكيف أن إضافة AppID إلى الـ CLSID وقيمة DllSurrogate فارغة تحت ذلك الـ AppID تستضيف الـ DLL داخل dllhost.exe دون كتابة EXE جديد، ولماذا تتبع بنية dllhost.exe الذي يعمل بنية الـ DLL لا بنية العميل، ولماذا يفوز LocalServer32 المسجَّل على الوسيط، وكيف يتم التحقق من النتيجة. كما يوضّح النص أن الوسيط لا يلغي فرق البنية: ما يزول هو قيد العملية الواحدة فقط، وتصبح الاستدعاءات خارج العملية بتكلفة marshaling و IPC. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.21621504)
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621503)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

小村 豪 (2026). فخاخ التسجيل وbitness في تطوير COM و OCX / ActiveX. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621503 https://comcomponent.com/ar/blog/2026/04/15/001-com-ocx-activex-pitfalls-visual-studio-bitness-admin-rights/

DOI (أحدث نسخة)
10.5281/zenodo.21621503
DOI (هذه النسخة)
10.5281/zenodo.22240918

في العمل على مكوّنات COM و OCX / ActiveX، تتعثّر الفِرَق في الغالب لا في الشيفرة نفسها بل في الحدود المحيطة بـ بيئة التشغيل والتسجيل والاستضافة والصلاحيّات.

تبدو الأعراض النموذجيّة هكذا:

  • البناء ينجح، لكنّ التشغيل يفشل بـ 0x80040154
  • يعمل على جهاز المطوّر، لكن لا يعمل على جهاز PC آخر
  • يعمل في وقت التشغيل، لكنّ Visual Studio Designer وحده يموت
  • يعمل عند التشغيل بصلاحيّات المسؤول، لكنّه ينكسر بصلاحيّات عاديّة
  • تواصل تشغيل regsvr32، ومع ذلك لا يُحَلّ شيء

في كثير من الحالات، ليست هذه أخطاءً مستقلّة. إنّها علامات على أنّ شرطًا مسبقًا من شروط COM لا يصطفّ بشكل صحيح.

إن أردت ترتيب المصطلحات أوّلًا، فمن المفيد البدء بـ ما هي COM / ActiveX / OCX: الفروق والعلاقات.
يكمل هذا المقال من تلك النقطة ويركّز على الأماكن التي يتعثّر الناس فيها فعلًا في الممارسة، بما في ذلك مشاكل bitness الخاصّة بـ Visual Studio وصلاحيّات المسؤول.

1. الإجابة الموجزة

إن قُلْنَاها بطريقة مفيدة في العمل الفعليّ، فإنّ النقاط الجوهريّة هي التالية.

  1. معظم إخفاقات COM / OCX / ActiveX سببها أقلّه منطق العمل، وأكثره عدم تطابق في bitness (32-bit / 64-bit) وموقع التسجيل وعمليّة المضيف والصلاحيّات.
  2. Visual Studio 2022 هو عمليّة 64-bit، لذا فإنّ مسارات التكامل في وقت التصميم التي كانت تعمل سابقًا تحت افتراضات 32-bit القديمة قد تفشل الآن كما هي.12
  3. regsvr32 ليس أمرًا سحريًّا يُسجّل كلّ شيء. إنّه لخوادم COM الأصليّة من نوع in-process مثل DLL و OCX. إن كنت تكشف تجميعات .NET Framework لـ COM، فالأداة هي Regasm.exe؛ وفي .NET 5+ / .NET 6+ / .NET 8+، يصبح المسار تسجيل الـ .comhost.dll المُولَّد.3456
  4. “يعمل عند التشغيل كمسؤول، فلا بدّ أنّه على ما يرام” خطير. كثيرًا ما يعني ذلك فقط أنّ المكوّن مرئيّ بالصدفة من خلال تسجيل per-user، أو أنّ شيئًا كان ينبغي تثبيته بشكل صحيح قد سُجِّل يدويًّا فقط على جهاز المطوّر.378

لذا، عند التعامل مع COM / OCX / ActiveX، الأكثر أمانًا أن تبدأ من هذه المحاور الأربعة:

  • أيّ عمليّة تستضيفه
  • هل تلك العمليّة 32-bit أم 64-bit
  • أين سُجِّل (HKCU / HKLM، عرض 32-bit / عرض 64-bit)
  • هل العمليّة أو مسار التنفيذ يتطلّب فعلًا صلاحيّات المسؤول

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 34، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. بالعمل عكسيًّا من الأعراض، يبدو الأمر عادةً هكذا

العَرَض أوّل ما يُشتبه به السبب الحقيقيّ في الغالب
0x80040154 Class not registered غير مُسجَّل في الواقع كثيرًا ما يكون “مُسجَّلًا فقط في عرض الـ bitness الآخر” أو “مُسجَّلًا فقط لذلك المستخدم”
DllRegisterServer failed: 0x80070005 صلاحيّات غير كافية محاولة التسجيل كمستخدم قياسيّ، أو تشغيل تسجيل post-build دون رفع
ينكسر VS2022 Designer وحده قيود Designer لا يستطيع Visual Studio 64-bit تحميل COM / ActiveX 32-bit مباشرةً
يعمل فقط عند التشغيل كمسؤول مشكلة صلاحيّات كثيرًا ما تكون المشكلة الحقيقيّة عدم تطابق نطاق التسجيل أو تصميم التثبيت، لا الصلاحيّات بحدّ ذاتها
تطبيق 64-bit لا يستطيع استدعاء OCX 32-bit قيود معماريّة في COM يجب أن تتطابق خوادم in-process مع bitness المضيف
يعمل على خيط الـ UI لكنّه يتجمّد على خيط خلفيّ نموذج خيوط تُنتَهَك افتراضات STA / MTA أو CoInitializeEx أو حلقة الرسائل

رسميًّا، 0x80040154 هو REGDB_E_CLASSNOTREG، أي حرفيًّا Class not registered.9
و 0x80070005 من regsvr32 موثّق أيضًا كحالة تنقص فيها صلاحيّات المسؤول ولا تستطيع العمليّة الكتابة إلى registry أو System32.10

النقطة المهمّة هي عدم الثقة باسم الخطأ حرفيًّا أكثر من اللازم.
على سبيل المثال، Class not registered لا يعني دائمًا “غير مُسجَّل تمامًا”. قد يحدث أيضًا حين يكون الفصل مُسجَّلًا فقط في عرض registry آخر أو فقط في موقع per-user لا تراه العمليّة الحاليّة.117

3. bitness الخاصّ بـ Visual Studio فخّ كبير

3.1. صار Visual Studio 2022 بـ 64-bit

هذا أحد أكبر الفخاخ في تطوير COM / ActiveX الحاليّ.

Visual Studio 2022 هو 64-bit فقط لـ devenv.exe.1
هذا يعني أنّ جانب Visual Studio من تجربة وقت التصميم لـ WinForms لا يستطيع تحميل المكوّنات 32-bit مباشرةً. تذكر Microsoft صراحةً أنّ Visual Studio 2022 هو عمليّة 64-bit ولذلك لا يمكنه تحميل مكوّنات .NET / COM / ActiveX 32-bit مباشرةً.2

بمعنى آخر، الإعداد الذي كان يعمل تحت افتراضات مثل:

  • المشروع x86
  • ضابط ActiveX المرجعيّ أيضًا x86
  • Visual Studio نفسه 32-bit

يتحوّل إلى شيء مختلف في VS2022:

  • لا يزال تطبيق وقت التشغيل يستطيع العمل كـ x86
  • لكنّ Designer يعمل الآن داخل Visual Studio 64-bit

ينشأ عن ذلك عدم تطابق.
والنتيجة هي الحالة المزعجة بشكل خاصّ حيث لا يزال وقت التشغيل يعمل، لكنّ Designer وحده يموت.2

3.2. التحوّل إلى AnyCPU لا يصلح الأمر تلقائيًّا

هذا سوء فهم شائع آخر.

AnyCPU ليس سحرًا يجعل كلّ تبعيّة محايدة أيضًا.
توضّح Microsoft أيضًا أنّه حتى إن بدا المكوّن نفسه AnyCPU، فإنّ وقت التصميم في Visual Studio 2022 لا يزال ينكسر إن كان شيء أسفله يعتمد في النهاية على COM / ActiveX 32-bit.2

لذا، حين لا يجعل التحوّل إلى AnyCPU الخطأ يختفي، يكون من الأسرع غالبًا أن تشتبه بـ:

  • ما إن كانت تلك التجميعة تعتمد على شيفرة أصليّة 32-bit في الأسفل
  • ما إن كان ضابط ActiveX / OCX مثبّتًا على x86
  • ما إن كانت هناك شيفرة تُحمَّل فقط في وقت Designer

3.3. فخّ System32 و SysWOW64

في Windows x64، تتباعد الأسماء عن الواقع بما يكفي لإرباك الناس.

تشرح Microsoft Learn أنّ %windir%\\System32 على Windows x64 مخصّص لـ التطبيقات 64-bit. أمّا الجانب 32-bit فيُعاد توجيهه إلى مكان آخر بواسطة WOW64 file-system redirector.12
يعمل registry بشكل مشابه: WOW64 registry redirector يقدّم عروضًا منطقيّة مختلفة للشيفرة 32-bit و 64-bit.11

لهذا السبب من الأكثر أمانًا، عند تشخيص المشاكل محلّيًّا، أن تجعل أيّ bitness من regsvr32 تستخدم صريحًا تمامًا.

# When you want to register a 64-bit DLL / OCX
C:\Windows\System32\regsvr32.exe vendor.ocx

# When you want to register a 32-bit DLL / OCX on x64 Windows
C:\Windows\SysWOW64\regsvr32.exe vendor.ocx

الجانب المزعج من هذا الفخّ هو أنّك قد تُسجِّل في الجانب الخطأ وتشعر مع ذلك أنّك سجّلته بنجاح.
تصبح النتيجة النهائيّة:

  • نجح regsvr32
  • لكنّ التطبيق لا يزال يحصل على 0x80040154
  • يبدو registry وكأنّ المُدخل موجود
  • لكنّك تنظر إلى العرض الخطأ

هذا النمط شائع للغاية.1113

3.4. لا يستطيع regsvr32 تسجيل كلّ شيء

هذا أيضًا يُساء فهمه على نطاق واسع.

عادةً تُصدِّر خوادم COM الأصليّة من نوع in-process DllRegisterServer / DllUnregisterServer وتدعم التسجيل الذاتيّ.3
regsvr32 هو الأداة لحالات DLL / OCX تلك.4

في المقابل، إن كنت تريد كشف تجميعة .NET Framework لـ COM، فإنّ الأداة العاديّة هي Regasm.exe. توضّح Microsoft Learn صراحةً أنّ Regasm.exe يُستخدم لتسجيل التجميعات لاستخدام COM.514

ثمّ تتغيّر القصّة مرّة أخرى في .NET 5+ / .NET 6+ / .NET 8+: مع <EnableComHosting>true</EnableComHosting>، يُولّد البناء *.comhost.dll، وذلك الـ DLL المُولَّد هو ما تُسجّله بـ regsvr32.6

بشكل تقريبيّ، الانقسام هو:

ما تريد كشفه طريقة التسجيل النموذجيّة
native C++ DLL / OCX regsvr32
تجميعة .NET Framework مكشوفة لـ COM Regasm.exe
كشف COM في .NET 5+ / 6+ / 8+ تسجيل الـ .comhost.dll المُولَّد بـ regsvr32

يُشغَّل Regasm.exe من Developer Command Prompt / Developer PowerShell. أكثر ما يُستخدَم هو الأشكال الثلاثة التالية.14

:: 1) تسجيل الأصناف العامّة داخل التجميعة
regasm myTest.dll

:: 2) توليد type library وتسجيله أيضاً (للمستهلكين الذين يحتاجون TLB، مثل VBA / VB6)
regasm myTest.dll /tlb:myTest.tlb

:: 3) كتابة المحتوى إلى ملفّ .reg للفحص دون تغيير الـ registry مباشرة
regasm myTest.dll /regfile:myTest.reg

الإلغاء يتمّ بـ /unregister. type library المسجَّل بـ /tlb يُلغى بتحديد /tlb و /unregister معاً.

regasm myTest.dll /tlb:myTest.tlb /unregister

هنا قيدان يسهل الوقوع فيهما.14

  • التجميعة التي لا تُدخَل في GAC تحتاج /codebase. لأنّ /codebase يسجّل مسار الملفّ وقت التسجيل في الـ registry، فإنّ نقل التجميعة لاحقاً يجعل التنشيط يفشل. كما توصي Microsoft بقوّة أن تكون أيّ تجميعة تُسجَّل بـ /codebase ذات اسم صارم (strong name).
  • لا يمكن الجمع بين /regfile و /unregister أو /tlb. وما يكتبه /regfile هو إدخالات الأصناف المُدارة فقط؛ لا يُخرَج TypeLibID و InterfaceID. إن افترضت أنّ «توزيع ملفّ .reg يكفي لإتمام التسجيل»، فلن يكفي.

عندما تختلط هذه، يكون نمط الإخفاق الشائع جدًّا:

  • تشغيل regsvr32 على DLL مُدارة
  • بوضوح، DllRegisterServer مفقود
  • الاستنتاج بأنّ الـ DLL لا بدّ أنّها معطوبة

ذلك الاستنتاج عادةً خاطئ.

في العوالم التي تحتاج type library أيضًا، مثل VBA و VB6 وبعض مستهلكي early-binding، فإنّ توليد TLB وتسجيله هو مسألة منفصلة أخرى. في .NET Framework، يستطيع Regasm.exe /tlb توليد type library وتسجيلها، وتشرح Microsoft أيضًا أنّ “تسجيل النوع” و “تسجيل type library” نشاطان منفصلان.15

3.5. حين تريد إخراج in-proc بـ 32-bit من عمليّة 64-bit - DllSurrogate

كان 3.3 و 3.4 عن سؤال “هل موقع التسجيل ووسيلته متطابقان”.
هنا نضيف مخرجًا واحدًا لحالة التسجيل صحيح لكنّ الـ bitness لا يتطابق.

الخلاصة أوّلًا.

  • حتى إن نجح regsvr32، فإنّ العمليّة 64-bit لا تستطيع تحميل InprocServer32 الخاصّ بـ 32-bit. هذا ليس خللًا في التسجيل، بل هو شرط in-proc نفسه.
  • إن أردت استخدام تلك الـ DLL من الجانب 64-bit دون كتابة EXE جديد، فأصغر علاج هو إضافة قيمة AppID إلى الـ CLSID، وكتابة DllSurrogate بسلسلة فارغة تحت مفتاح AppID ذاك.16
  • والذي يُشغَّل حينها ليس bitness جانب العميل، بل dllhost.exe بالـ bitness الخاصّ بالـ DLL الذي يشير إليه InprocServer32.

لماذا يستحيل الأمر ما دام in-proc

إنّ “تطبيق 64-bit لا يستطيع استدعاء OCX 32-bit” المذكور في جدول الأعراض في الفصل 2 يرتدي وجه 0x80040154 نفسه، لكنّ مضمونه مختلف.

InprocServer32 هو حرفيًّا تحديد لـ “خادم يعمل داخل عمليّة المستدعي”. ولن يحدث أبدًا أن تحمّل عمليّة 64-bit الـ DLL بـ 32-bit المكتوبة هناك، مهما أصلحت في الـ registry. وحتّى انقسام عروض الـ registry الذي رأيناه في 3.3 يعود في أصله إلى هذا القيد.

لذا فما يلي ليس عن “إصلاح التسجيل”، بل عن “إخراجه إلى عمليّة أخرى”.

ما الذي يفعله الـ surrogate

DllSurrogate هو تحديدٌ يجعل خادم الـ DLL من نوع in-proc يُحمَّل داخل عمليّة surrogate ويُقدَّم بوصفه Local Server.17
وإن جعلت القيمة سلسلة فارغة، فسيُستخدم الـ surrogate الافتراضيّ المرفق مع Windows (dllhost.exe).18

إن كانت الـ DLL بـ 32-bit، فسيُشغَّل surrogate بـ 32-bit، وتُحمَّل الـ DLL داخله in-proc كما كانت دائمًا. أمّا العميل 64-bit فيتعامل مع الكائن من خارج تلك العمليّة عبر proxy.

هذه النقطة يُساء فهمها بسهولة، فلنكتبها بوضوح.
الـ surrogate لا يمحو الفرق في الـ bitness. الذي يختفي هو قيد “وجوب التحميل في العمليّة نفسها” فقط. تصبح الاستدعاءات out-of-proc، وتُضاف إليها تكلفة الـ marshaling والاتّصال بين العمليّات. وكشرط مسبق، يلزم أيضًا أن تكون الأنواع المتبادَلة قابلة للـ marshaling (إمّا IDispatch، أو proxy / stub مُسجَّل، أو نوع يستطيع الـ marshaler القياسيّ التعامل معه).

ونقطة أخرى. ما يسهل التعامل معه عبر الـ surrogate هو كائنات automation التي تكتمل باستدعاءات الدوالّ والأحداث. أمّا الـ OCX المرئيّ الذي تضعه على نموذج ليرسم نفسه فيفترض أن تكون نافذته داخل عمليّة المضيف، ولذلك لا يحلّه هذا العلاج وحده.

هل ينتهي التفعيل إلى in-proc أم إلى الـ surrogateرسم يوضّح أنّه عند الطلب بـ CLSCTX يتضمّن in-proc، إن وُجد InprocServer32 في CLSID ضمن العرض نفسه الخاصّ بالعميل فيُحمَّل أوّلًا in-proc، وإن لم يوجد وكان هناك تسجيل لخادم EXE فيُشغَّل ذلك قبل الـ surrogate، وإن لم يوجد تسجيل EXE ووُجد DllSurrogate فارغ فيُشغَّل dllhost بالـ bitness الخاصّ بالـ DLL، وإن لم يوجد أيٌّ منها فلا يمكن التفعيل.يوجدلا يوجديوجدلا يوجديوجدلا يوجدالعميل يطلب التفعيلالطلب بـ CLSCTX يتضمّن in-procهل يوجد InprocServer32 في CLSID ضمن العرض نفسهيُحمَّل in-proc كما كان دائمًاهل يوجد تسجيل لخادم EXEيُشغَّل EXE قبل الـ surrogateهل يوجد DllSurrogate فارغيُشغَّل dllhost بالـ bitness الخاصّ بالـ DLLيتعذّر التفعيل 0x80040154

الشكل 1: تتفرّع عملية التنشيط أولاً بحسب ظهور InprocServer32 بالبنية نفسها، ولا تنتقل إلى خادم EXE ثم إلى DllSurrogate الفارغ إلا عند غيابه.

الحدّ الأدنى من التسجيل

تُعدِّد Microsoft Learn شروط تحميل خادم الـ DLL داخل surrogate على النحو التالي.16

  1. في مفتاح CLSID توجد قيمة AppID، ويوجد مفتاح AppID المقابل لها
  2. استدعاء التفعيل يرفع CLSCTX_LOCAL_SERVER، و لا يوجد في مفتاح CLSID أيٌّ من LocalServer32 / LocalServer / LocalService
  3. يوجد InprocServer32 في مفتاح CLSID
  4. الـ DLL التي يشير إليها InprocServer32 موجودة فعلًا
  5. توجد قيمة DllSurrogate تحت مفتاح AppID

وإن أردت تشغيل تلك الـ DLL وحدها في surrogate واحد، فإنّ الطريقة التي توصي بها Microsoft هي جعل الـ AppID بالـ GUID نفسه الخاصّ بالـ CLSID.16

ثلاثة أسطر تكفي للتسجيل. المثال أدناه لحالة استخدام COM DLL بـ 32-bit من مضيف 64-bit، والـ GUID فيه مجرّد عنصر نائب. والشرط المسبق هو أن يكون InprocServer32 قد أُدخل مسبقًا بـ regsvr32 من جانب SysWOW64 (انظر 3.3).

:: نفّذ هذا في موجّه أوامر بصلاحيّات المسؤول
set CLSID={11111111-2222-3333-4444-555555555555}
set APPID={11111111-2222-3333-4444-555555555555}

:: 1) أضف AppID إلى الـ CLSID. ما تحت CLSID منفصل بين 32 و 64، لذا حدّد العرض صراحةً
::    وللاتّجاه المعاكس، أي استخدام COM DLL بـ 64-bit من مضيف 32-bit، اقرأ هذا على أنّه /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /v AppID /t REG_SZ /d "%APPID%" /f /reg:32

:: 2) أنشئ مفتاح AppID المقابل. Classes\AppID مشترك بين 32 و 64، فلا حاجة إلى تحديد العرض
reg add "HKLM\SOFTWARE\Classes\AppID\%APPID%" /f

:: 3) اجعل DllSurrogate من نوع REG_SZ بقيمة فارغة. إن حذفت /d فسيُنشأ بسلسلة فارغة
reg add "HKLM\SOFTWARE\Classes\AppID\%APPID%" /v DllSurrogate /t REG_SZ /f

لماذا تكون سلسلة فارغة

DllSurrogate من نوع REG_SZ، و القيمة نفسها هي مسار الـ surrogate. فإن جعلتها سلسلة فارغة (أو NULL) استُخدم الـ surrogate الافتراضيّ للنظام، وإن كتبت مسارًا فسيحاول تشغيل الـ surrogate المخصّص الموجود في ذلك المسار.1718

أي أنّ كتابة مسار dllhost.exe بنيّة المساعدة تأتي بنتيجة عكسيّة، لأنّ الإفراغ نفسه هو التحديد الذي يعني “استخدم الافتراضيّ”.

وللعلم، فإنّ HKLM\SOFTWARE\Classes\AppID مفتاح مشترك بين 32-bit و 64-bit منذ Windows 7 / Windows Server 2008 R2، ولا تمييز فيه بين العروض.19
في المقابل، ما تحت CLSID تنقسم فيه العروض، لذا إن كان الخادم بـ 32-bit فيجب وضع قيمة AppID على جانب CLSID في العرض 32-bit. وحين لا ينجح الأمر، تحقّق من هذين الموضعين معًا دائمًا.

قد يبدو هذا متناقضًا مع ما في 3.3 و 6.3 من أنّ “التسجيل على الجانب 32-bit فقط يعني 0x80040154 من عمليّة 64-bit”، لكنّ ذاك كلام عن in-proc. أمّا في التفعيل out-of-proc، فحين لا يبدي العميل ولا الخادم رغبةً في bitness معيّن، يبحث COM عن خادم بالـ bitness المناسب للعميل، وإن لم يجده شغّل الخادم ذا الـ bitness الآخر.20
ولهذا تصحّ هذه الخطوات مع إبقاء جانب CLSID في العرض 32-bit. لا حاجة إلى نسخ InprocServer32 إلى العرض 64-bit.

الـ bitness الخاصّ بـ dllhost.exe الذي يُشغَّل

هنا يقع أكثر سوء الفهم شيوعًا.
يتحدّد الـ bitness الخاصّ بالـ surrogate لا من جانب العميل، بل من جانب الـ DLL التي يشير إليها InprocServer32. وهذا طبيعيّ، لأنّ الـ DLL تُحمَّل in-proc داخل الـ surrogate.

COM DLL التي تُحمَّل بالداخل dllhost.exe الذي يُشغَّل
32-bit %SystemRoot%\SysWOW64\dllhost.exe
64-bit %SystemRoot%\System32\dllhost.exe

وإن نظرت إلى سطر الأوامر في Task Manager أو Process Explorer، فستجده قائمًا بالشكل dllhost.exe /Processid:{...}. والـ GUID هنا هو AppID وليس CLSID. فإن بحثت عنه على أنّه CLSID فلن تجده، فانتبه لذلك.

حين يوجد LocalServer32 يفوز الـ EXE

تنصّ Microsoft Learn صراحةً على أنّه حين يوجد LocalServer / LocalServer32 / LocalService، فإنّ تشغيل خادم EXE أو خدمة له الأولويّة دائمًا على تحميل الـ DLL في surrogate.16

أي أنّ الـ surrogate هو “المخرج حين لا يوجد خادم EXE”. وإضافة DllSurrogate إلى CLSID مُسجَّل له خادم EXE بالفعل لن تجعل الـ surrogate يُستخدم. والأولويّة هنا هي في مواجهة الـ surrogate فقط، لا في مواجهة in-proc.

ومن المطمئن أيضًا أن تحيط بأثر ذلك على المستدعين الحاليّين.
فحتى مع إضافة DllSurrogate، تبقى العملاء الذين ينشئون الكائن بـ CLSCTX يتضمّن in-proc على حالهم in-proc كما كانوا، ما دام الـ bitness متطابقًا. ذلك أنّ تمرير عدّة قيم CLSCTX مجموعةً بـ OR يُجرَّب بترتيب التعداد (in-proc → local → remote)، وفي مرحلة in-proc يُستخدم مفتاح InprocServer32 إن كان موجودًا.20
وينطبق هذا على الاستدعاءات التي تمرّر in-proc و local معًا مثل CLSCTX_ALL أو CLSCTX_SERVER. وعلى العكس، المواضع التي تستدعي بـ CLSCTX_INPROC_SERVER وحده لن تتحوّل إلى الـ surrogate حتّى بعد إضافة DllSurrogate.

ومن منظور العميل 64-bit، فإنّ InprocServer32 الذي وُضع في العرض 32-bit غير موجود في العرض 64-bit. فتمرّ مرحلة in-proc بلا توقّف بوصفها “لا يوجد مفتاح مطابق”، ويمضي التفعيل مباشرةً إلى جانب local (الـ surrogate). فالأمر ليس محاولةً فاشلةً لتحميل DLL بـ bitness مختلف.2019

طريقة التحقّق

للتأكّد ممّا إن كان قد أُدخل فعلًا، الأضمن أن تقرأه مرّة أخرى مع تحديد العرض صراحةً.

reg query "HKLM\SOFTWARE\Classes\CLSID\{11111111-2222-3333-4444-555555555555}\InprocServer32" /ve /reg:32
reg query "HKLM\SOFTWARE\Classes\CLSID\{11111111-2222-3333-4444-555555555555}" /v AppID /reg:32
reg query "HKLM\SOFTWARE\Classes\AppID\{11111111-2222-3333-4444-555555555555}" /v DllSurrogate

لا تحذف السطر الأوّل. فـ reg add يُنشئ المفتاح المحدَّد إن لم يكن موجودًا، ولذلك إن أخطأت في كتابة الـ GUID أو سجّلت في عرض آخر، فسينشأ مفتاح CLSID فارغ لا يحمل سوى قيمة AppID، وسيمرّ السطر الثاني بنجاح. ولا يكون تحقّقك تحقّقًا فعليًّا إلّا حين ترى أنّ InprocServer32 موجود في العرض نفسه.

والنتيجة الصحيحة لـ DllSurrogate هي أن يظهر وقيمته فارغة. بعد ذلك أنشئ الكائن من عميل 64-bit وانظر هل يُشغَّل dllhost.exe من جانب SysWOW64.

  • لا يزال 0x80040154 → راجع ما إن كانت قيمة AppID موضوعة على جانب CLSID في العرض 32-bit، وما إن كانت الـ DLL التي يشير إليها InprocServer32 موجودة فعلًا
  • يُشغَّل dllhost.exe لكنّ الاستدعاء ينهار → تحقّق من شروط الـ marshaling (IDispatch / proxy-stub / الـ marshaler القياسيّ)
  • يُشغَّل EXE آخر → لا يزال LocalServer32 أو ما شابهه موجودًا على ذلك الـ CLSID

إلى هنا ينتهي الكلام عن “التسجيل”

أمّا هل يكفي الـ surrogate، أم ينبغي كتابة helper EXE خاصّ بك على الجانب 32-bit، فذلك ليس مسألة تسجيل بل مسألة اختيار البنية.
وقد رتّبنا ذلك في القسم 5.2 «الرغبة في إدخال OCX بـ 32-bit إلى الجانب 64-bit» من كيف تتعامل مع ActiveX / OCX اليوم - جدول قرار للإبقاء أو التغليف أو الاستبدال.

4. مشاكل صلاحيّات المسؤول فخّ كبير آخر

4.1. التسجيل موصول بـ post-build وينجح فقط تحت المسؤول

في بناءات C++ مع Visual Studio، من الطبيعيّ تمامًا بمعنى آليّ استدعاء regsvr32.exe من build events أو custom build steps. تُظهر Microsoft Learn حتى أمثلة post-build تستخدم regsvr32.exe.21

لكنّ الممكن و الآمن ليسا الشيء نفسه.

عندما يشغّل مستخدم قياسيّ regsvr32، قد يفشل بـ 0x80070005 لأنّه لا يستطيع الكتابة إلى registry أو System32. تصف KB من Microsoft أيضًا السبب بأنّه نقص في صلاحيّات المسؤول.10

ما يحدث غالبًا في الممارسة:

  • Visual Studio المُشغَّل بشكل عاديّ يبني الثنائيّات بنجاح
  • لكنّ تسجيل post-build وحده يفشل
  • يُغفل الإخفاق
  • لا يزال تسجيل قديم موجودًا من قبل، فيعمل التطبيق مصادفةً محلّيًّا
  • في بيئة نظيفة، يفشل بطبيعة الحال

في مشروع من هذا النوع، الافتراضيّ الأكثر أمانًا هو فصل البناء عن التسجيل.

  • يجب أن يُنتِج البناء ثنائيّات فقط
  • يجب أن يكون التسجيل خطوة تثبيت / سكريبتًا / خطوة installer واضحة
  • في CI، ينبغي أن تكون خطوة “تتطلّب التسجيل” مهمّة منفصلة عن البناء

هذا الفصل وحده يزيل عددًا مدهشًا من الحوادث.

4.2. خلط التسجيل per-user و per-machine يُعطّب الأمور

إن كانت ذاكرتك الوحيدة هي “COM ينظر في HKCR”، فقد تتعثّر هنا.

تشرح Microsoft Learn أنّ COM ينظر فعلًا في HKEY_CURRENT_USER\\Software\\Classes أوّلًا، ثمّ يتعامل مع المعلومات الخاصّة بالجهاز كلّه بعد ذلك.3
كما تشرح أنّ HKEY_CLASSES_ROOT هو عرض مدمج لـ HKLM\\Software\\Classes و HKCU\\Software\\Classes.722

هذا يعني أنّ مواقف كهذه طبيعيّة تمامًا:

  • يسجّل المطوّر A يدويًّا تحت مستخدمه
  • يعمل لدى المطوّر A
  • لا يعمل لدى المطوّر B
  • يفشل أيضًا لحساب خدمة
  • يتغيّر السلوك مرّة أخرى عند التشغيل كمسؤول

كما تذكر Microsoft أنّ التطبيقات التي تتطلّب صلاحيّات المسؤول ينبغي أن تسجّل كائنات COM التابعة في مخزن تكوين COM per-machine أثناء التثبيت.87

لذا في فريق تطوير حقيقيّ، يجب أن يكون التمييز التالي صريحًا:

  • هل هذا تسجيل per-user للمطوّر فقط
  • أم تسجيل إنتاجيّ per-machine لكلّ مستخدم على الـ PC
  • أم تسجيل يجب أن يكون مرئيًّا للخدمات أو التطبيقات المرفوعة

إن بقي ذلك ضبابيًّا، فستحصل على ارتباك كلاسيكيّ مثل “يعمل فقط كمسؤول” أو “يعمل من استضافة Explorer extension، لكن ليس من الخدمة”.

4.3. تشغيل Visual Studio دائمًا كمسؤول ليس إصلاحًا حقيقيًّا

ثمّة حالات يكون فيها الرفع ضروريًّا.
لكن إن كان Visual Studio يعمل دائمًا كمسؤول، فقد يخفي مشاكل ينبغي حلّها فعلًا بواسطة installer أو سكريبت تسجيل صريح.

كما يغيّر Visual Studio نفسه سلوكه في الوضع المرفوع. توثّق Microsoft Learn إعدادات حيث تُعطَّل الامتدادات per-user عند تشغيل Visual Studio مرفوعًا.23

لذا، كنموذج تشغيل، هذا عادةً أكثر أمانًا:

  • قُمْ بالتطوير العاديّ تحت صلاحيّات المستخدم العاديّ
  • شغّل فقط الخطوات التي تتطلّب فعلًا تسجيلًا بـ Developer Command Prompt / PowerShell / installer مرفوع صراحةً
  • إن تكرّر شيء كمسؤول فقط، فاعتبر ذلك الافتراض نفسه حقيقة تصميميّة ينبغي توثيقها

هذا يُسبّب عادةً مشاكل أقلّ لاحقًا.

5. لـ ActiveX / OCX فخاخها الإضافيّة الخاصّة

5.1. قد تختلف رخص وقت التصميم عن رخص وقت التشغيل

جزء مزعج بهدوء من العمل على OCX / ActiveX هو الترخيص.

خصوصًا لضوابط ActiveX الأقدم، قد تكون رخصة وقت التصميم و رخصة وقت التشغيل منفصلتين. توثيق MFC ActiveX يشرح أيضًا آليّات حيث تفصل ملفّات الرخصة أو مفاتيح الرخصة استخدام وقت التصميم عن وقت التشغيل.2425

ينشأ عن ذلك مواقف مثل:

  • استخدام وقت التشغيل يعمل
  • لكنّ وضع الضابط على نموذج يقول إنّه لا توجد رخصة
  • جهاز المطوّر A يستطيع وضعه
  • جهاز المطوّر B لا يستطيع

أخطاء مثل “license information for this component not found” أو “you don’t have an appropriate license” ليست غير معتادة في بيئات ActiveX الأقدم.26

5.2. عند وصوله إلى داخل WinForms، يكون هناك بالفعل طبقة مغلِّفة

عندما يستخدم WinForms ActiveX، فإنّ Windows Forms لا يستضيف ضابط ActiveX خامًا تمامًا.
كما تشرح Microsoft Learn في توثيق Aximp.exe، يُولّد ActiveX Control Importer مغلِّفًا لـ WinForms من type library الخاصّة بـ COM، ثمّ يعمل WinForms مع ذلك من خلال ضابط مبنيّ على AxHost.2728

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

  1. الـ OCX / ActiveX الأصليّ نفسه
  2. type library
  3. الـ interop / wrapper المُولَّد
  4. WinForms Designer / runtime

لهذا تحدث أمور كهذه:

  • استبدال OCX المورّد يغيّر تواقيع الأحداث
  • إعادة إضافة المرجع يُعيد توليد المغلِّفات ويُنشئ diff كبيرًا
  • تُولّد آلات مختلفة نتائج interop مختلفة قليلًا

رؤيته مدرجًا في Choose Toolbox Items لا تعني أنّك بأمان.
من الأفضل التحقّق مما إن كان Designer يستطيع وضعه، وما إن كانت أحداث وقت التشغيل تصل بشكل صحيح، وما إن كانت البيئة المنشورة تعمل بما في ذلك المغلِّف كفحوص منفصلة.

5.3. إن استهنت بـ STA / MTA وحلقة الرسائل، تتجمّد الأمور

أوّلًا نعرّف ثلاثة مصطلحات فقط.

المصطلح المعنى
الـ apartment مجموعة منطقيّة تجمع كائنات COM والخيوط تحت قاعدة التزامن نفسها. لا ينتمي الكائن الواحد إلّا إلى apartment واحد
STA (single-threaded apartment) apartment ينتمي إليه خيط واحد فقط. استدعاء ذلك الكائن يُنفَّذ حتمًا على ذلك الخيط الواحد
marshaling آليّة يمرّر بها COM الاستدعاء حين تُمرَّر مؤشّر الواجهة عبر حدود الـ apartment. يمرّ عبر proxy و stub

يلزم تهيئة COM لكلّ خيط يستخدمه بـ CoInitializeEx. تنصّ Microsoft Learn صراحةً على أنّ كلّ خيط يستخدم COM يجب أن يستدعي CoInitializeEx لنفسه.29

وفي STA (single-threaded apartment) تلزم حلقة رسائل.2930

لماذا يتجمّد الأمر إن لم توجد حلقة رسائل

حفظ الخلاصة وحدها لا يكفي للتطبيق، لذلك نفتح الآليّة درجة واحدة.

ينشئ COM لكلّ STA نافذة مخفيّة واحدة من صنف النوافذ OleMainThreadWndClass. حين يستدعي apartment آخر ذلك الكائن، لا يصير الاستدعاء استدعاء دالّة مباشرًا، بل يصل كرسالة نافذة موجَّهة إلى هذه النافذة المخفيّة. حين يأخذ خيط STA الرسالة ويوزّعها، يستدعي إجراء النافذة دالّة الواجهة المقابلة.30

أي أنّ حلقة الرسائل ليست «أسلوبًا يُستحسن وجوده»، بل آليّة توصيل الاستدعاء نفسها.

OCX / ActiveXخيط STAطابور رسائل النافذة المخفيّةالمستدعي من خيط آخرOCX / ActiveXخيط STAطابور رسائل النافذة المخفيّةالمستدعي من خيط آخرإن توقّفت حلقة الرسائللا يحدث هذا الأخذ ويبقى المستدعي منتظرًايُكدَّس استدعاء الدالّة كرسالةحلقة الرسائل تأخذ الرسالةإجراء النافذة يستدعي الدالّة المقابلةتُعاد القيمة

الشكل 2: الاستدعاء من خيط آخر يصل كرسالة إلى النافذة المخفيّة، ولا يُمرَّر إلى الكائن إلّا حين تأخذه حلقة الرسائل.

لذلك يتجمّد الأمر إن حُظر خيط STA. كتابات مثل Task.Wait() أو Task.Result على خيط الواجهة، أو الانتظار بـ WaitOne، توقف أخذ الرسائل، فتتوقّف توصيل استدعاءات COM بين الـ apartment والـ callback، ويحدث جمود.30

للسبب نفسه، لا يجوز نسخ مؤشّر واجهة كائن STA إلى خيط آخر كما هو. النسخ يجعل استدعاءً كان ينبغي توصيله إلى خيط STA يُنفَّذ مباشرة على خيط آخر، فيحدث تزامن لم يفترضه جانب الكائن. عند عبور الـ apartment يُجرى marshaling بـ CoMarshalInterThreadInterfaceInStream و CoGetInterfaceAndReleaseStream.30

وخصوصًا مكوّنات OCX / ActiveX الموجَّهة للواجهة كثيرًا ما تفترض STA، فيصير العطل المزعج الآتي.

  • يعمل على خيط الواجهة
  • يتجمّد إن أُلقي إلى Task.Run أو تجمّع الخيوط
  • لا تعود الأحداث
  • يتكرّر أحيانًا فقط

وفوق ذلك في STA يوجد أيضًا افتراض أنّك لا تنسخ مؤشّر الواجهة إلى خيط آخر كما هو، وإن لزم فاجعل marshaling.2930

هذا النوع من الأعطال ليس لطيفًا مثل 0x80040154، ولا يُرى إلّا كـ تجمّد / لا يعود / يسقط أحيانًا، فيأكل وقتًا بقدر أعطال السجلّ.

6. ترتيب عمليّ لتشخيص المشاكل يعمل في الميدان

في الممارسة، يكون من الأسرع غالبًا عدم الغوص العميق فورًا، بل تقطيع المشكلة بهذا الترتيب.

6.1. أوّلًا، ثبّت أيّ عمليّة بأيّ bitness

هذا أوّل شيء تتحقّق منه.

  • ما هي عمليّة المضيف (Visual Studio Designer / تطبيقك الخاصّ / Office / Access / Explorer / مضيف شبيه بالمتصفّح)
  • هل هذا المضيف 32-bit أم 64-bit
  • هل الـ DLL / OCX المستهدف 32-bit أم 64-bit
  • هل هو in-process أم out-of-process

إن بقي ذلك ضبابيًّا وبدأت من registry، فستضيع عادةً.

6.2. ثمّ تحقّق من نوع التسجيل الصحيح فعلًا

السؤال التالي هو ما الذي يجب تسجيله بأيّ شيء.

  • DLL / OCX أصليّ → regsvr32
  • كشف COM لـ .NET Framework → Regasm.exe
  • كشف COM لـ .NET 5+ / 6+ / 8+ → .comhost.dll
  • DLL بدون دعم تسجيل ذاتيّ → ليس regsvr32

هذا التصنيف وحده يمنع الكثير من الأخطاء غير المضطرّة.

6.3. بعد ذلك، تحقّق من المكان الذي سُجِّل فيه فعلًا

لمحة بسيطة على HKCR لا تكفي.

تحتاج غالبًا إلى النظر إلى:

  • HKCU\\Software\\Classes
  • HKLM\\Software\\Classes
  • عرض registry 32-bit / 64-bit إن كان ذا صلة
  • ProgID / CLSID / TypeLib المستهدف
  • InprocServer32 / LocalServer32
  • ThreadingModel
  • المسار الفعليّ لـ DLL المرجعيّ

“موجود في HKCR” لا يكفي بعد. لا يصبح ذا معنى إلّا حين تصطفّ مَن ينظر، بأيّ bitness، عبر أيّ عرض.711

مسارات المفاتيح التي ينبغي النظر إليها

فتح HKEY_CLASSES_ROOT\CLSID\{...} يعرض فقط ما يراه bitness الأداة التي تشغّلها والمستخدم الحاليّ. في الفصل ننظر إلى الكيان قبل الدمج في أربعة مواضع. ما تحت CLSID هدف إعادة توجيه WOW64، والموضع الفيزيائيّ للجانب 32-bit هو Wow6432Node تحت Classes.19

ما تريد رؤيته مسار السجلّ
الجهاز كلّه / عرض 64-bit HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{CLSID}
الجهاز كلّه / عرض 32-bit HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID}
ذلك المستخدم فقط / عرض 64-bit HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{CLSID}
ذلك المستخدم فقط / عرض 32-bit HKEY_CURRENT_USER\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID}
الاستدلال على CLSID من ProgID القيمة الافتراضيّة لـ HKEY_CLASSES_ROOT\{ProgID}\CLSID

regedit.exe في Windows 10 / 11 عمليّة 64-bit، لذلك إدخال المسارات أعلاه كما هي يفتح العرضين مباشرة. لصق المسار في شريط العنوان ينقلك إلى ذلك الموضع.

ومسار يشمل Wow6432Node موضع فيزيائيّ، وترشد Microsoft إلى عدم لمسه مباشرة من شيفرة التطبيق. لا بأس بالنظر إليه في تحقيق يدويّ، أمّا من سكربت أو تطبيق فأأمن استخدام تعيين العرض المذكور لاحقًا.11

التحقّق بالأمر

في reg.exe يمكن التصريح بالعرض بـ /reg:32 و /reg:64.

reg query "HKLM\SOFTWARE\Classes\CLSID\{00000000-0000-0000-0000-000000000000}\InprocServer32" /reg:32
reg query "HKLM\SOFTWARE\Classes\CLSID\{00000000-0000-0000-0000-000000000000}\InprocServer32" /reg:64

إن أردت رؤية المواضع الأربعة دفعة في PowerShell، مرّر العرض إلى RegistryKey.OpenBaseKey. لا تنجرّ النتيجة وراء bitness PowerShell نفسه، لذلك هذا أضمن في الفصل.

# عيّن ProgID المراد فحصه في موضع واحد
$progId = 'Vendor.Control.1'

# 1) اجلب CLSID من ProgID (HKCR هو view مدمج لـ HKLM و HKCU)
$clsid = (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\$progId\CLSID" -Name '(default)' -ErrorAction Stop).'(default)'
"ProgID $progId -> CLSID $clsid"

# 2) افحص بالمرور على 4 مواضع: hiveان × viewان
foreach ($hive in [Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryHive]::CurrentUser) {
    foreach ($view in [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32) {
        $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
        $key  = $base.OpenSubKey("SOFTWARE\Classes\CLSID\$clsid\InprocServer32")
        if ($null -eq $key) {
            '{0,-12} {1,-11} : بلا تسجيل' -f $hive, $view
        }
        else {
            '{0,-12} {1,-11} : {2} (ThreadingModel={3})' -f $hive, $view, $key.GetValue(''), $key.GetValue('ThreadingModel')
            $key.Dispose()
        }
        $base.Dispose()
    }
}

نتائج هذه الأسطر الأربعة هي جواب الفصل كما هي.

  • المواضع الأربعة كلّها «غير مسجَّل» → غير مسجَّل فعلًا. راجع إن كانت وسيلة التسجيل تطابق تصنيف 6.2.
  • يظهر في Registry32 فقط → مسجَّل في الجانب 32-bit فقط. من عمليّة 64-bit يصير 0x80040154.
  • يظهر في CurrentUser فقط → لا يراه إلّا ذلك المستخدم. لا يراه مستخدم آخر ولا حساب خدمة ولا عمليّة مرفوعة.
  • المسار يظهر ولا يعمل → أكّد أنّ الملفّ الذي تشير إليه القيمة الافتراضيّة لـ InprocServer32 موجود، وأنّ bitness ذلك الملفّ يطابق المضيف.

6.4. أخيرًا، تحقّق ممّا إن كانت الصلاحيّات تخفي الشكل الحقيقيّ فقط

في الخطوة الأخيرة، تحقّق ممّا إن كانت المشكلة فعلًا صلاحيّات، أو ما إن كانت الصلاحيّات تغيّر فقط أيّ تسجيل مرئيّ.

  • هل يتغيّر السلوك بين المستخدم القياسيّ والمسؤول
  • ماذا يتغيّر إن رُفع Visual Studio
  • هل يتكرّر تحت حساب خدمة أو مستخدم آخر
  • هل يعمل أيضًا في بيئة نظيفة مدفوعة بـ installer

إن كنت تنظر دائمًا إلى جهاز مطوّر واحد فقط، فهنا يُسيء الناس قراءة الموقف بسهولة كبيرة.

7. قرارات تشغيليّة تقلّل الحوادث

في تطوير وصيانة COM / ActiveX / OCX، قد تكون طريقة اتّخاذ قرارات نموذج التشغيل أهمّ من حِيَل التنفيذ منخفضة المستوى.

7.1. قرّر سياسة الـ bitness أوّلًا

في البداية، قرّر هذا صراحةً:

  • هل ستذهب x86 فقط
  • هل x64 هو الهدف الرئيسيّ
  • هل تدعم الاثنين
  • هل يجب أن يبقى هذا المكوّن in-process

إن كان OCX المورّد مثبّتًا على x86، فإنّ تجاهل ذلك وجعل التطبيق فقط x64 سيفشل عادةً لاحقًا.
للهيكليّة الأوسع حول هذه المشكلة، فإنّ دراسة حالة لجسر COM يستدعي DLL بـ 64bit من تطبيق 32bit وثيقة الصلة.

7.2. قرّر استراتيجيّة التسجيل

من الأفضل أيضًا التعامل مع التسجيل بشكل متعمَّد.

  • يستخدمه الجهاز كلّه → سجّل per-machine عبر installer
  • يستخدمه فقط ذلك المستخدم → استخدم تسجيل per-user عمدًا
  • يبقى داخل تطبيقك بالكامل → فكّر في COM بدون تسجيل
  • مطلوب فقط للتطوير → احصره في سكريبت إعداد تطوير صريح

COM بلا تسجيل مفيد لأنّه يستطيع الاحتفاظ بمعلومات التفعيل في manifest بدلًا من السجلّ، وهذا يساعد على تقليل جحيم التسجيل. كلّ من registration-free COM على جانب Win32 و RegFree COM على جانب .NET موثَّقان رسميًّا.31632

من حيث الآليّة، حين يعالج COM CoCreateInstance وما شابه، يبحث أوّلًا عن سياق التفعيل، فإن لم توجد فيه معلومات نظر إلى السجلّ. الـ manifest ملفّ يعلن محتوى سياق التفعيل ذلك.31

الـ manifest اللازم اثنان، ويُوضعان في المجلّد نفسه مع exe.

الأوّل manifest التجميعة على جانب المكوّن (VendorCtl.manifest). يكتب أيّ ملفّ يوفّر أيّ CLSID.33

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="MyCompany.VendorCtl"
                    version="1.0.0.0"
                    processorArchitecture="x86" />
  <file name="vendor.ocx">
    <comClass description="Vendor Control"
              clsid="{00000000-0000-0000-0000-000000000000}"
              threadingModel="Apartment"
              progid="Vendor.Control.1" />
  </file>
</assembly>

الثاني manifest التطبيق على جانب التطبيق (MyApp.exe.manifest). يكتب فقط الاعتماد على التجميعة أعلاه.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="MyCompany.MyApp"
                    version="1.0.0.0"
                    processorArchitecture="x86" />
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32"
                        name="MyCompany.VendorCtl"
                        version="1.0.0.0"
                        processorArchitecture="x86" />
    </dependentAssembly>
  </dependency>
</assembly>

ما يسهل الخطأ فيه عند الكتابة الآتي.33

  • assemblyIdentity على جانب الاعتماد يجب أن يطابق تمامًا assemblyIdentity على جانب المكوّن. انحراف واحد في name أو version أو processorArchitecture يمنع الحلّ.
  • اسم التجميعة واسم DLL منفصلان. عند وضع الـ manifest في ملفّ منفصل، يلزم أن يختلف اسم التجميعة واسم الـ manifest عن اسم DLL.
  • أسماء العناصر والسمات حسّاسة لحالة الأحرف. كتابة comClass كـ ComClass تمنع القراءة.
  • processorArchitecture هو bitness نفسه. لا يُحلّ تطبيق amd64 أمام OCX بـ x86. اجعله مطابقًا حتمًا لسياسة bitness في 7.1.
  • إن لصقت OCX فقد تلزم سمات عائلة miscStatus. يلزم نسخ معلومات تعادل مفتاح MiscStatus في السجلّ إلى الجانب الـ manifest كـ miscStatus / miscStatusContent وغيره.

7.3. لا تترك الكثير من الأصول المُولَّدة خارج التحكّم بالمصدر

من أنماط الإخفاق الشائعة في عمل OCX / ActiveX أنّ التبعيّات تتشتّت:

  • الـ OCX نفسه
  • DLLs التابعة
  • الـ TLB
  • الـ .lic
  • DLLs الخاصّة بـ interop
  • مغلِّف AxHost
  • سكريبتات التسجيل
  • المضيف النموذجيّ

إن وُجدت تلك فقط على آلة شخص ما المحلّيّة، فسيصبح ذلك تقريبًا حادثًا حتمًا بعد بضعة أشهر.

كحدّ أدنى، ينبغي الاحتفاظ بما يلي بجوار الشيفرة:

  • أيّ إصدار مفترض
  • ما الذي يجب تثبيته، وبأيّ ترتيب
  • أيّ أوامر تُسجّله
  • هل هو لـ x86 أم x64

8. هذا النوع من الاستشارات مناسب جيّدًا

غالبًا ما يقدّم هذا الموضوع قيمة حتى قبل بدء إعادة كتابة كاملة، لأنّ التشخيص و قرارات السياسة وحدها يمكنها دفع المشروع إلى الأمام.

على سبيل المثال، هذه أنواع عمل استشاريّ متوافقة جدًّا:

  • فصل أسباب 0x80040154 أو 0x80070005 إلى مشاكل bitness وتسجيل وصلاحيّات
  • التحقّق من مقدار ما يمكن إنقاذه واقعيًّا من إعداد Designer مكسور بعد الانتقال إلى Visual Studio 2022
  • الإبقاء على OCX المورّد في مكانه مع نقل الشيفرة المحيطة نحو .NET أو C#
  • تقرير إلى أيّ مدى نُمدّ عمر الأصول المثبّتة على x86، وأين نَجسر أو نُغلّف أو نستبدل
  • إعادة تصميم التثبيت / النشر بحيث لا يعتمد النظام على regsvr32 يدويّ

قرار keep / wrap / replace الأوسع نفسه أسهل بكثير في تنظيمه أيضًا مع كيف تتعامل مع ActiveX / OCX اليوم - جدول قرار للإبقاء أو التغليف أو الاستبدال.

9. الخلاصة

عندما يفشل العمل على مكوّنات COM و OCX / ActiveX، تقع الأسباب عادةً في هذه المجموعات الأربع:

  1. الـ bitness لا يصطفّ
  2. طريقة التسجيل خاطئة
  3. نطاق التسجيل غير متطابق (HKCU / HKLM، عرض 32-bit / 64-bit)
  4. النظام يبدو أنّه يعمل فقط لأنّ الصلاحيّات تكشف شيئًا مصادفةً

مع تحوّل Visual Studio 2022 إلى 64-bit، صار الإعداد القديم بأسلوب “كان يعمل بطريقة ما” أسهل بكثير في الانكسار بطرق مرئيّة.12
لهذا بالضبط، عند لمس COM / OCX / ActiveX اليوم، فإنّ المسار الأسرع غالبًا هو محاذاة افتراضات البيئة قبل كتابة المزيد من الشيفرة.

بدلًا من السؤال عن عدد مرّات تشغيل regsvr32، من الأسرع عادةً ترتيب:

  • أيّ عمليّة تستضيفه
  • أيّ bitness لتلك العمليّة
  • أين يجب تسجيله
  • هل ينبغي فعلًا أن يتطلّب ذلك التسجيل صلاحيّات مسؤول
  • هل يُفحَص Designer ووقت التشغيل بشكل منفصل

المراجع

  1. Microsoft Learn, Visual Studio 2022 version 17.0 Release Notes — devenv.exe is now 64-bit only. https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.0 ↩ ↩2 ↩3

  2. Microsoft Learn, Troubleshoot 32-bit problems - Windows Forms — Visual Studio 2022 is a 64-bit process, cannot directly load 32-bit .NET / COM / ActiveX, and has out-of-process designer constraints. https://learn.microsoft.com/en-us/dotnet/desktop/winforms/visualstudio/troubleshoot-32bit ↩ ↩2 ↩3 ↩4 ↩5

  3. Microsoft Learn, Classes and Servers — COM registration, HKCU / HKCR, self-registration, and DllRegisterServer. https://learn.microsoft.com/en-us/windows/win32/com/classes-and-servers ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, regsvr32 — syntax and role of regsvr32. https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/regsvr32 ↩ ↩2

  5. Microsoft Learn, Registering Assemblies with COM — .NET Framework COM registration uses Regasm.exe. https://learn.microsoft.com/en-us/dotnet/framework/interop/registering-assemblies-with-com ↩ ↩2

  6. Microsoft Learn, Expose .NET Core components to COM — EnableComHosting, generated .comhost.dll, regsvr32, and EnableRegFreeCom. https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com ↩ ↩2 ↩3

  7. Microsoft Learn, Merged View of HKEY_CLASSES_ROOT — HKCR is a merged view of HKLM and HKCU. https://learn.microsoft.com/en-us/windows/win32/sysinfo/merged-view-of-hkey-classes-root ↩ ↩2 ↩3 ↩4 ↩5

  8. Microsoft Learn, HKEY_CLASSES_ROOT Key — recommends per-machine COM registration for applications that require administrator rights. https://learn.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key ↩ ↩2

  9. Microsoft Learn, COM Error Codes (Generic) (Winerror.h) — including REGDB_E_CLASSNOTREG (0x80040154). https://learn.microsoft.com/en-us/windows/win32/com/com-error-codes-1 ↩

  10. Microsoft Learn, You receive 0x80070005 error when you try to register a DLL by using Regsvr32.exe — the typical permissions-related DLL-registration failure. https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/dllregisterserver-error ↩ ↩2

  11. Microsoft Learn, Registry Redirector — 32-bit / 64-bit registry views under WOW64. https://learn.microsoft.com/en-us/windows/win32/winprog64/registry-redirector ↩ ↩2 ↩3 ↩4 ↩5

  12. Microsoft Learn, File System Redirector — %windir%\\System32 on x64 Windows and WOW64 file-system redirection. https://learn.microsoft.com/en-us/windows/win32/winprog64/file-system-redirector ↩

  13. Microsoft Learn, Overview of compatibility considerations for 32-bit programs on 64-bit versions of Windows — file and registry redirection under WOW64. https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/compatibility-limitations-32-bit-programs-64-bit-system ↩

  14. Microsoft Learn, Regasm.exe (Assembly Registration Tool) — the role of Regasm.exe and options such as /tlb. https://learn.microsoft.com/en-us/dotnet/framework/tools/regasm-exe-assembly-registration-tool ↩ ↩2 ↩3

  15. Microsoft Learn, Packaging a .NET Framework Assembly for COM — type libraries and Regasm.exe /tlb. https://learn.microsoft.com/en-us/dotnet/framework/interop/packaging-an-assembly-for-com ↩

  16. Microsoft Learn, Registering the DLL Server for Surrogate Activation — شروط تحميل خادم الـ DLL داخل surrogate، وأنّ تشغيل خادم EXE أو خدمة له الأولويّة عند وجود LocalServer / LocalServer32 / LocalService، والتكوين الذي يجعل الـ AppID بالـ GUID نفسه الخاصّ بالـ CLSID. https://learn.microsoft.com/en-us/windows/win32/com/registering-the-dll-server-for-surrogate-activation ↩ ↩2 ↩3 ↩4

  17. Microsoft Learn, DllSurrogate — أنّ DllSurrogate تحت AppID من نوع REG_SZ، وأنّ السلسلة الفارغة تعني الـ surrogate الافتراضيّ للنظام، بينما كتابة مسار تعني استخدام الـ surrogate المخصّص الموجود في ذلك المسار. https://learn.microsoft.com/en-us/windows/win32/com/dllsurrogate ↩ ↩2

  18. Microsoft Learn, Using the system-supplied surrogate — أنّ تحديد سلسلة فارغة أو NULL يُشغّل الـ surrogate الافتراضيّ للنظام، والتعامل مع نموذج الخيوط وعمر العمليّة داخل الـ surrogate. https://learn.microsoft.com/en-us/windows/win32/com/using-the-system-supplied-surrogate ↩ ↩2

  19. Microsoft Learn, Registry Keys Affected by WOW64 — أنّ HKLM\SOFTWARE\Classes\CLSID و HKCU\SOFTWARE\Classes\CLSID خاضعان لإعادة التوجيه، وأنّ HKLM\SOFTWARE\Classes\AppID مفتاح مشترك (shared) منذ Windows 7 / Windows Server 2008 R2، وأنّ HKCR هو العرض المدمج للاثنين. https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys ↩ ↩2 ↩3

  20. Microsoft Learn, CLSCTX enumeration — أنّ تمرير عدّة قيم CLSCTX مجموعةً بـ OR يُجرَّب بترتيب التعداد، وأنّ مفتاح InprocServer32 يُستخدم في مرحلة in-proc إن كان موجودًا، وأنّه حين لا يبدي العميل ولا الخادم رغبةً في bitness معيّن يُختار خادم بالـ bitness المناسب للعميل، وإن لم يوجد شُغِّل ذو الـ bitness الآخر. https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx ↩ ↩2 ↩3

  21. Microsoft Learn, Understanding Custom Build Steps and Build Events — includes a post-build example that uses regsvr32.exe. https://learn.microsoft.com/en-us/cpp/build/understanding-custom-build-steps-and-build-events ↩

  22. Microsoft Learn, The Windows registry for advanced users — behavior of HKCU\\Software\\Classes, HKLM\\Software\\Classes, and HKCR. https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/windows-registry-advanced-users ↩

  23. Microsoft Learn, Find, install, and manage extensions for Visual Studio — behavior of per-user extensions under elevated execution. https://learn.microsoft.com/en-us/visualstudio/ide/finding-and-using-visual-studio-extensions ↩

  24. Microsoft Learn, MFC ActiveX Controls: Licensing an ActiveX Control — design-time / run-time licensing and .LIC. https://learn.microsoft.com/en-us/cpp/mfc/mfc-activex-controls-licensing-an-activex-control ↩

  25. Microsoft Learn, Application Settings, MFC ActiveX Control Wizard — runtime-license generation and .lic files. https://learn.microsoft.com/en-us/cpp/mfc/reference/application-settings-mfc-activex-control-wizard ↩

  26. Microsoft Learn, License information for this component not found. You don’t have an appropriate license to use this functionality in the design environment. https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/license-information-for-this-component-not-found-you-don-t-have-an-appropriate-l ↩

  27. Microsoft Learn, Aximp.exe (Windows Forms ActiveX Control Importer) — converts ActiveX to a WinForms wrapper. https://learn.microsoft.com/en-us/dotnet/framework/tools/aximp-exe-windows-forms-activex-control-importer ↩

  28. Microsoft Learn, AxHost Class — the AxHost-based wrapper produced by ActiveX Control Importer. https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.axhost ↩

  29. Microsoft Learn, Initializing the COM Library — CoInitializeEx, per-thread initialization, and the STA message loop. https://learn.microsoft.com/en-us/windows/win32/learnwin32/initializing-the-com-library ↩ ↩2 ↩3

  30. Microsoft Learn, Single-Threaded Apartments — STA message loops, marshaling, and ThreadingModel. https://learn.microsoft.com/en-us/windows/win32/com/single-threaded-apartments ↩ ↩2 ↩3 ↩4 ↩5

  31. Microsoft Learn, Creating Registration-Free COM Objects — registry-free activation through activation context. https://learn.microsoft.com/en-us/windows/win32/sbscs/creating-registration-free-com-objects ↩ ↩2

  32. Microsoft Learn, Registration-Free COM Interop — .NET Framework registration-free COM interop. https://learn.microsoft.com/en-us/dotnet/framework/interop/registration-free-com-interop ↩

  33. Microsoft Learn, Assembly Manifests — سمات assembly / assemblyIdentity / dependency / file / comClass، ولزوم تطابق assemblyIdentity في جانب REF وجانب DEF، والتعامل مع حالة الأحرف، وعائلة سمات miscStatus. ↩ ↩2

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

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

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

كيف أحقّق في 0x80040154 (Class not registered)؟
رسميًّا هو REGDB_E_CLASSNOTREG، أي خطأ «الصنف غير مسجَّل» حرفيًّا، لكنّه لا يعني دائمًا أنّه غير مسجَّل تمامًا. يحدث أيضًا حين يكون التسجيل في view السجلّ للـ bitness الآخر فقط، أو تحت HKCU حيث لا يراه إلّا ذلك المستخدم. الطريق السريع تثبيت إن كانت عمليّة المضيف 32-bit أم 64-bit أوّلًا، ثمّ التحقّق من أنّ وسيلة التسجيل صحيحة، ثمّ المرور على أين وقع الإدخال فعلًا عبر HKCU / HKLM وعرضَي 32-bit / 64-bit.
لماذا لا يعمل بعد التسجيل بـ regsvr32؟
regsvr32 موجَّه لخوادم COM الأصليّة من نوع in-proc (DLL / OCX) التي تصدّر DllRegisterServer؛ وليس أمرًا سحريًّا يسجّل أيّ شيء. كشف تجميعة .NET Framework لـ COM يمرّ عبر Regasm.exe، ومن .NET 5 فما بعد تسجّل بـ regsvr32 ملفّ .comhost.dll الذي يولّده EnableComHosting. على Windows x64 يلزم أيضًا اختيار regsvr32 الصحيح: System32 للجانب 64-bit، و SysWOW64 للجانب 32-bit؛ لأنّ التسجيل في الجانب الخطأ يترك المكوّن غير مرئيّ للعمليّة المستهدفة رغم نجاح الأمر.
لماذا ينكسر Designer وحده في Visual Studio 2022؟
لأنّ devenv.exe في Visual Studio 2022 عمليّة 64-bit ولا تستطيع تحميل مكوّنات COM / ActiveX بـ 32-bit مباشرة. التطبيق نفسه قد يعمل x86 وقت التشغيل، لكنّ Designer يعمل داخل عمليّة Visual Studio بـ 64-bit، فينشأ الالتواء الذي يبقى فيه التطبيق حيًّا وقت التشغيل بينما يسقط Designer وحده. التحويل إلى AnyCPU لا يحسمه أيضًا ما دام شيء في سلسلة المراجع يعتمد على مكوّن COM / ActiveX مقيَّد بـ 32-bit.
لماذا يعمل عند التشغيل كمسؤول ولا يعمل بالصلاحيات العاديّة؟
المشكلة الحقيقيّة في الغالب ليست الصلاحيات ذاتها بل انحراف نطاق التسجيل أو تصميم التثبيت. ينظر COM إلى HKCU\Software\Classes أوّلًا، و HKEY_CLASSES_ROOT عرض مدمج من HKLM و HKCU. إن سجّل المطوّر A المكوّن يدويًّا تحت مستخدمه، يعمل عادة تحت حساب A ويفشل لمستخدمين آخرين ولحسابات الخدمة. الأساس فصل تسجيل التطوير per-user عن تسجيل الإنتاج per-machine الذي يقوم به الـ installer، وفصل البناء عن التسجيل.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

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