Logo

ماركو كانتو:
أساسي باسكال

الفصل 9
برمجة ويندوز

توفّر دلفي تغليفا كاملا للمستويات الدنيا لوظائف API في ويندوز و ذلك باستخدام اوبجكت باسكال و مكتبة المكونات المرئية (VCL)، لذا فإن الحاجة نادرة لبناء تطبيقات ويندوز باستخدام لغة باسكال صافية و إستدعاءات مباشرة لوظائف API. المبرمجون الذين يحتاجون لإستخدام بعض التقنيات الخاصة غير المدعومة من قبل VCL لا يزال لديهم هذا الخيار في دلفي. سوف ترغب في هذا التوجه في حالات خاصة جدا، مثل بناء مكوّنات دلفي جديدة تعتمد على إستدعاءات API غير معتادة، و أنا لا أريد الخوض في تفاصيل هذا الأمر. بدلا من ذلك، سوف ننظر إلى بعض عناصر تفاعل دلفي مع نظام التشغيل و مجموعة من التقنيات التي قد يسفيد منها مبرمجوا دلفي.

مماسك ويندوز

من بين أنواع البيانات الخاصة بويندوز في دلفي، تعد المماسك handles أكثر المجموعات أهمية. اسم نوع البيانات هذا Thandle، والنوع معرّف في وحدة Windows كالتالي:

type
  THandle = LongWord;

أنواع بيانات Handle تنفّذ كأرقام، ولكنها لا تستعمل كذلك. في ويندوز، الممسك handle هو إشارة إلى بنية بيانات داخلية للنظام. مثلا، عندما تتعامل مع نافذة Window (أو نموذج Form في دلفي)، يعطيك النظام ممسكا للنافذة. النظام يخبرك بأن النافذة التي تتعامل معها هي نافذة رقم 142 مثلا. بدءا من هذه النقطة، يمكن لتطبيقك أن يطلب من النظام أن يشتغل على النافذة رقم 142 لتحريكها، لتغيير حجمها، لتحويلها إلى أيقونة، و هكذا. العديد من وظائف API في ويندوز، في الواقع، لديها ممسك كأول محدد. هذا لا ينطبق فقط على الوظائف التي تتناول النوافذ؛ بل هناك وظائف API أخرى محددها الأول ممسك رسومي GDI handle، ممسك لائحة أوامر menu handle، ممسك صورة bitmap handle، ممسك لتمثّل instance handle.

بتعبير آخر، الممسك هو توليف داخلي يمكنك استعماله لتشير به إلى عنصر معين يتم مناولته من قبل النظام، يتضمن ذلك نافذة window، صورة bitmap، أيقونة icon، كتلة ذاكرة memory block، مشير cursor، خط font، لائحة أوامر menu، و هكذا. في دلفي، نادرا ما تحتاج إلى استعمال المماسك مباشرة، حيث أنها مخفية داخل النماذج forms، و الصور، و داخل كائنات دلفي الأخرى. ستكون مفيدة عندما ترغب في استدعاء وظيفة API في ويندوز ليست مدعومة من قبل دلفي.

لتكملة هذا الوصف، فيما يلي مثال بسيط يستعرض مماسك ويندوز. برنامج WHandle لديه نموذج form بسيط، يحتوي فقط على زرّ. في التوليف، قُمت بالاستجابة لحدث OnCreate الخاص بالنموذج، و حدث OnClick الخاص بالزرّ، كما هو واضح في التوصيف النصّي التالي للنموذج الرئيسي:

object FormWHandle: TFormWHandle
  Caption = 'Window Handle'
  OnCreate = FormCreate
  object BtnCallAPI: TButton
    Caption = 'Call API'
    OnClick = BtnCallAPIClick
  end
end

حالما يتم خلق النموذج، يقوم البرنامج باستخلاص ممسك النافذة الخاصة بهذا النموذج، من خلال الحصول على سمة Handle الخاصة بالنموذج نفسه. نستدعي IntToStr لتحويل القيمة الرقمية للممسك إلى جملة، ثم نلحقها بعنوان النموذج، كما يمكنك أن تراه في الشكل 9.1:

procedure TFormWHandle.FormCreate(Sender: TObject);
begin
  Caption := Caption + ' ' + IntToStr (Handle);
end;

