1. مؤشر الوظيفة
يحصل AddressOf على مؤشر دالة داخل VB. يمكننا تمرير مؤشر الدالة هذا إلى واجهة برمجة التطبيقات (API) التي تحتاج إلى إعادة استدعاء هذه الوظيفة. وتتمثل مهمتها في السماح للبرامج الخارجية باستدعاء الوظائف داخل VB.
ومع ذلك، فإن تطبيق مؤشرات الوظائف في VB ليس واسع النطاق كما هو الحال في C، لأن مستند VB يقدم فقط كيفية تمرير مؤشرات الوظائف إلى واجهات برمجة التطبيقات لتنفيذ عمليات الاسترجاعات، ولا يشير إلى العديد من الوظائف السحرية لمؤشرات الوظائف، لأن VB لا يُشجع على استخدام المؤشرات، فالمؤشرات الوظيفية ليست استثناءً.
أولاً، دعونا نصنف كيفية استخدام المؤشرات الوظيفية.
1. رد الاتصال. هذه هي الوظيفة الأساسية والأكثر أهمية. على سبيل المثال، جوهر تقنية اشتقاق الفئة الفرعية المقدمة في وثائق VB هو واجهتي برمجة التطبيقات: SetWindowLong وCallWindowPRoc.
يمكننا استخدام SetWindowLong API لاستبدال مؤشر وظيفة النافذة الأصلي بمؤشر الوظيفة الخاص بنا وحفظ مؤشر وظيفة النافذة الأصلي. بهذه الطريقة، يمكن إرسال رسائل النافذة إلى وظائفنا الخاصة، ويمكننا استخدام CallWindowProc لاستدعاء مؤشر النافذة المحفوظ مسبقًا في أي وقت لاستدعاء وظيفة النافذة الأصلية. بهذه الطريقة، يمكننا التعامل مع الرسائل المعلقة دون تدمير وظيفة النافذة الأصلية.
يجب أن نكون على دراية بالمعالجة المحددة، ويشرحها مستند VB أيضًا بشكل واضح جدًا. ما يحتاج إلى اهتمام هنا هو CallWindowProc API، وسنرى استخدامه الرائع لاحقًا.
نحن هنا نستدعي عمليات الاسترجاعات للسماح "بالاستدعاءات الخارجية لمؤشرات الوظائف الداخلية".
2. للاستخدام الداخلي للبرنامج. على سبيل المثال، في لغة C، يمكننا تمرير مؤشر دالة C كمعلمة إلى دالة C التي تتطلب مؤشر دالة، مثل دالة مكتبة C qsort التي سيتم مناقشتها لاحقًا:
يتطلب مؤشر دالة من النوع COMPARE لمقارنة أحجام متغيرين، بحيث يمكن لوظيفة الفرز استدعاء مؤشر الوظيفة هذا لمقارنة أنواع مختلفة من المتغيرات، بحيث يمكن لـ qsort فرز صفائف متغيرة من أنواع مختلفة.
دعنا نسمي هذا التطبيق "استدعاء مؤشرات الوظائف الداخلية من الداخل".
3. استدعاء الوظائف الخارجية
قد تتساءل، أليس استخدام واجهة برمجة التطبيقات (API) هو مجرد استدعاء وظائف خارجية؟ نعم، ولكن في بعض الأحيان ما زلنا بحاجة للحصول على مؤشر الدالة الخارجية مباشرة. على سبيل المثال، تحميل DLL ديناميكيًا من خلال LoadLibrary، ثم الحصول على مؤشر إدخال الوظيفة الذي نحتاجه من خلال GetProcAddress، ثم استدعاء الوظائف الخارجية من خلال مؤشر الوظيفة هذا، تتيح لنا تقنية تحميل DLL ديناميكيًا استدعاء الوظائف الخارجية بشكل أكثر مرونة.
نسمي هذه الطريقة "استدعاء مؤشر دالة خارجي من الداخل"
4. وغني عن القول أنه يمكننا أيضًا التحكم في "استدعاء مؤشرات الوظيفة الخارجية من الخارج". لا، على سبيل المثال، يمكننا تحميل ملفات DLL متعددة وتمرير مؤشر الوظيفة في ملف DLL واحد إلى الوظيفة في ملف DLL آخر.
الأقسام "الداخلية" و"الخارجية" المذكورة أعلاه كلها مصطلحات نسبية (مكتبة الارتباط الحيوي (DLL) في الواقع لا تزال قيد المعالجة). سيساعدنا هذا التصنيف في مناقشة المشكلات في المستقبل. يرجى تذكر تصنيفي أعلاه، لأن المقالات المستقبلية ستستخدم هذا التصنيف أيضًا لتحليل المشكلة.
إن استخدام المؤشرات الوظيفية ليس أكثر من الطرق الأربع المذكورة أعلاه. ولكن في الاستخدام الفعلي فهو مرن وقابل للتغيير. على سبيل المثال، الوراثة وتعدد الأشكال في لغة C، والواجهات في COM كلها تطبيقات ذكية لجدول مؤشر دالة يسمى vTable. يمكن أن يؤدي استخدام المؤشرات الوظيفية إلى جعل معالجة البرامج أكثر كفاءة ومرونة.
باستثناء الطريقة الأولى، لا يقدم مستند VB طرقًا أخرى، وينص أيضًا بوضوح على أن مؤشر الوظيفة "Basic to Basic" غير مدعوم (أي الطريقة الثانية المذكورة أعلاه في الواقع، من خلال HACK معين). يمكن تحقيق جميع الطرق الأربع المذكورة أعلاه. اليوم، دعونا نلقي نظرة على كيفية تنفيذ الطريقة الثانية، نظرًا لأنها سهلة التنفيذ نسبيًا، فلنبدأ بالطريقة البسيطة. أما بالنسبة لكيفية استدعاء مؤشرات الوظائف الخارجية في VB، وكيفية تحقيق التطبيق الذكي لمؤشرات الوظائف المختلفة من خلال معالجة جدول القفز لمؤشر وظائف واجهة vTable في VB، نظرًا لأن هذا سيتضمن المبادئ الداخلية لـ COM، فسوف أشرح ذلك في مقال آخر .
في الواقع، وثائق VB ليست خاطئة، لا يدعم VB مؤشر الوظيفة "Basic to Basic"، ولكن يمكننا إنشاء طريقة ملتوية لتحقيق ذلك، أي "Basic to API" أولاً، ثم استخدم الطريقة الأولى. "استدعاء مؤشر الوظيفة الداخلي" للانتقال من "API إلى BASIC" ، وبالتالي تحقيق الغرض من الطريقة الثانية من "Basic إلى Basic". يمكننا أن نطلق على هذه التقنية "رد الاتصال القسري" ، وهو متوفر فقط في VB. هذا غريب تكنولوجيا.
إنه أمر معقد بعض الشيء، لكن فكر جيدًا في CallWindowProc في تقنية اشتقاق الفئة الفرعية للنافذة. يمكننا استخدام CallWindowProc لإجبار نظام التشغيل الخارجي على استدعاء مؤشر وظيفة النافذة الأصلية المحفوظة لدينا مؤشر الوظيفة.
هاها، قلت من قبل أننا يجب أن نتحدث أقل عن المبادئ وأكثر عن الحركات الآن دعونا نبدأ في تعلم الحركات!
ضع في اعتبارك أننا نطبق qsort في VB الذي يدعم مقارنة الكلمات الرئيسية المتعددة تمامًا مثل لغة C. يمكن العثور على الكود المصدري الكامل في الكود الداعم لهذه المقالة، ويرد هنا فقط الكود المتعلق بتطبيق مؤشر الوظيفة.
دعونا نلقي نظرة أخيرة على بيان qsort النهائي.
ArrayPtr أعلاه هو مؤشر للعنصر الأول في المصفوفة الذي يحتاج إلى فرز، وnCount هو عدد العناصر في المصفوفة، وnElemSize هو حجم كل عنصر، وpfnCompare هو مؤشر وظيفة المقارنة لدينا. هذا الإعلان يشبه إلى حد كبير qsort في وظيفة مكتبة C.
مثل لغة C، يمكننا تمرير مؤشر دالة Basic إلى دالة qsort الخاصة بـ Basic.
كيفية استخدامه:
أيها الأصدقاء الأذكياء، هل رأيتم اللغز هنا بالفعل؟ كاختبار، هل يمكنك الآن أن تعطيني طريقة لاستخدام مؤشرات الوظائف في qsort؟ على سبيل المثال، نحتاج الآن إلى مقارنة حجم العنصر i والعنصر j في المصفوفة عن طريق استدعاء مؤشر دالة.
نعم، بالطبع يجب عليك استخدام واجهة مقارنة API التي تم الإعلان عنها سابقًا (في الواقع CallWindowProc) لإجراء عمليات الاسترجاعات القسرية.
التنفيذ المحدد هو كما يلي:
تم تقديم الحركات، هل تفهم؟ اسمحوا لي أن أشرح بإيجاز معنى المقارنة أعلاه، فهي تستخدم واجهة برمجة تطبيقات CallWindowProc بشكل ذكي للغاية. تتطلب واجهة برمجة التطبيقات هذه خمس معلمات. المعلمة الأولى هي مؤشر وظيفة عادي. يمكن لواجهة برمجة التطبيقات هذه إعادة استدعاء مؤشر الوظيفة على الفور وتمرير المعلمات الأربعة الأخيرة من واجهة برمجة التطبيقات هذه إلى الوظيفة التي يشير إليها مؤشر الوظيفة. ولهذا السبب يجب أن تحتوي وظيفة المقارنة لدينا على أربع معلمات، لأن واجهة برمجة تطبيقات CallWindowProc تتطلب أن يتوافق مؤشر الوظيفة الذي تم تمريره إليها مع النموذج الأولي لوظيفة WndProc. النموذج الأولي لـ WndProc هو كما يلي:
يمكن أن تتوافق كل من LRESULT وHWND وUINT وWPARAM وLPARAM المذكورة أعلاه مع النوع الطويل في VB، وهذا أمر رائع حقًا، لأنه يمكن استخدام النوع الطويل كمؤشر!
دعونا نلقي نظرة على سير العمل مرة أخرى. عندما نستدعي qsort باستخدام AddressOfCompareSalaryName كمعلمة مؤشر الدالة، يتم تعيين المعلمة الرسمية pfnCompare لـ qsort كمؤشر الدالة للمعلمة الفعلية CompareSalaryName. في هذا الوقت، استدعاء مقارنة لفرض رد اتصال إلى pfnCompare يعادل استدعاء عبارة VB التالية:
ألن يتسبب هذا في حدوث خطأ في عدم تطابق نوع المعلمة؟ أليست المعلمتان الأوليان من CompareSalaryName من النوع TEmployee؟ في الواقع، مثل هذا الاتصال غير ممكن في VB، لأن التحقق من النوع في VB لن يسمح بمثل هذا الاتصال. ومع ذلك، فإن هذا الاستدعاء هو في الواقع رد اتصال تم إجراؤه بواسطة واجهة برمجة التطبيقات (API)، ومن المستحيل على VB التحقق مما إذا كان نوع المعلمة لوظيفة رد اتصال API هو نوع رقمي طويل عادي أو مؤشر بنية، لذلك يمكن القول أيضًا أن لدينا تجاوزت تحكم VB في معلمات الوظائف للتحقق من النوع، يمكننا أن نعلن عن هذه المعلمة الطويلة كمؤشر من أي نوع، مهما كان ما نعلن عنه، فإن VB سيعتقد أنه كذلك. لذلك، يجب علينا استخدام هذه التقنية بعناية. على سبيل المثال، فإن المعلمة "ArrayPtr (i-1)*nElemSize" التي سيتم تمريرها في النهاية إلى الدالة CompareSalaryName أعلاه هي مجرد عنوان، ولن يقوم VB بالتحقق من هذا العنوان دائمًا يتم التعامل مع العنوان كمؤشر من النوع TEmployee إذا تم استخدامه عن طريق الخطأ كـ "ArrayPtr i*nElemSize"، فعندما يكون i هو العنصر الأخير، سنتسبب في حدوث خطأ في الوصول إلى الذاكرة، لذلك يتعين علينا الانتباه إلى مشكلات الحدود تمامًا كما هو الحال عند التعامل مع المؤشرات في C.
يمكن بالفعل رؤية التطبيق الذكي لمؤشرات الوظائف هنا، ولكن الطريقة المقدمة هنا لا تزال بها قيود كبيرة. يجب أن تحتوي وظيفتنا على أربع معلمات. تتمثل الطريقة الأنظف في كتابة ملف DLL في VC أو دلفي وإنشاء واجهة برمجة تطبيقات أكثر توافقًا لتنفيذ الوظائف مشابه لـ CallWindowProc. لقد تتبعت التنفيذ الداخلي لـ CallWindowProc، وهو يقوم بالكثير من العمل المتعلق برسائل النوافذ، وهو أمر زائد عن الحاجة في تطبيقنا. في الواقع، لتنفيذ واجهة برمجة تطبيقات رد الاتصال القسري، ما عليك سوى دفع المعلمات القليلة الأخيرة إلى المكدس ثم استدعاء المعلمة الأولى، وهي مجرد عدد قليل من تعليمات التجميع.
بسبب قيود CallWindowProc بالتحديد، لا يمكننا استخدامه لاستدعاء مؤشرات دالة خارجية لتنفيذ طريقة استدعاء مؤشر الدالة الثالثة المذكورة أعلاه. لتنفيذ الطريقة الثالثة، قدم Master MattCurland طريقة HACK تشبه الكابوس، نحتاج إلى إنشاء واجهة IUnknown من فراغ في VB، وإضافة إدخال جديد بعد الإدخالات الثلاثة الأصلية لـ vTable لواجهة IUnknown إدخال جديد أدخل رمز الجهاز فيه، يجب أن يتعامل رمز الجهاز هذا مع هذا المؤشر قبل استدعاء مؤشر الوظيفة الذي قدمناه في النهاية. لا يمثل مؤشر الوظيفة هذا مشكلة سواء كان داخليًا أو خارجيًا. سأعود إلى هذه الطريقة عندما نتعمق في تفاصيل COM الداخلية.
بالإضافة إلى ذلك، تعد خوارزميات الفرز مسألة رأي في الأصل، حيث كنت أرغب في تقديم الخوارزمية الأكثر تنوعًا بأفضل أداء في هذه المقالة. على الرغم من أن هذه الفكرة جيدة، إلا أنه لا توجد خوارزمية "أفضل" في كل موقف. يجب أن تكون طريقة الفرز السريع التي يتم تنفيذها باستخدام تقنيات المؤشر المختلفة المتوفرة في هذه المقالة أسرع بكثير وتستهلك ذاكرة أقل بكثير من استخدام تقنية الكائن لتنفيذ نفس الوظيفة. ولكن حتى خوارزمية الفرز السريع هذه، التي قمت بتحسينها، لا تزال غير جيدة مثل ShellSort لأن ShellSort سهل التنفيذ. من الناحية النظرية من الخوارزمية، يجب أن يكون أداء qsort متوسطًا أفضل من ShellSort، ولكن هذا ليس هو الحال بالضرورة في VB (يمكنك رؤية الكود الداعم لهذه المقالة، والذي يوفر أيضًا الكود الداعم لعمود ShellSort VBPJ، وهو أمر جيد جدًا) ، وفكرة هذه المقالة مأخوذة من هذا ShellSort).
ومع ذلك، تجدر الإشارة إلى أنه يمكن تحسين كل من Quick Sort وShellSort هنا بشكل كبير، لأن تنفيذهما يتطلب استخدامًا مكثفًا لـ CopyMemroy لنسخ البيانات (وهذا أحد عيوب استخدام المؤشرات في VB). في الواقع، لدينا طريقة أفضل، وهي اختراق بنية مصفوفة VB، وهي SafeArray في أتمتة COM. يمكننا وضع مؤشرات كل عنصر مصفوفة في SafeArray في مصفوفة طويلة مرة واحدة، بدون CopyMemroy لتبادل العناصر في المصفوفة الطويلة لتحقيق الغرض من تبادل مؤشرات عناصر مصفوفة SafeArray في الوقت الفعلي، لا يتم نقل البيانات، بل يتم نقل المؤشرات فقط.
->