كيف تختار وسيلة الاتّصال بين عمليّات Windows ── جدول قرار للأنابيب المسمّاة / TCP / gRPC / الذاكرة المشتركة / COM
· آخر تحديث: · غو كومورا · الاتّصال بين العمليّات, الأنابيب المسمّاة, Windows, .NET, C#, gRPC, الذاكرة المشتركة, COM, التصميم, جدول القرار, الاستشارة التقنيّة
سجل التعديلات (3 تحديثات، آخر تحديث 2 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- تُرجمت تعليقات ضمان عدم إعادة محاولة startJob غير المِثْلية إلى العربية.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621603)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
غو كومورا (2026). كيف تختار وسيلة الاتّصال بين عمليّات Windows ── جدول قرار للأنابيب المسمّاة / TCP / gRPC / الذاكرة المشتركة / COM. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621603 https://comcomponent.com/ar/blog/windows-ipc-decision-table/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621603
- DOI (هذه النسخة)
- 10.5281/zenodo.22240988
«حين نقسم واجهة المستخدم عن الخدمة، كيف يكون الاتّصال بينهما؟» «أريد استخدام وظيفة DLL بنظام 64 بت من تطبيق بنظام 32 بت» «أريد إرسال البيانات من محرّك قياس يعمل في عمليّة منفصلة إلى الشاشة». تقسيم التطبيق إلى عدّة عمليّات حلّ واقعيّ متكرّر لأجل المتانة وفصل الصلاحيّات ومشكلات عدد البتّات، لكن بمجرّد التقسيم يظهر دائماً خيار «كيف يكون الاتّصال بينهما».
في هذه المدوّنة كتبنا سابقاً عن وسائل اتّصال منفردة، مثل مطبّات الذاكرة المشتركة، وتجزئة TCP إلى إطارات، والتعامل الآمن مع العمليّات الفرعيّة، وتكامل الملفّات والقفل. لكن لم يكن هناك بعد نقاش عامّ حول «أيّ وسيلة ينبغي اختيارها أصلاً». في هذا المقال ننظّم مجالات تفوّق ومطبّات الخيارات الرئيسيّة للاتّصال بين عمليّات Windows (IPC) ── تكامل الملفّات، والأنابيب المسمّاة، وTCP المحليّ، وgRPC، والذاكرة المشتركة، وCOM ── بصيغة جدول القرار المعتاد في مدوّنتنا.
مصطلحات تُثبَّت أوّلاً
نجمع ما يظهر في النصّ مختصراً قبل المضيّ.
| المصطلح | المعنى |
|---|---|
| IPC (Inter-Process Communication) | الاتّصال بين العمليّات. اسم جامع لآليّات تبادل البيانات والإشارات مع عمليّة أخرى |
| ACL (Access Control List) | قائمة التحكّم بالوصول. قائمة «من يُسمح له بأيّ عمليّة»، وتُلحَق في Windows بالأنابيب والملفّات والذاكرة المشتركة وغيرها. في .NET تُركَّب بأصناف مثل PipeSecurity |
| UAC (User Account Control) | التحكّم بحساب المستخدم. حتّى حساب المدير يعمل افتراضيّاً بصلاحيّة قياسيّة ولا يُرفَع إلّا عند الحاجة. الجانب المرفوع والجانب غير المرفوع يعملان بصلاحيّتين مختلفتين، فيصير الاتّصال بينهما حديث القسم 2.2 |
| RPC (Remote Procedure Call) | آليّة تتيح كتابة إجراءات عمليّة أخرى أو جهاز آخر كما تُكتَب استدعاءات دوال محلّيّة. استدعاء COM خارج العمليّة يجلس فوقها أيضاً |
| data plane / control plane | مستوى البيانات ومستوى التحكّم. تسمية عند فصل تدفّق البيانات الفعليّة عن تدفّق الأوامر مثل البدء والإيقاف وتغيير الإعدادات (القسم 2.5 والفصل 4) |
| التجزئة إلى إطارات (framing) | آليّة تحدّد من أين إلى أين في تتابع البايتات تمتدّ رسالة واحدة. في TCP تصميمها الذاتيّ إلزاميّ (القسم 2.3 والفصل 6) |
1. الخلاصة أوّلاً
- لنمط الطلب والاستجابة داخل الجهاز نفسه (إرسال أمر واستلام النتيجة)، الأنابيب المسمّاة هي الخيار الأوّل. معياريّة في نظام التشغيل، بلا منفذ، مدمجة مع التحكّم بالوصول في Windows، وتُكتَب باستقامة من .NET عبر
System.IO.Pipes.1 - إن رُئي احتمال عبور الشبكة مستقبلاً، فابدأ بـ TCP المحليّ أو gRPC. الانتقال من أنبوب إلى مقبس فرق لا يُنجَز عادة بـ«تعديل بسيط لاحقاً». غير أنّ TCP الخام تدفّق بايتات، فيلزم تصميم التجزئة إلى إطارات.
- عندما تزداد أنواع الخدمات والاستدعاءات وتصير صيانة البروتوكول الذاتيّ عبئاً فـ gRPC. تحصل على تعريف مخطّط عبر proto وتوليد شيفرة وبثّ ثنائيّ الاتّجاه، وابتداءً من .NET 8 يستطيع ASP.NET Core (Kestrel) اتخاذ الأنابيب المسمّاة طبقة نقل.2
- الذاكرة المشتركة للبيانات كبيرة الحجم وعالية التكرار فقط (إطارات صور، موجات). الأسرع، لكنّ المزامنة تُصمَّم كلّها بنفسك، فالقاعدة الحديديّة ألا تُحمَل رسائل التحكّم أيضاً على الذاكرة المشتركة.3
- لتكامل غير محكم غير متزامن مع رغبة في إبقاء سجلّ تدقيق، تكامل الملفّات ما زال قويّاً. غير أنّ النجاح يُحسَم بتصميم الحصر.
- COM (خارج العمليّة) ليس الخيار الأوّل في تصميم جديد، لكنّه أداة ما زالت حيّة في سياق الاستدعاء من VBA ولغات أخرى وجسر 32-bit/64-bit.4
- أيّاً كانت الوسيلة، لا مفرّ من قضايا تصميم مشتركة: حدّ الرسالة، ورقم الإصدار، والمهلة، وإعادة الاتّصال (الفصل 6). خصّص لها من الوقت ما تخصّصه لاختيار الوسيلة.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 21، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. طبيعة الخيارات
2.1 تكامل الملفّات ── اقتران غير محكم، غير متزامن، قابل للتدقيق
تكامل كلاسيكيّ: «A يضع ملفّاً في مجلّد إخراج، وB يلتقطه ويعالج». لا يلزم أن يعمل الطرفان في الوقت نفسه، ويبقى أثر المعالجة ملفّاً، وعند العطل يمكن لإنسان أن يرى الملفّ ويصلحه ويعيد إدخاله. ما زال قويّاً في تكامل شبيه بالدفعات لا يُطلب فيه الزمن الحقيقيّ.
المطبّ يكاد ينحصر في نقطة واحدة: الحصر. «قراءة ملفّ في منتصف الكتابة» و«معالجتان تتنازعان الملفّ نفسه» حوادث نمطيّة، ويلزم اتباع الأسلوب المتعارف: الكتابة باسم مؤقّت ثمّ إعادة التسمية. التفاصيل مجموعة في «أفضل ممارسات تكامل الملفّات والقفل»، فإن اخترت هذا الأسلوب فارجع إليه حتماً. لا يناسب اتّصالاً تفاعليّاً يريد ردّاً، ولا تبادلاً يحدث عشرات المرّات في الثانية.
2.2 الأنبوب المسمّى ── الخيار الأساسيّ للاتّصال داخل الجهاز نفسه
الأنبوب المسمّى مسار اتّصال ثنائيّ الاتّجاه يقدّمه Windows في النواة، وهو الخيار الجادّ لـ IPC من نوع عميل-خادم داخل الجهاز نفسه. في .NET يُتعامل معه بـ NamedPipeServerStream / NamedPipeClientStream، ويمكن لعملاء عدّة الاتّصال باسم أنبوب واحد.5 بخلاف TCP لا تحتاج إدارة رقم منفذ، ولا تصطدم بجدار الحماية.
نقاط ينبغي تثبيتها في العمل.
- يمكن استخدام وضع الرسائل. بتحديد
PipeTransmissionMode.Messageتصل الكتابة الواحدة كرسالة واحدة محدودة. مزيّة عمليّة كبيرة: نظام التشغيل ينوب عنك في التجزئة إلى إطارات مثل بادئة الطول الواجبة في TCP (على جانب الاستقبال يلزم قراءة الرسالة كاملة بـIsMessageComplete. راجع الفصل 5). - صلاحيّات الوصول الافتراضيّة أوسع ممّا يُتوقَّع. واصف الأمان الافتراضيّ للأنبوب يمنح التحكّم الكامل لـ LocalSystem والمدير والمنشئ، ويسمح أيضاً بالقراءة لـ Everyone وللحسابات المجهولة.6 بين عمليّات المستخدم نفسه يكفي إلحاق
PipeOptions.CurrentUserOnlyلفرض «لا يتّصل إلّا بطرف أنشأه المستخدم نفسه»، ويُستحسَن جعل هذا الأدب الافتراضيّ.7 بين حسابات مختلفة (الاتّصال مع خدمة مثلاً) صرِّح بـ ACL عبرPipeSecurity. - اسم الأنبوب في فضاء أسماء يراه الجميع. تُوضَع أسماء الأنابيب في فضاء أسماء واحد تحت
\\.\pipe\، وتراها عمليّات مستخدمين آخرين على الجهاز. وما ينبغي الانتباه إليه هنا احتلال الاسم (squatting). إن نصب عمليّة خبيثة خادماً بالاسم نفسه وانتظرت أوّلاً، اتّصل العميل بها. على جانب الخادم حدِّدPipeOptions.FirstPipeInstanceليفشل إن وُجد أنبوب بالاسم نفسه، وعلى جانب العميل تحقّق بعد الاتّصال من حساب مالك الأنبوب ── أدخل هذه الإجراءات على جهاز يتعايش عليه مستخدمون بصلاحيّات مختلفة.8 - يمكن الاتّصال عبر حدود الصلاحيّة. مسار نمطيّ أيضاً بين «واجهة بصلاحيّة قياسيّة + وسيط بامتيازات مدير» المفصولين بـ UAC. غير أنّ
CurrentUserOnlyلا يُستخدم في هذا التكوين (لأنّه يطلب تطابق مستوى الرفع أيضاً حتّى للمستخدم نفسه7). يلزم تصميم ACL صراحة، والتنفيذ الملموس مفصَّل في «فصل امتيازات المدير (الوسيط)».
نقطة الضعف أنّه عمليّاً لا يناسب اتّصالاً يعبر الأجهزة (ممكن في المواصفة لكن قيود التشغيل كثيرة)، ويصعب استخدامه للاتصال المتبادل خارج Windows. إن ظهر هذا المتطلّب فاختر TCP / gRPC.
2.3 TCP المحليّ ── تعميم يتجاوز اللغة ونظام التشغيل
اتّصال TCP نحو localhost أكثر IPC تعميماً: تتحدّث إليه تقريباً أيّ لغة وبيئة تشغيل ونظام تشغيل. في تكوين مختلط مثل «محرّك تحليل على Linux وواجهة على Windows» يكون TCP (أو HTTP/gRPC فوقه) المرشّح أوّلاً.
ثلاث نقاط انتباه.
- هو تدفّق بايتات. ليس لـ TCP خاصّيّة «يصل بوحدة الإرسال». رسائل ثلاث أرسلتها بـ
Sendقد تصل ملتحمة فيReceiveواحد، ورسالة واحدة قد تصل مجزّأة، وكلاهما طبيعيّ. يلزم تصميم تجزئة إلى إطارات مثل بادئة الطول في طبقة التطبيق، والشيفرة التي تُسقط هذا «تعمل مصادفة» فقط. التفاصيل في «وهم أنّ Receive يصل بوحدة Send في TCP». - ضيِّق نطاق إعلان الانتظار. حتّى إن قصدت اتّصالاً داخل الجهاز نفسه، فالانتظار على
0.0.0.0يتيح الاتّصال من أجهزة أخرى على الشبكة. لـ IPC محلّيّ المبدأ الربط على127.0.0.1(حلقة راجعة). ومع ذلك يمكن لمستخدم آخر على الجهاز نفسه الاتّصال، فإن لزم تأكيد الطرف فأضِف مصادقة في طبقة التطبيق. الفرق الواضح غياب تحكّم وصول مدمج مع نظام التشغيل كالذي في الأنابيب. - تشغيل المنفذ يلازمك. المنفذ الثابت قد يصطدم ببرمجيّات أخرى، وقد تكتشفه منتجات جدار الحماية كـ«انتظار مريب». أدخل وسيلة تغيير رقم المنفذ في التصميم من البداية.
2.4 gRPC ── شراء المخطّط وتوليد الشيفرة
مقابل مقبس خام + بروتوكول ذاتيّ، ما يقدّمه gRPC ليس الاتّصال نفسه بقدر ما هو إطار تطوير. بتعريف الخدمات والرسائل في ملفّ proto تُولَّد الشيفرة كلّها للتسلسل والتجزئة والعميل/الخادم، ويُكتَب البثّ ثنائيّ الاتّجاه (إشعارات دفع من الخادم) كوظيفة لغويّة. إذا دخلت مرحلة «كلّما زادت رسالة أصلحت جملة switch في البروتوكول الذاتيّ والتوثيق»، نفع هذا الإطار.
ابتداءً من .NET 8 يدعم ASP.NET Core (Kestrel) مباشرة كمقابس نطاق Unix وأنابيب مسمّاة طبقة نقل إلى جانب TCP.8 على جانب الخادم يكفي استدعاء ListenNamedPipe، ويمكن أيضاً ضبط التحكّم بالوصول عبر PipeSecurity.2 تركيب «مسار الاتّصال أنبوب بلا منفذ ومدمج مع ACL، وطبقة البروتوكول gRPC» خيار قويّ في فصل الواجهة عن الخدمة (الفصل 4).
وبالمقابل، بين أدوات صغيرة كثيراً ما يصير أكبر من اللازم. إقامة خادم gRPC تعني حمل بنية استضافة ASP.NET Core، فيزيد التوزيع والتبعيّات وتكلفة البدء. إن لم تتجاوز الأوامر نوعين أو ثلاثة بين الأدوات، ارسم الخطّ: الأنابيب المسمّاة + JSON (الفصل 5) أقلّ تكلفة إجماليّة. لا يتأخّر الأمر إن انتظرت حتّى ينطبق واحد من: «تجاوزت الرسائل التي تريد كتابتها في proto عشرة»، أو «إشعارات البثّ ضرورة جوهريّة»، أو «الطرف خارج .NET».
2.5 الذاكرة المشتركة (ملفّ مُمَثَّل في الذاكرة) ── الأسرع، لكنّ كلّ المزامنة عليك
أسرع IPC داخل الجهاز نفسه الذاكرة المشتركة. في .NET تُنشئ ذاكرة مشتركة مسمّاة بـ MemoryMappedFile.CreateNew، وتقرأ وتكتب العمليّات عدّة تتابع البايتات نفسه مباشرة.3 بلا نسخ ولا تسلسل، فيخرج أداء يكاد يكون الخيار الوحيد لبيانات «كبيرة وسريعة» مثل إطارات الصور أو الموجات.
غير أنّ الذاكرة المشتركة ليست «أنبوباً سريعاً». تظهر تتابع البايتات نفسه فقط، ولا تُقدَّم مزامنة ولو ببايت. آليّة عدم قراءة بيانات في منتصف الكتابة، واكتشاف حياة الطرف الآخر أو موته، والتعافي بعد إنهاء أحد الطرفين إنهاءً غير طبيعيّ ── كلّها تُصمَّم بنفسك. نظريّة التصميم بما فيها تكوين المخزن الحلقيّ وإدارة إصدار التخطيط مجموعة في «مطبّات الذاكرة المشتركة وأفضل الممارسات العمليّة».
موضع الاستخدام في العمل واضح: تكوين يحمل مستوى البيانات (data plane) فقط على الذاكرة المشتركة، ويهرب بمستوى التحكّم (control plane) إلى قناة أخرى مثل أنبوب مسمّى (التكوين 3 في الفصل 4). إن بدأت تجاهد لتمرير رسائل تحكّم مثل البدء والإيقاف وتغيير الإعدادات أيضاً عبر الذاكرة المشتركة، فتلك إشارة لإعادة النظر في التصميم.
2.6 COM (خارج العمليّة) ── ليس الخيار الأوّل الجديد، لكن لا يزال له مواضع فعّالة
خادم COM خارج العمليّة (خادم EXE) آليّة قديمة في Windows لـ«استدعاء كائن عمليّة أخرى كما تُستدعى دالة محلّيّة».4 لم يعد الخيار الأوّل لاتّصال جديد بين تطبيقات، لكنّه ما زال عمليّاً في السياقات التالية.
- جسر 32-bit/64-bit: تطبيق 32-bit لا يحمّل DLL 64-bit (أو العكس) في العمليّة نفسها، لكن COM خارج العمليّة يعبر الحدّ. بنية COM تتولّى المارشلينغ نيابة عنك، فلا تكاد تتغيّر شيفرة الطرف المستدعي. مثال عمليّ في «دراسة حالة جسر COM من 32-bit إلى 64-bit».
- الاستدعاء من VBA ولغات أخرى: إذا أردت استدعاء وظائف .NET من بيئة تنفيذ قديمة مثل Excel VBA، فالكشف كـ COM ما زال أقصر مسار.
- التكامل مع أصول COM قائمة: إن كان الطرف لا يتحدّث إلّا COM، فأقصر طريق أن تتحدّث أنت أيضاً COM (شرح COM نفسه في «ما هي COM وActiveX وOCX»).
أسباب التردّد في الاعتماد الجديد: عبء التوزيع المصاحب لتسجيل Registry، وتكلفة تعلّم تصميم الواجهة وعدّ المراجع، وصعوبة التحقيق عند العطل. قارن قبل الحسم بالبديل: «إن كان الغرض جسر 32/64 فاجعل جانب 64-bit مجرّد عمليّة مساعدة وتحدّث بأنبوب مسمّى» (التكوين 2 في الفصل 4).
2.7 وسائل كلاسيكيّة ── لا تُختار في تصميم جديد
لدى Windows وسائل IPC أخرى مثل WM_COPYDATA (إرسال بيانات برسالة نافذة)، والحافظة، وDDE، وmailslot، وما زالت مصفوفة في صفحة نظرة IPC الرسميّة.4 غير أنّها تفترض وجود نافذة، أو أنّ الإلغاء جارٍ (mailslot البعيد في طريق الإلغاء)، فلا سبب تقريباً لاختيارها في تصميم جديد. يكفي فهمها عندما تصادفها في صيانة تطبيق قائم.
3. جدول القرار
نحسم معنى الرموز أوّلاً. تمثّل بأربع درجات «كم تصميم وتنفيذ إضافيّ يلزم لتلبية ذلك الجانب بعد اختيار الوسيلة». ليست تقييماً مطلقاً للسرعة أو الوظائف.
| الرمز | معيار التقييم |
|---|---|
| ◎ | صُنعت لذلك الاستخدام. تكفي الوظائف المعياريّة كما هي، ولا يكاد يلزم تصميم إضافيّ |
| ○ | تُستخدم بلا مشكلة. غير أنّ تنفيذاً أو ضبطاً وفق الأسلوب المتعارف (وصف ACL، تجزئة إلى إطارات، إلخ) لازم |
| △ | ممكن، لكن يلزم تصميم ذاتيّ معتبر، أو تلازمه قيود وآثار جانبيّة لا تُهمَل |
| ✕ | متعذّر عمليّاً. يلزم الجمع مع وسيلة أخرى |
صفّ «تكلفة التنفيذ» مكتوب بمنخفض/متوسط/مرتفع لا بالرموز، والاتّجاه أنّ الأدنى أفضل. صفّ «الإنتاجيّة/زمن الاستجابة» بالعكس: الأعلى أفضل (◎ الأسرع).
| الجانب | ملفّ | أنبوب مسمّى | TCP محلّيّ | gRPC | ذاكرة مشتركة | COM |
|---|---|---|---|---|---|---|
| نطاق الاتّصال | يمكن عبور الأجهزة بمجلّد مشترك | النطاق العمليّ الجهاز نفسه | يمكن عبور الأجهزة | يمكن عبور الأجهزة | الجهاز نفسه فقط | النطاق العمليّ الجهاز نفسه |
| نموذج الاتّصال | تسليم ملفّ (غير متزامن) | تدفّق + وضع رسائل | تدفّق بايتات | RPC + بثّ | حالة مشتركة | استدعاء طريقة |
| إشعار خادم ← عميل | ✕ (استقصاء) | ○ | ○ | ◎ (بثّ ثنائيّ الاتّجاه) | △ (مع أحداث) | △ (ممكن لكن معقّد) |
| عبور حدود الصلاحيّة (UAC، خدمة) | ○ (ACL مجلّد) | ◎ (PipeSecurity) | △ (المصادقة ذاتيّة) | △〜○ (◎ إن كانت طبقة النقل أنبوباً) | ○ (ACL ممكن، التصميم صعب) | ○ |
| اختلاط 32/64 واللغات | ◎ | ○ | ◎ | ◎ (توليد لكلّ لغة من proto) | △ (تصميم ABI إلزاميّ) | ○ (يجيد الجسر) |
| تكلفة التنفيذ | منخفضة | منخفضة〜متوسّطة | متوسّطة (تجزئة ذاتيّة) | متوسّطة (إدخال بنية) | مرتفعة | مرتفعة (للجديد) |
| سهولة التصحيح | ◎ (المحتوى يبقى ملفّاً) | ○ | ○ (يمكن الالتقاط) | △ (HTTP/2 + ثنائيّ) | △ (الأعراض صارخة) | △ |
| الإنتاجيّة/زمن الاستجابة | منخفضة | متوسّطة〜مرتفعة | متوسّطة | متوسّطة | ◎ | متوسّطة |
تكملة واحدة. الاستخدام الصحيح ليس اختيار «الوسيلة الأكثر ◎» بل الاستبعاد بالنظر إلى الصفوف التي تؤثّر في المتطلّب فقط. مثلاً لحظة حسم «30 إطاراً في الثانية من الصور» يصير مستوى البيانات ذاكرة مشتركة لا غير.
ونتيجة التضييق ترسو عادة على واحد من التكوينات الثلاثة الشائعة في الفصل التالي. بعد حسم الوسيلة بالجدول ارجع بهذا التقابل إلى الفصل 4.
| الوسيلة والمتطلّب المتبقّيان في جدول القرار | الوجهة |
|---|---|
| أنبوب مسمّى + عبور حدود الصلاحيّة (UAC، خدمة) | التكوين 1: فصل تطبيق الواجهة عن خدمة Windows |
| أنبوب مسمّى أو COM + اختلاط 32/64-bit متطلّب | التكوين 2: جسر من تطبيق 32-bit إلى وظائف 64-bit |
| ذاكرة مشتركة + حاجة إلى تحكّم مثل البدء والإيقاف | التكوين 3: تكوين قناتين للتحكّم والبيانات |
| بقي gRPC | شكل يستبدل طبقة اتّصال التكوين 1 بطبقة نقل أنبوب مسمّى لـ gRPC2 |
| بقي تكامل الملفّات | دفعات غير محكمة أكثر منه تكويناً شائعاً. جسم التصميم هو الحصر (القسم 2.1) |
4. أمثلة على البِنى الشائعة
إن طُبِّق جدول القرار على متطلّبات منفردة، كثيراً ما يسقط العمل إلى التكوينات الثلاثة التالية. المخطّط كالتالي.
flowchart TB
subgraph K1["التكوين 1: فصل تطبيق الواجهة عن خدمة Windows"]
U1["تطبيق الواجهة صلاحيات مستخدم تسجيل الدخول"]
P1["أنبوب مسمّى طلب واستجابة JSON بين حسابين مختلفين لذا يُصرَّح بـ ACL عبر PipeSecurity"]
S1["خدمة Windows حساب آخر مثل LocalSystem معالجة مقيمة ومعالجة امتيازات"]
U1 <--> P1
P1 <--> S1
end
subgraph K2["التكوين 2: جسر من تطبيق 32-bit إلى وظائف 64-bit"]
A2["تطبيق 32-bit (أصل قائم)"]
P2["أنبوب مسمّى أو COM خارج العمليّة"]
H2["عمليّة مساعدة 64-bit إنهاء مترابط للأب والابن عبر Job Object"]
D2["DLL أو SDK خاصّ بـ 64-bit"]
A2 <--> P2
P2 <--> H2
H2 --> D2
end
subgraph K3["التكوين 3: بيانات عالية التكرار من محرّك القياس إلى العرض"]
E3["محرّك قياس ومعالجة صور"]
C3["قناة التحكّم: أنبوب مسمّى بدء وإيقاف وتغيير إعدادات. عدّة مرّات في الثانية"]
D3["قناة البيانات: ذاكرة مشتركة + أحداث مسمّاة إطارات وموجات. عشرات المرّات في الثانية فما فوق"]
V3["تطبيق الواجهة (عرض)"]
V3 <--> C3
C3 <--> E3
E3 --> D3
D3 --> V3
end
التكوين 1: فصل تطبيق الواجهة عن خدمة Windows. تكوين يضع المعالجة المقيمة أو المعالجة التي تحتاج امتيازاً في خدمة، ويجعل الواجهة عمليّة مستخدم عاديّة (طريقة صنع جانب الخدمة في «مقال خدمة Windows» المنشور في اليوم نفسه). الاتّصال خياره الجادّ أنبوب مسمّى، والبروتوكول يبدأ بثبات من رسائل JSON + وضع الرسائل (أو وضع البايتات + بادئة طول)، وإن نمت أنواع الاستدعاء أمكن الانتقال إلى طبقة نقل أنبوب مسمّى لـ gRPC.2 الخدمة تعمل بحساب آخر (مثل LocalService) فلا يُستخدم CurrentUserOnly، والنقطة التصريح بـ ACL عبر PipeSecurity من نوع «السماح لمستخدمي Users بالقراءة والكتابة، ورفض البعيد».
التكوين 2: جسر من تطبيق 32-bit إلى وظائف 64-bit. إذا أردت استخدام DLL أو SDK برنامج تشغيل لا يعمل إلّا بـ 64-bit من تطبيق قائم 32-bit، فافصل جانب 64-bit إلى عمليّة أخرى. التنفيذ على طريقتين: (أ) إقامة عمليّة مساعدة 64-bit والتحدّث بأنبوب مسمّى، (ب) جعلها خادم COM خارج العمليّة 64-bit. إن كان الطرف المستدعي VBA أو لغة قديمة فـ (ب) أصدق، وبين C# وC# فـ (أ) أسهل توزيعاً وتحقيقاً. في (أ) استخدم نمط Job Object لبدء العمليّة المساعدة ومراقبتها والإنهاء المترابط للأب والابن كما كتبنا في «التعامل الآمن مع العمليّات الفرعيّة».
التكوين 3: بيانات عالية التكرار من محرّك القياس إلى العرض. في تكوين يجعل محرّك القياس ومعالجة الصور عمليّة منفصلة ويسيل البيانات إلى الواجهة، الشائع قناتان: التحكّم (بدء، إيقاف، إعدادات) أنبوب مسمّى، والبيانات (إطارات، موجات) ذاكرة مشتركة + أحداث مسمّاة. التحكّم عدّة مرّات في الثانية فيكفي الأنبوب، والبيانات تقترب من صفر النسخ بالذاكرة المشتركة. فصل القنوات يتيح تحسين تصميم جانب البيانات (مخزن حلقيّ وغيره) بمعزل عن مقتضيات التحكّم. لعرض حالة المحرّك والأجهزة الخارجيّة راجع أيضاً «عرض حالة الأجهزة الخارجيّة».
5. مثال تنفيذ ── خادم وعميل الأنبوب المسمّى غير المتزامنَين
نعرض الحدّ الأدنى في .NET 8 كأساس لتنفيذ أنبوب مسمّى. الشكل يشمل وضع الرسائل ودعم عملاء عدّة ومعالجة القطع. البروتوكول «JSON بـ UTF-8 رسالة واحدة في كلّ إرسال»، ويحمل حتماً حقل version. المثال أدناه يفترض اتّصالاً بين عمليّات المستخدم نفسه فيستخدم CurrentUserOnly. عند الاتّصال مع خدمة (حساب آخر) كما في التكوين 1، أزل CurrentUserOnly كما سيأتي وصرِّح بـ ACL عبر PipeSecurity.
أوّلاً جانب الخادم. في كلّ اتّصال يُعاد إنشاء تدفّق خادم جديد، وتُفصَل معالجة كلّ عميل إلى مهمّة.
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeServer(string pipeName)
{
// camelCase・大文字小文字非区別のWeb既定。System.Text.Jsonの既定は
// 大文字小文字を区別するため、これを共有しないと "version" が
// Request.Version に束縛されず、正しい要求が unknown_type に落ちる
internal static readonly JsonSerializerOptions JsonOptions =
new(JsonSerializerDefaults.Web);
public async Task RunAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var pipe = new NamedPipeServerStream(
pipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message, // 1書き込み=1メッセージ
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
await pipe.WaitForConnectionAsync(ct);
_ = Task.Run(() => HandleClientAsync(pipe, ct), ct); // 接続ごとに分離
}
}
// 1メッセージの上限。相手が巨大なメッセージを送ってきても
// サービス側のメモリを守る(IPCの相手も外部入力。第6章)
private const int MaxMessageBytes = 1024 * 1024;
private static async Task HandleClientAsync(
NamedPipeServerStream pipe, CancellationToken ct)
{
await using (pipe)
{
var buffer = new byte[64 * 1024];
try
{
while (!ct.IsCancellationRequested)
{
// メッセージモードでも、1回のReadで1メッセージ全部が
// 読めるとは限らない。IsMessageComplete まで読み切る
using var ms = new MemoryStream();
do
{
int n = await pipe.ReadAsync(buffer, ct);
if (n == 0) return; // クライアントが切断した
ms.Write(buffer, 0, n);
if (ms.Length > MaxMessageBytes) return; // 上限超過。切断する
} while (!pipe.IsMessageComplete);
byte[] response = Dispatch(ms.ToArray());
await pipe.WriteAsync(response, ct);
}
}
catch (IOException)
{
// このクライアントとの通信路が壊れただけ。
// サーバー全体は止めず、他の接続の処理を続ける
}
}
}
private static byte[] Dispatch(byte[] payload)
{
Request? req;
try { req = JsonSerializer.Deserialize<Request>(payload, JsonOptions); }
catch (JsonException) { req = null; }
// 形式不正・未知のバージョン・未知のtypeはここで検査して拒否する
object result = req switch
{
null => new { version = 1, error = "bad_request" },
// version 未指定(0に束縛される)や未知のバージョンはここで拒否する
{ Version: not 1 } => new { version = 1, error = "version_unsupported" },
{ Type: "getStatus" } => new { version = 1, running = true },
{ Type: "startJob" } => new { version = 1, jobId = StartJob(req) },
_ => new { version = 1, error = "unknown_type" },
};
return JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions);
}
}
public sealed record Request(int Version, string Type, JsonElement? Body);
جانب العميل يغلق «اتّصال ← طلب ← استجابة» في طريقة واحدة، ويدخل المهلة وإعادة المحاولة من البداية.
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeClient(string pipeName)
{
// idempotent: نعيد المحاولة فقط عندما يعلن المستدعي أن هذا الطلب
// آمن حتى إن وصل مرّتين. الافتراض: «لا تعِد المحاولة»
public async Task<TResponse?> RequestAsync<TResponse>(
object request, CancellationToken ct, bool idempotent = false)
{
for (int attempt = 1; ; attempt++)
{
// 接続だけでなく要求→応答の交換全体に上限時間を設ける。
// サーバーが接続だけ受けて応答を書かずに固まった場合の
// 無限待ちを防ぐ
using var deadline =
CancellationTokenSource.CreateLinkedTokenSource(ct);
deadline.CancelAfter(TimeSpan.FromSeconds(10));
try
{
using var pipe = new NamedPipeClientStream(
".", pipeName, PipeDirection.InOut,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
// 無限に待たない。サーバー未起動なら例外になる
await pipe.ConnectAsync(timeout: 3000, deadline.Token);
pipe.ReadMode = PipeTransmissionMode.Message;
await pipe.WriteAsync(
JsonSerializer.SerializeToUtf8Bytes(
request, PipeServer.JsonOptions),
deadline.Token);
using var ms = new MemoryStream();
var buffer = new byte[64 * 1024];
do
{
int n = await pipe.ReadAsync(buffer, deadline.Token);
if (n == 0) throw new IOException("قطع الخادم الاتصال.");
ms.Write(buffer, 0, n);
} while (!pipe.IsMessageComplete);
return JsonSerializer.Deserialize<TResponse>(
ms.ToArray(), PipeServer.JsonOptions);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw; // 呼び出し側のキャンセル。再試行しない
}
catch (Exception ex) when (
ex is IOException or TimeoutException or OperationCanceledException
&& idempotent && attempt < 3)
{
// نعيد المحاولة عند فشل مؤقّت مثل إعادة تشغيل الخادم.
// في حالة «أُرسل لكنّ الاتّصال انقطع قبل قراءة الاستجابة»
// قد يكون الطلب قد نُفِّذ أصلاً. لذلك طلبات ذات أثر جانبي مثل startJob
// لا تُعاد محاولتها افتراضياً. صمِّم أوّلاً رفض التكرار بـ request ID
// ثمّ مرِّر idempotent: true (الفصل 6)
await Task.Delay(500 * attempt, ct);
}
}
}
}
نكمل ثلاث قرارات تصميم في هذه الشيفرة فقط.
- أدخل
versionمن الإصدار الأوّل. الواجهة والخدمة تُحدَّثان منفصلتين، ولحظة «أحدهما أحدث» آتية حتماً. رفض الجانب المستجيب «إصداراً لا يعرفه» صراحة يحوّل حادث السلوك الغريب الصامت إلى «خطأ مفهوم». - احسم: اتّصال يُرمى بعد الاستخدام أم اتّصال دائم. المثال أعلاه نوع يُرمى يتّصل لكلّ طلب، فلا يحتاج إدارة حالة القطع وإعادة الاتّصال، وبالمقابل لا يناسب استدعاءات عالية التكرار. إن لزم اتّصال دائم + إشعارات دفع من الخادم، صارت تلك التعقيدات مادّة حكم للانتقال إلى البثّ ثنائيّ الاتّجاه في gRPC.
- عند الاتّصال مع خدمة أزل
CurrentUserOnlyوصمِّمPipeSecurity. المثال أعلاه يفترض عمليّات المستخدم نفسه. إن كان الطرف خدمة بحساب آخر، فأنشئ تدفّق خادم مع ACL عبرNamedPipeServerStreamAcl.Createوصرِّح بالمجموعة المسموح لها بالاتّصال.6
5.1 تأكيد العمل ── ترتيب التشغيل، وما الذي تراه لتقول إنّه نجح
الشيفرة أعلاه تعمل إن أُلصقت في تطبيقي سطر أوامر. بعد النسخ أكّد بالترتيب التالي.
أوّلاً جانب الخادم. في Program.cs للمشروع الذي ألصقت فيه PipeServer اكتب البدء والإيقاف فقط.
// サーバー側 Program.cs (.NET 8 / トップレベルステートメント)
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
Console.WriteLine(@"بدء الانتظار: \\.\pipe\demo-ipc (Ctrl+C للإيقاف)");
try
{
await new PipeServer("demo-ipc").RunAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("توقّف.");
}
ثمّ جانب العميل. ارمِ طلباً واحداً واعرض الاستجابة فقط.
// クライアント側 Program.cs (.NET 8 / トップレベルステートメント)
using System.Text.Json;
var client = new PipeClient("demo-ipc");
var res = await client.RequestAsync<JsonElement>(
new { version = 1, type = "getStatus" }, CancellationToken.None);
Console.WriteLine(res); // {"version":1,"running":true}
إجراء التأكيد كالتالي.
- شغِّل الخادم أوّلاً. ظهور «بدء الانتظار» يعني الجاهزيّة.
- شغِّل العميل. يُعرض JSON الذي أعاده
Dispatchفي جانب الخادم كما هو. صيرورة الاستجابة{"version":1,"running":true}لأنّ الإخراج camelCase عبرJsonSerializerDefaults.Web. - أوقف الخادم عمداً وشغِّل العميل وحده. يعمل
ConnectAsync(timeout: 3000, ...)وتأكّد أنّ الفشل يعود خلال 3 ثوانٍ. إن تجمّد هنا ارتدّ لاحقاً كـ«سقوط الخدمة يجمد الواجهة معها». - جرِّب ثلاث حالات شاذّة. عند اجتيازها فقط تقول «البروتوكول يعمل».
| ما تجرّبه | ما يرسله العميل | النتيجة المتوقَّعة |
|---|---|---|
| إصدار مجهول | new { version = 2, type = "getStatus" } |
{"version":1,"error":"version_unsupported"} |
| أمر مجهول | new { version = 1, type = "reboot" } |
{"version":1,"error":"unknown_type"} |
| رسالة تتجاوز الحدّ | متن يتجاوز 1 ميغابايت (MaxMessageBytes) |
يقطع الخادم صامتاً، ويصير جانب العميل IOException |
إن أردت رؤية الأنبوب قائماً من جانب نظام التشغيل، يمكن تعداد الأسماء تحت \\.\pipe\ بأداة pipelist من Sysinternals. تفصل في دقيقة هل سبب «لا يتّصل» خطأ إملاء اسم الأنبوب أم سقوط الخادم، فينفع إدراجها في دليل التحقيق.
6. تنبيهات مشتركة في تصميم البروتوكول ── العمل بعد اختيار الوسيلة
أيّاً كان IPC الذي تختاره، لتصميم محتوى الاتّصال قضايا مشتركة. وهذه في الواقع منبع الحوادث أكثر من اختيار الوسيلة.
- حدّ الرسالة (التجزئة إلى إطارات): ما عدا الحالات التي يحدّ فيها نظام التشغيل كما في وضع رسائل الأنبوب، «من أين إلى أين رسالة واحدة» مسؤوليّة التطبيق. حدّ السجلّ في الذاكرة المشتركة المشكلة نفسها. اجعل أسلوب بادئة الطول أساساً.
- إدارة الإصدار: ضمّن إصدار الصيغة في الرسالة، ويرفض جانب الاستقبال «إصداراً لا يعرفه» صراحة. ضمان تحديث العمليّتين دائماً معاً غير موجود حتّى إن وُزِّعتا بالمثبِّت نفسه.
- المهلة: صمِّم على أنّ «الطرف لا يردّ» يحدث حتماً. حدِّد زمناً أعلى لكلّ من الاتّصال والطلب والاستجابة، ولا تُبقِ شيفرة انتظار بلا نهاية. الانتظار المتزامن على خيط الواجهة خارج البحث.
- إعادة الاتّصال وتماثل القوّة: إعادة المحاولة تعني إمكان وصول الطلب نفسه مرّتين. صنِّف «هل الطلب آمن إن نُفِّذ مرّتين» حسب نوع الطلب، وما ليس آمناً (إدخال مهمّة مثلاً) ألحق به معرّف طلب واطرح التكرار.
- تحقّق من الطرف أيضاً كإدخال خارجيّ: في اتّصال داخل الجهاز نفسه يسهل إسقاط التحقّق بـ«الطرف تطبيقي»، لكن عند وجود حدّ صلاحيّة، طرف IPC إدخال غير موثوق كالوارد من الشبكة. وسيط امتيازات المدير أو الخدمة لا يجوز أن ينفّذ كما هي مسارات وأوامر وردت من عميل بصلاحيّة قياسيّة. بما في ذلك احتلال اسم الأنبوب (القسم 2.2)، أدب IPC عبر حدود الصلاحيّة الشكّ في «من» و«ماذا» معاً.
- أبقِ وسيلة رصد: تمكين سجلّ اتّصال (على الأقلّ نوع الرسالة والطرف والنتيجة) يغيّر زمن تحقيق «لا يتّصل» و«لا يعود» بمراتب.
7. الخلاصة
اختيار الاتّصال بين العمليّات يكاد لا يتردّد إن فكّرت بهذا الترتيب. أوّلاً هل يكفي الجهاز نفسه. إن كفى فالطلب والاستجابة أنبوب مسمّى، والبيانات كبيرة الحجم عالية التكرار ذاكرة مشتركة فقط، ودفعات غير محكمة تكامل ملفّات. إن رُئي عبور أجهزة أو اختلاط لغات فـ TCP محلّيّ أو gRPC، وعلى حجم تصير فيه إدارة البروتوكول الذاتيّ عبئاً فـ gRPC. COM ليس الخيار الأوّل للجديد، ويُحفَظ أداة لجسر 32/64 وتكامل VBA ── جدول القرار في هذا المقال هذا المسار في هيئة جدول.
وبعد حسم الوسيلة، أدخل في الإصدار الأوّل التصميم المشترك في الفصل 6 (تجزئة، إصدار، مهلة، إعادة اتّصال، التحقّق من الطرف). أعطال IPC في العمل، بإحساسنا، أسبابها «إسقاط تصميم البروتوكول» أكثر بكثير من «خطأ اختيار الوسيلة». يُستحسَن بدء مراجعة تقسيم العمليّات أو أسلوب الاتّصال بجرد «أيّ عمليّة، وبصلاحيّة من، وماذا، وبأيّ تكرار، تتبادل».
مقالات ذات صلة
- مطبّات الذاكرة المشتركة وأفضل الممارسات العمليّة
- وهم أنّ Receive يصل بوحدة Send في TCP ── تصميم الاستقبال بالتعامل مع TCP كتدفّق بايتات
- كيف تفصل في تطبيق Windows «المعالجة التي تحتاج امتيازات مدير فقط» كتابة ملموسة
- كيفيّة إنشاء خدمات Windows وتشغيلها ── من التمييز عن جدولة المهام إلى تحويل BackgroundService إلى خدمة
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع مراجعة تصميم تقسيم العمليّات وأسلوب الاتّصال، وتصميم التكامل مع الأصول القائمة بما فيه جسر 32-bit/64-bit، وتحقيق أسباب أعطال اتّصال من نوع «لا يتّصل ولا يعود».
- الاستشارات التقنيّة ومراجعة التصميم
- تطوير تطبيقات Windows
- دعم استخدام الأصول القائمة والترحيل
- التواصل معنا
روابط مرجعيّة
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication. حول مثال تنفيذ خادم وعميل يدعمان عملاء عدّة عبر NamedPipeServerStream / NamedPipeClientStream. ↩
-
Microsoft Learn, Inter-process communication with gRPC and Named pipes. حول تكوين تشغيل gRPC على أنبوب مسمّى عبر ListenNamedPipe في Kestrel ابتداءً من .NET 8، والتحكّم بالوصول عبر PipeSecurity. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, MemoryMappedFile Class. واجهة .NET للملفّ المعيَّن في الذاكرة. حول ملاءمة الذاكرة المشتركة غير المرتبطة بملفّ، المنشأة بـ CreateNew، لاستخدام IPC. ↩ ↩2
-
Microsoft Learn, Interprocess communications. حول قائمة آليّات IPC التي يقدّمها Windows (الحافظة، COM، WM_COPYDATA، DDE، تعيين الملفّات، mailslot، الأنابيب، RPC، مقابس Windows) ومبادئ الفصل بينها. ↩ ↩2 ↩3
-
Microsoft Learn, NamedPipeServerStream Class. تدفّق جانب الخادم للأنبوب المسمّى. حول المنشئ الذي يحدّد PipeTransmissionMode وPipeSecurity. ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. حول أنّ واصف الأمان الافتراضيّ للأنبوب المسمّى يسمح بالقراءة لـ Everyone وللحسابات المجهولة، وتكوين حقوق الوصول. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum. حول أنّ CurrentUserOnly يسمح بالاتّصال فقط مع «طرف أنشأه المستخدم نفسه»، وأنّه على Windows يتحقّق أيضاً من مستوى الرفع إضافة إلى حساب المستخدم. ↩ ↩2
-
Microsoft Learn, Inter-process communication with gRPC. حول طبقات النقل عند استخدام gRPC لـ IPC (مقابس نطاق Unix، الأنابيب المسمّاة)، واعتبارات الأمان مثل التحقّق من مالك الخادم وإجراءات انتحال الهويّة (impersonation). ↩ ↩2
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
كيف تختار مكان حفظ بيانات تطبيق Windows محليّاً ── جدول قرار بين SQLite وJSON وRegistry وAccess
أين ينبغي حفظ بيانات تطبيق سطح مكتب Windows، وبأيّ صيغة؟ نرتّب الفرق بين AppData وProgramData، ونقاط قوّة وعيوب كلّ من SQLite وملفّات JSO...
أفضل الممارسات العمليّة لتعدّد مؤشّرات الترابط: نسخة .NET ── ما تقرِّره قبل إضافة مؤشّرات ترابط
قواعد تصميم عمليّة تمنع شيفرة .NET/C# متعدّدة مؤشّرات الترابط من الانهيار أو التجمّد أحياناً: اركب على Task بدل إنشاء المؤشّرات بنفسك، قل...
التوافق الخلفي لواجهات DLL وCOM ── جدول قرار لتحديد أيّ تغيير يكسر جهة الاستدعاء
أيّ تغيير في مكوّنات DLL أو COM يكسر جهة الاستدعاء؟ نرتّب الطبقات الثلاث للتوافق: الثنائيّ، والمصدريّ، والسلوكيّ، ونقدّم جدول قرار حسب نو...
كيف نفهم عزل الجلسات في Windows ── Session 0 وRDP وتشغيل عدة مستخدمين معاً
نوضح مفهوم «الجلسة» الذي يربك كثيراً من مطوّري تطبيقات Windows. نشرح عملياً سبب عزل Session 0 الذي يمنع الخدمة من عرض واجهة، وسلوك الجلسا...
منع التشغيل المتعدّد لتطبيقات Windows ── Mutex المسمّى وتفعيل النافذة عند التشغيل المزدوج
نرتّب تنفيذ منع التشغيل المتعدّد لتطبيقات Windows للأعمال بـ Mutex مسمّى. نشرح الفرق بين Global\ وLocal\ ومطبّاته في بيئات RDP، وAbandone...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
دعم إعادة استخدام الأصول القديمة وترحيلها
ندعم إعادة استخدام وترحيل الأصول التي تحمل قيود COM / ActiveX / OCX أو 32bit / 64bit.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما الذي ينبغي أن يكون الخيار الأوّل للاتّصال بين عمليّات Windows؟
- بالنسبة إلى نمط الطلب والاستجابة (إرسال أمر واستلام النتيجة) داخل الجهاز نفسه، فإنّ الأنابيب المسمّاة هي الخيار الأوّل. فهي معياريّة في نظام التشغيل، ولا تحتاج إلى إدارة رقم منفذ، ولا تصطدم بجدار الحماية، وهي مدمَجة مع التحكّم بالوصول في Windows (PipeSecurity)، وتُكتَب بسهولة من .NET عبر System.IO.Pipes. إن كان من المحتمل تجاوز حدود الشبكة مستقبلاً، فمن البداية اختر TCP المحليّ أو gRPC، وللبيانات كبيرة الحجم وعالية التكرار (كإطارات الصور والموجات) اختر الذاكرة المشتركة فقط، ولتكامل غير محكم غير متزامن مع رغبة في الاحتفاظ بسجلّ تدقيق اختر تكامل الملفّات ── هذا هو الاختيار الصحيح بأسلوب الاستبعاد التدريجيّ.
- متى ينبغي استخدام gRPC في الاتّصال بين العمليّات؟
- عندما تزداد أنواع الخدمات أو الاستدعاءات وتصبح صيانة جملة switch الخاصّة ببروتوكولك الذاتيّ وتوثيقها عبئاً. عندئذٍ تحصل على تعريف المخطّط وتوليد الشيفرة عبر ملفّات proto، وعلى البثّ ثنائيّ الاتّجاه، كما يدعم ASP.NET Core (Kestrel) ابتداءً من .NET 8 الأنابيب المسمّاة مباشرةً كطبقة نقل. في المقابل، بين أدوات لا تتجاوز أوامرها نوعين أو ثلاثة، يكون gRPC مبالغاً فيه بسبب حمل بنية استضافة ASP.NET Core، وتكون الأنابيب المسمّاة + JSON أقلّ تكلفة إجماليّة. لا حرج في الانتقال بعد أن يتجاوز عدد الرسائل التي تريد كتابتها في proto عشرة، أو تصبح إشعارات البثّ ضرورة جوهريّة، أو يكون الطرف الآخر خارج .NET.
- كيف أستخدم DLL بنظام 64 بت من تطبيق بنظام 32 بت؟
- بما أنّ عمليّة 32 بت لا يمكنها تحميل DLL بنظام 64 بت في العمليّة نفسها، يُفصَل جانب 64 بت إلى عمليّة منفصلة. التنفيذ على طريقتين: (أ) إطلاق عمليّة مساعِدة بنظام 64 بت والتواصل عبر أنبوب مسمّى، و(ب) جعله خادم COM خارج العمليّة بنظام 64 بت. إن كان الطرف المستدعي VBA أو لغة قديمة، فالطريقة (ب) أبسط، لأنّ بنية COM تتولّى المارشلينغ نيابة عنك فتحتاج إلى تعديل ضئيل في شيفرة الطرف المستدعي. أمّا بين تطبيقين مكتوبين بـ C#، فالطريقة (أ) أسهل توزيعاً وتحقيقاً. في حالة (أ) صمِّم إنهاء العمليّة المساعدة المرتبط بالعمليّة الأمّ عبر Job Object.
- في أيّ الحالات ينبغي استخدام الذاكرة المشتركة؟
- تُستخدَم حصراً للبيانات كبيرة الحجم وعالية التكرار كإطارات الصور أو بيانات الموجات. إنّها أسرع وسيلة اتّصال بين العمليّات داخل الجهاز نفسه لأنّها لا تنطوي على نسخ ولا تسلسل، لكنّها لا توفّر أيّ مزامنة على الإطلاق، فيتوجّب عليك تصميم آليّة لعدم قراءة بيانات في منتصف الكتابة، واكتشاف حياة أو وفاة الطرف الآخر، والتعافي بعد الإنهاء غير الطبيعيّ، كلّ ذلك بنفسك. في العمل الفعليّ، البِنية الشائعة هي وضع مستوى البيانات (data plane) فقط في الذاكرة المشتركة، وإرسال مستوى التحكّم (control plane) مثل البدء والإيقاف وتغيير الإعدادات إلى قناة منفصلة كالأنبوب المسمّى. إذا بدأتَ تحاول تمرير رسائل التحكّم أيضاً عبر الذاكرة المشتركة، فتلك علامة على ضرورة إعادة النظر في التصميم.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.