لأن FormCreate هي مسار لطبقة النموذج، يمكنها الولوج لسمات و مسارات أخرى تابعة لنفس الطبقة class مباشرة. لهذا، في هذه الإجرائية يمكننا ببساطة الإشارة إلى سمات Caption و Handle الخاصة بالنموذج مباشرة.

الشكل 9.1: مثال WHandle يعرض ممسك نافذة النموذج. كل مرّة تقوم بتشغيل هذا البرنامج ستتحصّل على قيمة مختلفة.

إذا قمت بتشغيل البرنامج عدّة مرّات تحصل بصفة عامة على قيما مختلفة للممسك. هذه القيمة، في الواقع، يتم تقريرها من قبل ويندوز و يعاد ارسالها إلى التطبيق. (الممسكات لا يتم تقريرها من قبل البرنامج، و لاتملك قيمة محددة مسبقا؛ الممسكات يتم تقريرها من قبل النظام، و التي تقوم بتوليد قيم جديدة في كل مرّة تقوم بتشغيل البرنامج.)

عندما يضغط المستخدم على الزرّ، يقوم البرنامج ببساطة باستدعاء وظيفة API و هي SetWindowText، التي تغيّر نص أو عنوان النافذة التي تم تمريرها كمحدد أول. لنكون أكثر دقّة، المحدد الأول لوظيفة API هو ممسك النافذة التي نريد تعديلها:

procedure TFormWHandle.BtnCallAPIClick(Sender: TObject);
begin
  SetWindowText (Handle, 'Hi');
end;

لهذا التوليف نفس تأثير مناول الحدث السابق، الذي قام بتغيير نصّ النافذة بواسطة إعطاء قيمة جديدة لسمة Caption بالنموذج. في هذه الحالة فإن إستدعاء وظيفة API ليس له معنى، لأنه توجد تقنية لدلفي مشابهة. بعض وظائف API، على أي حال، ليس لها ما يوافقها في دلفي، كما سنرى في أمثلة متقدمة أكثر لاحقا في الكتاب.

التصريحات الخارجية

عنصر مهم آخر في البرمجة لويندوز و هو ما تمثله التصريحات الخارجية external declarations. كانت في الأصل تستخدم لربط توليف باسكال بوظائف خارجية كُتبت بلغة التجميع assembly، التصريح الخارجي يستخدم عند البرمجة لويندوز لإستدعاء وظيفة من مكتبة DLL (مكتبة الربط الحيوية). في دلفي، يوجد العديد من هذه التصريحات في وحدة Windows unit.

// forward declaration
// تعريف مسبق
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall; // external declaration (instead of actual code)
// تعريف خارجي (بدلا من التوليف الفعلي)
function LineTo; external 'gdi32.dll' name 'LineTo';

هذا التصريح يعني أن توليف وظيفة LineTo مخزّنة في المكتبة الحيوية GDI32.Dll (أحد أهم مكتبات نظام ويندوز) بنفس الإسم الذي نستخدمه في التوليف. داخل التصريح الخارجي، في الواقع، يمكننا توضيح أن وظيفتنا تشير إلى و ظيفة DLL و التي أصلا لها إسما مختلف.

أنت ناردا ما تحتاج لكتابة تصريح مثل الذي سبق عرضه، ما دامت التصريحات هي بالفعل متضمنة في وحدة Windows و في عدد من وحدات النظام في دلفي. السبب الوحيد الذي قد يدعوك لكتابة توليف لتصريح خارجي هو لإستدعاء وظائف من مكتبات DLL خاصة، أو لإستدعاء وظائف ويندوز غير موثّقة.

ملاحظة: في نسخة دلفي 16-بت، التصريح الخارجي يستعمل اسم المكتبة دون الامتداد extension، و كانت تُتبع بتوجيه name (كما في التوليف أعلاه) أو بتوجيه index كبديل، متبوع بترتيب رقم الوظيفة داخل DLL. التغيير قد عكس تبديل النظام لطريقة الولوج للمكتبات: بالرغم من أن WIN32 لا زالت تسمح بالوصول إلى وظائف DLL بواسطة الرقم، إلا أن ميكروسفت أعلنت أن هذه الطريقة لن تدعم مستقبلا. لاحظ أيضا وحدة Windows حلّت محلّ وحدات WinProcs و WinTypes التي في دلفي نسخة 16-بت.

وظيفة نداء عكسي لويندوز

شاهدنا في الفصل 6 أن أوبجكت باسكال تدعم الأنواع الإجرائية procedural types. الإستعمال الشائع للأنواع الإجرائية هي لتوفير وظائف نداء عكسي callback لوظائف API ويندوز.

قبل كلّ شيء، ما هي وظيفة نداء عكسي؟ الفكرة هي أن يعض وظائف API تنجز عمل ما على عدد من العناصر الداخلية في النظام، كما كل النوافذ من نفس النوع. مثل هذه الوظيفة، أيضا تسمّى وظيفة سردية أو تواترية enumerated، تتطلب كمحدد الفعل الذي ستقوم بانجازه على كل عنصر من العناصر، و الذي يمرر كوظيفة أو إجراء متوافق مع النوع الإجرائي الذي تم اعطاؤه. تستعمل ويندوز وظائف النداء العكسي في ظروف أخرى، لكننا سنحدد دراستنا في هذه الحالة البسيطة.

الآن راقب وظيفة API المسماة EnumWindows، و التي تملك التوصيف التالي (منسوخة من ملف مساعدة WIN32):

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,  // address of callback function
  LPARAM lParam // application-defined value
  );

بالطبع، هذا تعريف بلغة س. يمكننا أن أن ننظر داخل ملف وحدة Windows لرؤية التعريف الموافق له بلغة باسكال:

function EnumWindows (
  lpEnumFunc: TFNWndEnumProc;
  lParam: LPARAM): BOOL; stdcall;

باستشارة ملف المساعدة، نجد أن الوظيفة الممررة كمحدد يجب أن تكون بالنوع التالي (مرة أخرى بلغة س):

BOOL CALLBACK EnumWindowsProc (
  HWND hwnd, // handle of parent window
  LPARAM lParam // application-defined value
  );

هذا يوافق تعريف دلفي التالي للنوع الإجرائي:

type
  EnumWindowsProc = function (Hwnd: THandle;
    Param: Pointer): Boolean; stdcall;

المحدد الأول هو الممسك handle لكل نافذة رئيسية عليها الدور، بينما الثاني هي القيمة التي نمررها عندما ننادي وظيفة EnumWindows. في الواقع في باسكال نوع TFNWndEnumProc ليس معرفا بطريقة مناسبة؛ هو ببساطة مؤشر poiter. هذا يعني أنه علينا توفير وظيفة بالمحددات المناسبة ثم نستخدمها كمؤشر، بأخذ عنوان الوظيفة بدلا من استدعائها. لسوء الحظ، هذا يعني أيضا أن المجمّع لن يقدم أي عون في حالة وجود خطأ في نوع أحد المحددات.

تتطلب ويندوز من المبرمجين اتباع طرقة استدعاء stdcall في كل مرة نقوم فيها باستدعاء وظيفة API أو نمرر فيها وظيفة نداء عكسي للنظام. أما دلفي، افتراضيا ، تستخدم طريقة استدعاء مختلفة و أكثر كفاءة، يشار إليها بالكلمة المفتاحية register.

ها هنا تعريفا مناسبا لوظيفة متوافقة، تقوم بقراءة عنوان النافذة في جملة، ثم تضيفها إلى مربّع قائمة لنموذجم عين:

function GetTitle (Hwnd: THandle; Param: Pointer): Boolean; stdcall;
var
  Text: string;
begin
  SetLength (Text, 100);
  GetWindowText (Hwnd, PChar (Text), 100);
  FormCallBack.ListBox1.Items.Add (
    IntToStr (Hwnd) + ': ' + Text);
  Result := True;
end;

النموذج form  لديه مربّع قائمة تغطّي تقريبا كامل المنطقة، رفق لوحة panel صغيرة في الأعلى تستضيف زرّا، عندما يُضغط الزرّ، يتم استدعاء وظيفة EnumWindows، و يتم تمرير وظيفة GetTitle كمحدد لها:

procedure TFormCallback.BtnTitlesClick(Sender: TObject);
var
  EWProc: EnumWindowsProc;
begin
  ListBox1.Items.Clear;
  EWProc := GetTitle;
  EnumWindows (@EWProc, 0);
end;

كان بامكاني استدعاء الوظيفة دون تخزين القيمة أولا في متغير مؤقت نوع إجرائي، لكنّي أردت جعل ما يجري في هذا المثال واضحا. تأثير هذا البرنامج مثير للإهتمام بالفعل، كما ترى في الشكل 9.2. مثال Callback يعرض قائمة بكل النوافذ الرئيسية الشغّالة في النظام. معظمها نوافذ مخفية لن تراها عادة (و الكثير منها ليس لها عنوان في الواقع).

شكل 9.2: ناتج مثال Callback، يسرد النوافذ الرئيسية الحالية (المرئية و المخفية).

برنامج ويندوز محدود

لإكمال تغطية موضوع البرمجة لويندوز و لغة باسكال، أريد أن أعرض لك تطبيقا بسيطا لكنه كاملا و بُني بدون استعمال مكتبة VCL. البرنامج ببساطة يأخد معطيات سطر الأمر command-line (مخّزنة من قبل النظام في المتغير العام cmdline) ثم يقوم باستخلاص المعلومات منها بواسطة وظائف باسكال ParamCount و ParamStr. أولى هذه الوظائف تسترجع عدد المعطيات؛ الثاني يرجع المعطى أو المحدد حسب موقعه.

بالرغم من أن المستخدمين نادرا ما يحددون معطيات سطر الأمر في بيئة واجهة رسومية، إلا أن معطيات سطر الأمر في ويندوز تعد مهمة للنظام. مثلا، حالما تقوم بالربط بين امتداد اسم ملف و تطبيق ما، بعدها يمكنك ببساطة تشغيل البرنامج من خلال اختيار الملف المرتط به. عمليا، عندما تقوم بلمسة مزدوجة على الملف، تبدأ ويندوز بتشغيل البرنامج المرتبط وتحيل له الملف المختار كمحدد لأمر سطري.

فيما يلي توليف مصدري كامل للمشروع (ملف DPR، وليس ملف PAS):

program Strparam;

uses
  Windows;

begin
  // show the full string
  MessageBox (0, cmdLine, 
    'StrParam Command Line', MB_OK);

  // show the first parameter
  if ParamCount > 0 then
    MessageBox (0, PChar (ParamStr (1)), 
      '1st StrParam Parameter', MB_OK)
  else
    MessageBox (0, PChar ('No parameters'), 
      '1st StrParam Parameter', MB_OK);
end.

التوليف يستخدم وظيفة API و هي MessageBox ، ببساطة لتجنب أخذ كامل مكتبة VCL داخل المشروع. برنامج ويندوز صاف كالذي في الأعلى له ميزة ، في الواقع، و هي أن بصمته صغيرة جدا في الذاكرة: حجم الملف التنفيذي للبرنامج حوالي 16 ك.ب.

لتقديم معطيات سطر الأمر لهذا البرنامج، يمكنك استخدام أوامر دلفي Run ثم Parameters. طريقة أخرى و هي أن تفتح مستكشف ويندوز Windows Explorer، تذهب للدليل الذي يحتوي على الملف التنفيذي للبرنامج، ثم تقوم بجرّ drag الملف الذي المراد تشغيله و اسقاطه فوق الملف التنفيذي. سيقوم مستكشف ويندوز بابتداء البرنامج مستعملا اسم الملف الذي أُسقط كمعطيات لسطر الأمر. الشكل 9.3 يعرض المستكشف و المخرجات المتعلقة به.

الشكل 9.3: يمكنك تقديم محدد سطر الأمر لمثال StrParm بجرّ ملف و اسقاطه فوق الملف التنفيذي في مستكشف ويندوز.

ملخّص

في هذا الفصل شاهدنا تقديما برؤيا منخفضة لبرمجة ويندوز، مناقشين المماسك و برنامج ويندوز بسيط جدا. لأغراض برمجة ويندوز العادية، ستقوم عموما باستخدام دعم التطوير المرئي المقدمة من قبل دلفي و المعتمدة على مكتبة VCL. لكن هذا الأمر خارج نطاق هذا الكتاب، الذي يركّز على لغة باسكال.

الفصل التالي سيغطي المتباينات variants، اضافة غريبة جدا لنظام أنواع بيانات باسكال، و التي تم ادخالها لتقديم دعم كامل لتقنية OLE.

الفصل التالي: المتباينات

حقوق النسخ محفوظة لماركو كانتو؛ وينتش ايطاليا © Copyright Marco Cantù, Wintech Italia Srl 1995-2000
حقوق الترجمة: خالد الشقروني ، 2000