الفصل 6
الإجراءات والوظائف
فكرة أخرى مهمة ركّزت عليها باسكال هي مفهوم الإجرائيات routine. مبدئيا هي سلسلة من التعليمات تحت اسم خاص غير مكرّر، و التي يمكن تنشيطها في كل مرّة باستخدام اسمها. بهذه الطريقة تتجنب معاودة كتابة التعليمات مرّة بعد أخرى، فتتحصّل على نسخة واحدة من التوليف يمكنك بسهولة تعديله لصالح كامل البرنامج. من وجهة النظر هذه، يمكنك أن تنظر إلى الإجرائيات كآلية أساسية لتغليف التوليف. سأعود إلى هذا الموضوع لاحقا مع مثال بعد أن أقدم أولا الصيغة النحوية syntax لإجرائيات باسكال.
إجراءات و وظائف باسكال
في باسكال، الإجرائية routine يمكن افتراضها بشكلين: إجراء procedure و وظيفة function. نظريا، الإجراء هو عملية تقوم أنت كمبرمج بسؤال الحاسب كي ينجزها، الوظيفة هي حسبة تردّ قيمة. هذا الفرق يؤكّده حقيقة أن الوظيفة لها نتيجة result، قيمة مسترجعة، بينما الإجراء ليس كذلك. كلا النوعين من الاجرائيات يكمن أن يكون لهما عدّة محدّدات parameters، من أنواع بيانات تعطى لها.
عمليا، الفرق عموما بين الوظائف و الإجراءات محدود جدا: يمكنك استدعاء وظيفة لإنجاز عمل ما ثم تتخطّى النتيجة (التي قد تكون رمز خطأ اختياري أو ما شابه) كما بإمكانك إستدعاء إجراء يمرّر نتيجته ضمن محدّداته (سيأتي الحديث أكثر عن المحدّدات بالإشارة reference parameters لاحقا في هذا الفصل).
ها هنا تعريفات لإجراء و نسختين من نفس الوظيفة، باستخدام صيغ مختلفة قليلا:
procedure Hello;
begin
ShowMessage ('Hello world!');
end;
function Double (Value: Integer) : Integer;
begin
Double := Value * 2;
end;
// or, as an alternative
function Double2 (Value: Integer) : Integer;
begin
Result := Value * 2;
end;
استخدام result بدلا من اسم الوظيفة من أجل تخصيص قيمة الوظيفة المرتجعة أصبحت شائعة جدا، و تنحى لجعل التوليف أكثر مقروئية، حسب رأيي.
حالما يتم تعريف هذه الإجرائات، يمكنك إستدعائهم مرة أو أكثر. تستدعي الإجراء لجعله ينجز مهمته، و تستدعي الوظيفة لحساب القيمة:
procedure TForm1.Button1Click (Sender: TObject);
begin
Hello;
end;
procedure TForm1.Button2Click (Sender: TObject);
var
X, Y: Integer;
begin
X := Double (StrToInt (Edit1.Text));
Y := Double (X);
ShowMessage (IntToStr (Y));
end;
ملاحظة: في الوقت الراهن لا تهتم كثيرا بصيغة الإجراءات أعلاه، و التي هي حقيقة مسارات methods. ببساطة قم بوضع زرّين على نافذة دلفي، لمسة مزدوجة فوقهما وقت التصميم، و ستقوم بيئة دلفي IDE بتوليد ما يناسب من توليف داعم: الآن و ببساطة عليك ملء الأسطر بين begin و end. لتجميع التوليف أعلاه تحتاج أيضا إلى إضافة خانة كتابة Edit للنافذة.
الآن يمكننا العودة إلى مفهوم تغليف التوليف الذي أشرت إليه سابقا. عندما تستدعي وظيفة Double، أنت لا تحتاج إلى معرفة الخوارزمية التي أُستخدمت لتنفيذها. إذا وجدت لاحقا طريقة أفضل لمضاعفة الأرقام، يمكنك بسهولة تغيير توليف الوظيفة، لكن التوليف الذي قام بالإستدعاء سيبقى ثابتا (بالرغم من أن التنفيذ سيكون أسرع!). نفس المفهوم يمكن تطبيقه على وظيفة Hello: يمكننا تعديل مخرجات البرنامج بتغيير التوليف داخل هذه الوظيفة، و بطريقة آلية سيتغير التأثير الذي يحدثه مسار Button2Click بدون أن نغيّر فيه:
procedure Hello;
begin
MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;
فائدة: عندما تستدعي إحدى وظائف أو إجراءات دلفي، أو أي مسار لمكوّن VCL، يجب أن تتذكر عدد و نوع المحدّدات. محرّر دلفي يمكن أن يساعد باقتراحه لقائمة المحدّدات الخاصة بالوظيفة أو الإجراء بواسطة تلميحة محادية حالما تقوم بطباعة اسم الاجرائية و تفتح قوسا. هذه الخاصية تدعى Code Parameters و هي جزءا من تقنية Code Insight.
المحدّدات بالإشارة
تسمح لك إجرائيات باسكال بتمرير المحدّدات parameter بقيمتها by value و بالإشارة by reference. افتراضيا المحدّدات يتم تمريرها بالقيمة: يتم نسخ القيمة في الصفّ stack و تقوم الإجرائيات باستخدام و معالجة النسخة، و ليست القيمة الأصلية.
تمرير المحدّد بالإشارة يعني أن قيمته لايتم نسخها في الصفّ لدي الإجرائية (تجنبّ النسخ دائما يعني أن تنفيذ البرنامج يكون أسرع). بدلا من ذلك، البرنامج يشير إلى القيمة الأصلية، يحدث هذا أيضا في توليف الإجرائية. هذا يسمح للإجراء أو الوظيفة بأن تغيّر في قيمة المحدّد. المحدّد المُرّر بالإشارة يُعبّر عنه بالمصطلح var .
هذا الأسلوب موجود في معظم لغات البرمجة. هو ليس موجودا في س، و لكن تم ادخاله في س++، حيث تقوم باستعمال علامة & (تمرير بالإشارة). في فيجوال بيسك كل محّدد لا يكون بصفة ByVal يتم تمريره بالإشارة.
هنا مثال لتمرير محدّد بالإشارة باستخدام مصطلح var :
procedure DoubleTheValue (var Value: Integer);
begin
Value := Value * 2;
end;
في هذه الحالة، المحدّد تم استخدامه لغرضين، لتمرير قيمة للإجرائية و لإسترجاع القيمة الجديدة للتوليف الذي قام بالاستدعاء. عندما تكتب:
var
X: Integer;
begin
X := 10;
DoubleTheValue (X);
فإن قيمة المتغير X تغدو 20، لأن الإجرائية تتعامل مع إشارة لموقع الذاكرة الأصلي ل X ، مؤثّرة في قيمتها الأولى.
تمرير المحدّدات بالإشارة له مايبرّره فيما يتعلّق بالأنواع التراتبية ordinal، و الجُمل strings بالطريقة التقليدية، و بالتسجيلات records الضخمة. في الواقع إن كائنات objects دلفي دائما يتم تمريرها بالقيمة، لأنها هي نفسها إشارة. لهذا السبب فإن تمرير الكائنات بإشارتها لامعنى له تقريبا (ما عدا بعض الحالات الخاصة جدا)، لأنها كما لوكانت "تمرير إشارة بالإشارة".
جُمل strings دلفي الضخمة لها سلوك مختلف بعض الشيء: هي تتصرّف و كأنها إشارة، لكنك إذا قمت بتغيير واحدة من متغيرات الجمل التي تشير إلى نفس الجملة في الذاكرة، يتم نسخها قبل تحديثها. الجُمل الطويلة التي تمرر كمحدد بقيمة تتصرف و كأنها إشارة فقط من حيث استخدام الذاكرة و سرعة الشغيل. لكن إذا قمت بتعديل قيمة الجملة ، فإن القيمة الأصلية لاتتأثّر، بالمقابل، إذا مرّرت الجُملة الطويلة بالإشارة، يمكنك تغيير القيمة الأصلية.
أدخلت دلفي 3 نوعا جديدا من المحدّدات، وهي out. محدّد out ليس لديه قيمة ابتدائية و يستخدم فقط لترجيع قيمة. هذه المحدّدات يجب استخدامها فقط لإجرائيات و وظائف COM ؛ عموما، من الأفضل التشبّت بمحدّدات var الأكثر فعالية. محددات out تتصّرف مثل محدّدات var بإستثناء عندما لا يوجد لديها قيمة ابتدائية.
محدّدات الثوابت
كبديل للمحددات بالإشارة، يمكنك استعمال محدد const. بما أنّه لايمكنك تخصيص قيمة لمحدد ثابت داخل الإجرائية، يمكن للمجمّع تحسين كفاءة تمرير المحدّد. المجمّع يمكن أن يختار أسلوبا شبيها بالمحددات بالإشارة (أو الإشارة لثابت const reference حسب مصطلحات س++)، لكن التصرّف سيبقى شبيها بالمحددات بالقيمة، لأن القيمة الأصلية لن تتأثر بالإجرائيات.
في الواقع، إذا حاولت تجميع التوليف (السخيف) التالي، ستقوم دلفي باصدار خطأ:
function DoubleTheValue (const Value: Integer): Integer;
begin
Value := Value * 2; // compiler error
Result := Value;
end;
محدّدات المصفوفة المفتوحة
عكس لغة س، وظيفة أو إجراء دلفي لديهما دائما عددا ثابتا من المحدّدات, إلا أنه توجد طريقة لتمرير عددا غير ثابت من المحدّدات إلى الإجرائية بإستخدام المصفوفة المفتوحة open array.
التعريف الأساسي لمحدد مصفوفة مفتوحة open array parameter هو مصفوفة مفتوحة ذات نوع. هذا يعني انك تشير إلى نوع المحدّد لكنك لاتعرف كم عنصر من هذا النوع سيكون لدى المصفوفة. هنا مثال لمثل هذا التعريف:
function Sum (const A: array of Integer): Integer;
var
I: Integer;
begin
Result := 0;
for I := Low(A) to High(A) do
Result := Result + A[I];
end;
بإستخدام High(A) يمكننا الحصول على حجم المصفوفة، لاحظ أيضا استخدام قيمة الترجيع في الوظيفة، Result، لتخزين قيم مؤقتة. يمكنك استدعاء هذه الوظيفة بأن تمرر إليها مصفوفة من التعبيرات ذات نوع صحيح Integer.
X := Sum ([10, Y, 27*I]);
إذا كان لديك مصفوفة من أي حجم ذات نوع صحيح، تستطيع تمريرها مباشرة لإجرائية تتطلب محدد بمصفوفة مفتوحة، أو بدلا من ذلك، يمكنك استدعاء وظيفة Slice لتمرير جزء فقط من المصفوفة (كما هو مشار اليه في ثاني محدد في الوظيفة). ها هنا مثال، حيث مصفوفة كاملة تمّ تمريرها كمحدّد:
var
List: array [1..10] of Integer;
X, I: Integer;
begin
// initialize the array
for I := Low (List) to High (List) do
List [I] := I * 2;
// call
X := Sum (List);
إذا أردت تمرير فقط جزء من المصفوفة إلى وظيفة Sum، ببساطة قم باستدعائها بالطريقة التالية:
X := Sum (Slice (List, 5));
تستطيع أن تجد كل أجزاء التوليف الذي تم عرضه في هذا القسم في مثال OpenArr (أنظر الشكل 6.1، لاحقا، بالنسبة للنموذج).
الشكل 6.1: مثال OpenArr عندما يتم الضغط على زرّ Partial Slice
المصفوفات المفتوحة النوعية في دلفي 4 متوافقة تماما مع المصفوفات الحيّة dynamic (تم تقديمها في دلفي 4 و مغطّاة في الفصل 8). المصفوفات الحيوية تستخدم نفس الصيغة في المصفوفات المفتوحة، مع اختلاف انه يمكنك استخدام التركيب array of Integer لتعريف متغيّر، و ليس فقط لتمرير محدّد.
محدّدات مصفوفة مفتوحة نوع متباين
بجانب هذه المصفوفات المفتوحة النوعية، تسمح لك دلفي بتحديد مصفوفات مفتوحة نوع متباين type-variant أو بلا نوع. هذا النوع الخاص من المصفوفات لديه عدد غير محدود من القيم، و التي يمكن الاستفادة منها لتمرير المحدّدات.
تقنيا، بنية مصفوفة الثوابت تسمح لك بتمرير مصفوفة بعدد غير محدود من العناصر من أنواع مختلفة إلى إجرائية دفعة واحدة. مثال ذلك، ها هنا تعريف لوظيفة Format (سنرى كيف نستخدم هذه الوظيفة في الفصل 7، عند الحديث عن الجمل):
function Format (const Format: string;
const Args: array of const): string;
المحدّد الثاني هو مصفوفة مفتوحة، تستقبل عددا غير محدود من القيم. في الواقع، يمكنك استدعاء هذه الوظيفة بالطرق التالية:
N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);
لاحظ أنه بإمكانك تمرير المحدّد كقيمة ثابت، أو قيمة متغير، أو كتعبير. تعريف وظيفة من هذا النوع أمر سهل، لكن كيف تقوم بتوليفه؟ كيف تتعرف على نوع المحدّدات؟ ان قيم محددات مصفوفة مفتوحة نوع متباين هي متوافقة مع عناصر نوع TVarRec.
ملاحظة: لا تخلط بين تسجيلة TVarRec و تسجيلة TVarData المستخدمة من قبل نوع Variant نفسه. هاتان البنيتان تخدمان أغراضا مختلفة و ليستا متوافقتين. بالرغم من أن قائمة الأنواع المحتملة مختلفة، لأن TVarRec يمكن ان تضم أنواع بيانات دلفي، بينما TVarData يمكن أن تحوي أنواع بيانات OLE.
تسجيلة TVarRec لها البُنية التالية:
type
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
end;
كل تسجيلة محتملة لديها حقل نوع VTipe ، بالرغم انه ليس سهلا رؤيته من المرة الأولى لأن تعريفه يتم مرة واحدة فقط.
بواسطة هذه المعلومات يمكننا فعلا كتابة وظيفة قادرة على التعامل مع أنواع بيانات مختفلة. في مثال وظيفة SumAll ، أريد أن أكون قادرا على جمع قيم من أنواع مختلفة، تحويل الجمل إلى أعداد صحيحة، الحروف إلى ما يقابلها من قيمة ترتيبية، و إضافة 1 للقيم البولية الموجبة. التوليف يعتمد على تعليمة case، و يعدّ سهلا، بالرغم من أنه علينا التعامل مع المؤشرات pointers أكثر من مرّة:
function SumAll (const Args: array of const): Extended;
var
I: Integer;
begin
Result := 0;
for I := Low(Args) to High (Args) do
case Args [I].VType of
vtInteger: Result :=
Result + Args [I].VInteger;
vtBoolean:
if Args [I].VBoolean then
Result := Result + 1;
vtChar:
Result := Result + Ord (Args [I].VChar);
vtExtended:
Result := Result + Args [I].VExtended^;
vtString, vtAnsiString:
Result := Result + StrToIntDef ((Args [I].VString^), 0);
vtWideChar:
Result := Result + Ord (Args [I].VWideChar);
vtCurrency:
Result := Result + Args [I].VCurrency^;
end; // case
end;
لقد اضفت هذا التوليف إلى مثال OpenArr، الذي يستدعي وظيفة SumAll عند يتم الضغط على زرّ معيّن.
procedure TForm1.Button4Click(Sender: TObject);
var
X: Extended;
Y: Integer;
begin
Y := 10;
X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);
ShowMessage (Format (
'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) = > %n', [X]));
end;
يمكن رؤية نتاج هذا الاستدعاء، و النموذج بمثال OpenArr، في الشكل 6.2.
الشكل 6.2: النموذح في مثال OpenArr، مع مربع رسالة تُعرض عند الضغط على زرّ Untyped.
طرق الإستدعاء في دلفي
أدخلت نسخة 32-بت في دلفي مفهوما جديدا لتمرير المحدّدات، تعرف باسم fastcall: فحيثما أمكن، و حتى لثلاث محدّدات يمكن تمريرها في مسجِّلات registers المعالج، جاعلة من استدعاء الوظيفة أسرع بكثير. طريقة الاستدعاء السريع fast calling convention (تستخدم افتراضيا في دلفي 3) يشار لها بمصطلح register.
المشكلة أنها الطريقة الإفتراضية، و الوظائف التي تستخدمها ليست متوافقة مع ويندوز: وظائف Win32 يجب تعريفها باستخدام طريقة استدعاء sdtcall، و هي مزيج من الطريقة الأصلية للإستداعاء في باسكال لوظائف Win16 و طريقة استدعاء cdecl في لغة س.
لا يوجد عموما سبب يمنع استعمال طريقة الإستدعاء السريع، إلا إذا كنت تقوم باستدعاءات ويندوز خارجية external أو تقوم بتحديد وظائف callback. سوف نرى مثالا عن استخدام طريقة stdcall قبل نهاية هذا الفصل, يمكنك أن تجد ملخّصا لطرق استدعاءات دلفي تحت موضوع Calling conventions في ملف مساعدة دلفي.
ماهو المسار؟
إذا سبق لك بالفعل العمل بدلفي أو قرأت أدّلة التشغيل، فمن المحتمل انك سمعت بمصطلح method مسار. المسار هو نوع خاص من الوظائف أو الإجرائيات ذات علاقة بنوع بيانات، الطبقة class. في دلفي، كل مرة نتناول حدثا، نحتاج لتعريف مسار، أو بصفة عامة إجراء. على أية حال، مصطلح مسار يستخدم للإشارة إلى الوظائف و الإجراءات ذات العلاقة بالطبقة class.
سبق لنا أن رأينا بالفعل عددا من المسارات في الأمثلة الواردة في هذا الفصل و في ما سبقه. في ما يلي مسار فارغ أضيف آليا بواسطة دلفي للتوليف المصدري لنموذج:
procedure TForm1.Button1Click(Sender: TObject);
begin
{here goes your code}
end;
التعريف المسبق
عندما تحتاج إلى استخدام معرّف identifier (من أي نوع)، يجب أن يكون المجمّع قد رأى بالفعل تحديدا ما ليعلم إلى ماذا يشير هذا المعرّف. لهذا السبب، فأنت عادة ما تقدّم تعريفا كاملا قبل استخدام أية إجرائية. على أية حال، توجد حالات لايمكنك فيها ذلك. إذا فرضنا أن الإجراء A يستدعي الإجراء B، و الإجراء B يستدعي الإجراء A، عندما تبدأ بكتابة التوليف، فستحتاج إلى مناداة إجرائية لا يزال المجمّع لم ير تعريفها.
إذا أردت تعريف وجود إجراء أو وظيفة بإسم معيّن و محدّدات معطاة، من غير تقديم توليفها الفعلي، يمكنك كتابة الإجراء أو الوظيفة متبوعة بالكلمة المفتاحية forward:
procedure Hello; forward;
لاحقا، يجب أن يقدم التوليف تعريفا كاملا للإجراء، لكن هذا يمكن استدعاؤه حتى قبل أن يتم تعريفه بالكامل. فيما يلي مثال ساذج، فقط لإعطائك فكرة:
procedure DoubleHello; forward;
procedure Hello;
begin
if MessageDlg ('Do you want a double message?',
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
DoubleHello
else
ShowMessage ('Hello');
end;
procedure DoubleHello;
begin
Hello;
Hello;
end;
هذه الطريقة تسمح لك بكتابة تواتر recursion متبادل: DoubleHello تنادي Hello، لكن Hello ربما تنادي DoubleHello، أيضا. طبعا لابد من وجود شرط لإيقاف التواتر، لتجنب فوران التكدّس stack overflow.
بالرغم من أن تعريف الإجراء المسبق forward procedure ليس شائعا في دلفي، توجد حالة مشابهة و التي تتكر أكثر. عندما تقوم بتعريف إجرائية أو وظيفة في جزء الواجهة interface في الوحدة unit (المزيد عن الوحدات في الفصل القادم)، يتم إعتباره تعريفا مسبقا، حتى إذا كان مصطلح forward غير ظاهر. فعليا لايمكنك أن تكتب جسم الاجرائية في جزء الواجهة. في نفس الوقت، يجب أن تقوم بتقديم التنفيذ الفعلي لكل إجرائية قمت بتعريفها، وذلك في نفس الوحدة.
نفس الشيء ينطبق على تعريف المسار methos داخل نوع طبقة class و الذي يتم توليده آليا بواسطة دلفي (كما يحدث عند اضافة حدث لنموذج أو أحد مكوناته). مناولات الحدث المعرّفة داخل طبقة TForm هي تعريفات مسبقة: حيث سيتم تقديم التوليف في جزء التنفيذ implementaion من الوحدة. فيما يلي مقطع من توليف مصدري لمثال سابق، مع تعريف لمسار Button1Click :
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
الأنواع الإجرائية
ميزة فريدة أخرى في اوبجكت باسكال وهي وجود الأنواع الإجرائية procedural types. في الواقع يعّد هذا موضوعا برمجيا متقدما، و قلّة من مبرمجي دلفي سوف يستعملونه باستمرار. عموما، ما دمنا سوف نناقش الموضوعات ذات العلاقة في الفصول القادمة (خاصة، مؤشرات المسار، التقنية المستخدمة بكثافة في دلفي)، فإن الأمر يستحق أن نلقي بنظرة سريعة على هذا الموضوع هنا. إذا كنت مبرمجا مبتدئا، يمكنك تخطّي هذا القسم مؤقتا، و أن تعود لاحقا عندما تشعر بأنك مستعدّ لذلك.
في باسكال، يوجد مفهوم النوع الإجرائي procedural type (و الذي يشبه مفهوم مؤشر الوظيفة function pointer في لغة س). تعريف النوع الإجرائي يشير إلى قائمة من المحدّدات و نوع الترجيع في حالة الوظيفة. مثلا، يمكنك تعريف نوع إجراء مع محدد برقم صحيح يتم تمريره بالإشارة مثل:
type
IntProc = procedure (var Num: Integer);
النوع الإجرائي هذا متوافق مع أي إجرائية تملك تماما نفس المحددات (أو نفس توقيع الوظيفة function signature، بتعبير لغة س). هنا مثال لإجرائية متوافقة.
procedure DoubleTheValue (var Value: Integer);
begin
Value := Value * 2;
end;
ملاحظة: في نسخة 16-بت من دلفي، يجب أن يتم تعريف الإجرائيات بإستخدام توجيه far من أجل إستعمالها كقيمة فعلية للنوع الإجرائي.
الأنواع الإجرائية يمكن استخدامها لغرضين مختلفين: يمكنك تعريف متغيرات من نوع إجرائي أو تمرير نوع إجرائي - مؤشّر وظيفة- كمحددات إلى إجرائية أخرى. بوجود النوع السابق و تعريفات الإجراء، يمكنك كتابة هذا التوليف:
var
IP: IntProc;
X: Integer;
begin
IP := DoubleTheValue;
X := 5;
IP (X);
end;
هذا التوليف له نفس التأثير الذي للنسخة الأقصر التالية:
var
X: Integer;
begin
X := 5;
DoubleTheValue (X);
end;
واضح أن النسخة الأولى أكثر تعقيدا، لذا لماذا علينا استعمالها؟ في بعض الحالات، القدرة على تقرير أية وظيفة يمكن استدعاؤها و أن يتم أستدعاؤها فعليا لاحقا، هذه الامكانية قد تكون مفيدة. يمكن بناء مثال متشعب يعرض هذا التوجه. عموما، أنا أفضّل أن أجعلك تستكشف مثالا بسيط بما يكفي، اسمه ProcType. هذا المثال أكثر تشعبا من كل ما سبق أن رأيناه حتى الآن، لجعل الأمور أكثر واقعية.
ببساطة قم بانشاء مشروع جديد و قم بوضع زرّي خيار radio buttons، زرّ ضغط، و ملصقين lables على النافذة. كما هو موضّح في الشكل 6.3. هذا المثال مبني على إجرائين. الإجراء الأول يتم استخدامه لمضاعفة قيمة المحدّد. هذا الإجراء شبيه بذلك الذي قمت بعرضه في هذا القسم. الإجراء الثاني يتم استخدامه لزيادة قيمة المحدد بثلاثة أضعاف، لهذا فإن اسمه TripleTheValue:
الشكل 6.3: نموذج مثال ProcType.
procedure TripleTheValue (var Value: Integer);
begin
Value := Value * 3;
ShowMessage ('Value tripled: ' + IntToStr (Value));
end;
الإجراءان يعرضان ماذا يجري فيهما، لكي يعلمانا بأنهما قد استدعيا. هذه ميزة تعرّف بسيطة يمكنك استخدامها لاختبار اذا ما تم تنفيذ جزء معين من التوليف أو متى تم ذلك، بدلا من اضافة نقاط اعاقة breakpoint فيهما.
في كلّ مرة يقوم فيها المستخدم بالضغط على زرّ Apply، يتم تنفيذ أحد الإجرائين، حسب حالة خاناتي الخيار. في الواقع، عندما يكون لديك إثنان من خانات الخيار في النموذج، واحدة منهما فقط يمكن إختيارها في نفس الوقت. يمكن لهذا التوليف ان يُنفّذ باختبار قيمة خانتي الخيار داخل التوليف الخاص بالحدث OnClick لزرّ Apply. لكن و من أجل استعراض كيفية استخدام الأنواع الإجرائية، قمت بدلا من ذلك باتّباع توجّه أطول لكن مثير للإهتمام. كلّ مرّة يضغط فيها المستخدم على واحدة من خانات الخيار، أحد الإجرائين يتم تخزينه في متغيّر:
procedure TForm1.DoubleRadioButtonClick(Sender: TObject);
begin
IP := DoubleTheValue;
end;
عندما يضغط المستخدم على الزرّ، يتم تنفيذ الإجرائية التي يتم تخزينها:
procedure TForm1.ApplyButtonClick(Sender: TObject);
begin
IP (X);
end;
من أجل السماح لثلاث وظائف مختلفة بتناول المتغيرين IP و X ، نحتاج لجعلهما مرئيين على مستوى النموذج form بالكامل؛ لايمكن تعريفهما محليا localy (داخل أحد المسارات). الحل لهذه المشكلة هي وضع المتغيرين داخل تعريف النموذج:
type
TForm1 = class(TForm)
...
private
{ Private declarations }
IP: IntProc;
X: Integer;
end;
سوق نرى تماما ماذا يعني هذا في الفصل التالي، لكن حاليا، يتطلب منك الأمر تعديل التوليف الذي قامت دلفي بإعداده لنوع الطبقة كما هو موضّح أعلاه. و قم باضافة النوع الإجرائي الذي قمت بعرضه سابقا. من أجل تمهيد هذين المتغيرين بقيم مناسبة، يمكننا مناولة حدث OnCreate الخاص بالنموذج (اختر هذا الحدث في معاين الكائنات Object Inspector بعد تفعيل النموذج، أو قم ببساطة بضغط مزدوج على النموذج). اقترح أن تقوم بمراجعة التوليف لدراسة تفاصيله في المثال.
يمكنك مشاهدة مثال عملي لإستخدام الأنواع الإجرائية في الفصل 9، في قسم وظيفة Callback في ويندوز.
التحميل المضاف لوظيفة
فكرة التحميل المضاف overloading بسيطة: يسمح لك المجمّع compiler بتحديد وظيفتين أو إجرائين يحملان نفس الإسم، بشرط أن تختلف المحدّدات. و باختبار هذه المحددات، يمكن للمجمّ استنتاج أية نسخة من الإجرائيتين تريد استدعاؤها.
راجع هذه السلسة من الوظائف المستخرجة من وحدة Math في مكتبة VCL:
function Min (A,B: Integer): Integer; overload;
function Min (A,B: Int64): Int64; overload;
function Min (A,B: Single): Single; overload;
function Min (A,B: Double): Double; overload;
function Min (A,B: Extended): Extended; overload;
عندما تنادي Min (10,20)، يستنتج المجمّع ببساطة بأنك تقصد استدعاء الوظيفة الأولى من المجموعة، لذا فالقيمة المرتجعة ستكون رقما صحيحا.
القواعد الأساسية اثنان:
كل نسخة من الإجرائية يجب أن تكون متبوعة بالكلمة المفتاحية overload.
الاختلاف يجب أن يكون في عدد أو نوع المحدّدات، أو في كلاهما. و ليس في نوع المرتجع، الذي لا يمكن استخدامه للتفريق بين الاجرائيتين.
فيما يلي ثلاث نسخ بحمل مضاف لإجراء ShowMsg قمت باضافتها لمثال OverDef (التطبيق الذي يستعرض الحمل المضاف و المحددات الافتراضية)
procedure ShowMsg (str: string); overload;
begin
MessageDlg (str, mtInformation, [mbOK], 0);
end;
procedure ShowMsg (FormatStr: string;
Params: array of const); overload;
begin
MessageDlg (Format (FormatStr, Params),
mtInformation, [mbOK], 0);
end;
procedure ShowMsg (I: Integer; Str: string); overload;
begin
ShowMsg (IntToStr (I) + ' ' + Str);
end;
الوظائف الثلاثة تعرض نافذة رسالة مع جملة، بعد صياغة متكررة للجملة بعدة طرق. ها هنا النداءات الثلاث للبرنامج:
ShowMsg ('Hello');
ShowMsg ('Total = %d.', [100]);
ShowMsg (10, 'MBytes');
ما ادهشني ايجابيا هو ان تقنية Code Parameters محددات التوليف في دلفي تعمل بصورة رائعة مع محددات الحمل المضاف. فمع بداية فتح قوس في طباعتك بعد اسم الإجرائية، يتم عرض كل البدائل المتوفرة. و مع ادخالك للمحدد، تقوم دلفي باختبار نوعه لتقرير أيا من البدائل لايزال متوفرا. في الشكل 6.4 يمكنك رؤية هذا بعد البدء بكتابة ثابت جملة تعرض دلفي نسخة متوافقة واحدة (تستثني نسخة إجراء ShowMsg الذي لها نوع صحيح كأول محدد).
الشكل 6.4: البدائل المتعددة التي اقترحتها تقنية Code Parameters لإجرائيات الحمل المضاف، مفروزة بحسب المحددات المتوفرة فعلا.
حقيقة أن كل نسخة من إجرائية حمل مضاف overloaded يجب أن تكون معلّمة بوضوح؛ هذا يعني ضمنا أنك لاتستطيع تحميل إجرائية موجودة في نفس الوحدة unit وليست معّلّمة بمصطلح overload. (رسالة الخطأ التي تظهر لك عندما تحاول ذلك هي: تعريف سابق ل <اسم الاجرائية> ليست معلمّة بتوجيه 'overload'.) عموما ، يمكنك اجراء تحميل اضافي لإجرائية تكون قد سبق تعريفها في وحدة مختلفة. هذا لأجل التوافقية مع النسخ السابقة من دلفي، و التي تسمح لعدة وحدات أن تعيد استخدام نفس اسم الإجرائية. لاحظ، على أية حال، بأن هذه الحالة الخاصة ليست ميزة اضافية للتحميل المضاف، لكنها اشارة الى المشكلات التي قد تواجهها.
مثلا، يمكنك اضافة التوليف التالي للوحدة:
procedure MessageDlg (str: string); overload;
begin
Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
end;
هذا التوليف لايقوم فعلا بحمل اضافي لإجرائية MessageDlg الأصلية. في الواقع إذا كتبت:
MessageDlg ('Hello');
سوف تتحصل على رسالة خطأ لطيفة تشير إلى غياب بعض المحددات. الطريقة الوحيدة لإستدعاء نسخة محلية بدلا من أخرى تابعة لمكتبة VCL هي في أن تشير صراحة إلى الوحدة المحليّة، الأمر الذي يخدش فكرة التحميل المضاف:
OverDefF.MessageDlg ('Hello');
المحدّدات الافتراضية
خاصّية جديدة أخرى في دلفي 4 و هي أنك تستطيع أن تعطي قيمة افتراضية default لمحدد إجراء أو وظيفة، و يمكنك استدعاء هذه الوظيفة رفق المحدد أو بدونه. دعني أعرض مثالا. يمكننا تحديد التغليف التالي لمسار method MessageBox الخاص بالكائن العام Application، و الذي يستخدم أنواع PChars بدلا من جمل strings، و سوف نوفّر محددين افتراضيين:
procedure MessBox (Msg: string;
Caption: string = 'Warning';
Flags: LongInt = mb_OK or mb_IconHand);
begin
Application.MessageBox (PChar (Msg),
PChar (Caption), Flags);
end;
بهذا التعريف، يمكننا استدعاء الاجرائية بأي من الطرق التالية:
MessBox ('Something wrong here!');
MessBox ('Something wrong here!', 'Attention');
MessBox ('Hello', 'Message', mb_OK);
في الشكل 6.5 يمكنك رؤية محددات التوليف Code Parameters لدلفي يستخدم نمط مختلف و مناسب ليشير الى المحددات التي لها قيم افتراضية، بحيث تستطيع بسهولة تبيان أي المحددات التي يمكنك استبعادها.
الشكل 6.5: محددات التوليف لدلفي تشير بين أقواس مربعة الى المحددات التي لها قيم افتراضية؛ و التي يمكنك استبعادها في هذا الاستدعاء.
لاحظ ان دلفي لا تقوم بتوليد أي توليف خاص لدعم المحددات الافتراضية؛ كما لا تنشىء نُسخا متعدّدة من الإجرائية. ببساطة، المحددات المستبعدة يتم اضافتها من قبل المجمّع إلى التوليف الذي قام بالإستدعاء.
هناك قيد واحد مهم يؤثّر على استخدام المحدّدت الإفتراضية: لا يمكنك تخطّي المحدّدات. مثلا، لا تستطيع تمرير المحدّد الثالث للوظيفة بعد استبعادك للمحدّد الثاني:
MessBox ('Hello', mb_OK); // error
هذه هي القاعدة الرئيسية للمحددات الافتراضية: حين الاستدعاء، يمكنك فقط استبعاد المحددات بدءا من المحدد الأخير. بعبارة أخرى، إذا استبعدت محدّدا يجب أيضا استبعاد ما يليه.
هناك أيضا بعض القواعد الأخرى للمحددات الافتراضية:
المحددات ذات القيمة الافتراضية يجب أن تكون في آخر قائمة المحددات.
القيم الإفتراضية يجب تكون ثوابت constants. واضح، ان هذا يقيّد الأنواع التي تستطيع استعمالها مع المحددات الافتراضية. مثلا المصفوفة المفتوحة أو نوع واجهة interface type لايمكن أن يكون لها قيمة افتراضية غير لاشيء nil؛ التسجيلات records لا يمكن استخدامها اطلاقا.
المحددات الافتراضية يجب أن يتم تمريرها بقيمة أو كثابت. محدد (var) بالاشارة reference لايمكن أن يكون لها قيمة افتراضية.
استخدام محددات افتراضية و حمل مضاف في نفس الوقت يمكن ان يسبب عدّة مشاكل، بسبب امكانية تعارض الميزتين. مثال ذلك، إذا ما اضفت للمثال السابق النسخة الجديدة التالية من إجرائية ShowMsg:
procedure ShowMsg (Str: string; I: Integer = 0); overload;
begin
MessageDlg (Str + ': ' + IntToStr (I),
mtInformation, [mbOK], 0);
end;
عندها المجمّع لن يتذمر إنّه تعريف صحيح. لكن الإستدعاء:
ShowMsg ('Hello');
يتم اعتباره من قبل المجمّع على أنه استدعاء حمل مضاف ملتبس Ambiguous overloaded call to 'ShowMsg'. لاحظ أن هذا الخطأ يظهر في سطر التوليف الذي تم تحويله بطريقة صحيحة قبل تعريف الحمل المضاف الجديد. عمليا، ليس لدينا أية طريقة لاستدعاء أجراء ShowMsg بمحدد جملة واحد، حيث أن المحوّل لا يعرف إذا كنا نريد استدعاء النسخة التي بمحدد جملة واحد فقط أو تلك التي بمحدد جملة و محدد رقم صحيح ذو قيمة افتراضية. عندما يكون لديه مثل هذا الشكّ، المحوّل يتوقّف و يسأل المبرمج أن يبيّن قصده بوضوح أكثر.
ملخّص
كتابة الإجراءات و الوظائف هو العنصر الأساسي في البرمجة، بالرغم من أنك في دلفي سوف تتجه لكتابة المسارات methods -- إجراءات و وظائف مرتبطة بالطبقات classes و الكائنات objects.
بدلا من التنقّل إلى خصائص الإتجاه الكائني object-oriented، الفصول القليلة القادمة تقدّم لك بعض التفاصيل عن عناصر أخرى في برمجة باسكال، مبتدئين بالجُمل strings.
الفصل التالي: مناولة الجُمل
الفصل 7
مناولة الجُمل
مناولة الجُمل strings في دلفي أمر بسيط، لكن وراء الكواليس؛ الحالة معقدة بعض الشيء. لباسكال طريقتها التقليدية لمناولة الجُمل، ويندوز لها طريقتها الخاصة، المستمدة من لغة س. نسخ دلفي 32-بت تضمنت نوع بيانات قوي لجمل طويلة، و التي تشكل النوع الافتراضي لنوع جملة string في دلفي.
أنواع الجُمل
في تربو باسكال و في دلفي 16-بت من بورلاند، نجد أن النمط العام لنوع جملة هو تتابع من الأحرف مع بايت (حرف) في بدايتها، بشير إلى الحجم الحالي للجملة. و لأن الحجم يعبّر عنه ببايت وحيد، فليس بإمكانه أن يتعدّى 255 حرفا، و هو قيمة منخفضة جدا تخلق عدة مشاكل عند مناولة الجمل. كلّ جملة تُحدّد بحجم ثابت (افتراضيا تكون بحدها الأقصى، 255)، مع انك تستطيع أن تعرّف جملا أقصر للحفاظ على مساحة الذاكرة.
نوع الجملة يشبه نوع مصفوفة. في الواقع، ان الجملة هي تقريبا مصفوفة من أحرف. ما يبيّن هذا؛ حقيقة أنه يمكنك الوصول إلى حرف معيّن في الجملة باستخدام تركيبة [].
لتجاوز قيود جمل باسكال التقليدية، قامت نسخ 32-بت من دلفي بدعم الجمل الطويلة. يوجد في الواقع ثلاث أنواع جمل:
نوع ShortString جملة قصيرة و يتوافق مع جمل باسكال التقليدية، كما تم وصفه سابقا، هذه الجمل محدودة ب 255 حرف و تتماشى مع الجمل في نسخة 16-بت من دلفي. كل جزء من جملة قصيرة هي من نوع ANSIChar (النوع القياسي للأحرف).
نوع ANSIString و يتطابق مع جديد الجمل الطويلة متغيرة الطول. هذه الجمل يتم تخصيصها حيويا، هي معدودة الاشارة reference counted ، و تستخدم تقنية copy-on-write نسخ عند الكتابة. حجم هذه الجمل غير محدود تقريبا (يمكنها التخزين لغاية 2 بليون حرف!). وهي أيضا مبينة على نوع ANSIChar.
نوع WideString جملة عريضة وهي تشبه نوع ANSIString لكنها مبنية على نوع WideChar . لتخزين أحرف Unicode.
استخدام الجمل الطويلة
اذا استخدمت ببساطة نوع جملة طويلة، فإنك ستتحصّل إما على جمل قصيرة أو جمل ANSI، و ذلك حسب قيمة التوجيه $H للمجمّع. قيمة $H+ (و هي القيمة الافتراضية) تعني جمل طويلة (نوع ANSIString)، و هو المستخدم من قبل مكوّنات دلفي.
جمل دلفي الطويلة تعتمد على آلية تعداد الإشارة reference counting، و التي تقوم بتتبّع مقدار المتغيرات نوع جملة التي تشير إلى نفس الجملة في الذاكرة. تعداد الإشارة هذا يُستخدم أيضا لتحرير الذاكرة التي تحتلّها جملة يكون قد توقّف استعمالها، أي عندما يبلغ تعداد الإشارة صفرا.
إذا أردت زيادة حجم جملة في الذاكرة لكن المنطقة المجاورة من الذاكرة تكون محجوزة من قبل شيء آخر، عندها لا يمكن للجملة أن تتوسع في نفس منطقة الذاكرة، بل يجب أخذ نسخة كاملة من الجملة و نقلها لمكان آخر في الذاكرة. عندما تحدث مثل هذه الحالة، فإن وقت التشغيل لدلفي يدعم اعادة توطين الجملة من أجلك و ذلك بأسلوب شفاف تماما و غير محسوس. أنت فقط تقوم بتحديد السعة القصوى للجملة بواسطة إجراء SetLength، و سيتم تخصيص المقدار المطلوب من الذاكرة بكفاءة.
SetLength (String1, 200);
إجراء SetLength يقوم بطلب ذاكرة، و ليس بعملية تخصيص فعلية لذاكرة. أنه يحتفظ يمساحة الذاكرة المطلوية لإستعمالها لاحقا، بدون أن يقوم باستعمالها فعليا. هذه التقنية تعتمد على خاصية في أنظمة تشغيل ويندوز، و تستخدمها دلفي لجميع تخصيصات الذاكرة الحيوية. مثلا عندما أنت تقوم بطلب مصفوفة ضخمة جدا، يتم الحجز في الذاكرة المطلوبة و لكن لا يتم تخصيصها.
نادرا ما تدعو الحاجة إلى تحديد طول لجملة. الحالة الوحيدة التي يجب فيها أن تقوم بتخصيص ذاكرة لجملة طويلة باستخدام SetLenth هي عندما يتطلب الأمر منك تمرير جملة كمحدد الى وظيفة API (بعد تلبيس نوع مناسب)، كما سأقوم بعرضه بعد قليل.
النظر إلى الجُمل في الذاكرة
لمساعدتك على فهم أفضل لتفاصيل إدارة الذاكرة للجُمل، قمت بكتابة مثال StrRef البسيط. في هذا البرنامج قمت بتعريف جامع global لجملتين: Str1 و Str2. عندما يتم الضغط على أوّل الزرّين، يقوم البرنامج بتخصيص ثابت جملة لأوّل المتغيرين ثم بعدها يقوم بتخصيص المتغير الثاني بالأول:
Str1 := 'Hello';
Str2 := Str1;
بجانب التعامل مع الجمل، يقوم البرنامج بعرض حالتها الداخلية في مربّع قائمة listbox، مستعملا وظيفة StringStatus التالية:
function StringStatus (const Str: string): string;
begin
Result := 'Address: ' + IntToStr (Integer (Str)) +
', Length: ' + IntToStr (Length (Str)) +
', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) +
', Value: ' + Str;
end;
أمر حيوي في وظيفة StringStatus أن يتم تمرير محدد الجملة كمحدد ثابت. ان تمرير هذا المحدد بواسطة النسخ سينتج عنه آثارا جانبية لوجود إشارة أخرى اضافية للجملة في نفس وقت تنفيذ الوظيفة. بالمقابل، ان تمرير المحدد بطريق الإشارة (var) أو ثابت (const) لا يسبب في خلق إشارة إضافية للجملة. لقد استعملت في هذه الحالة محدد const، حيث ليس من المفترض أن تقوم الوظيفة بتعديل الجملة.
للحصول على عنوان موقع الجملة في الذاكرة (مفيد لمعرفة صفتها الفعلية و لرؤية متى تقوم جملتان بالإشارة إلى نفس منطقة الذاكرة)، قمت ببساطة و في عمق التوليف بتلبيس نوع typecast من نوع جملة إلى نوع صحيح. الجمل عمليا هي إشارات، هي مؤشّرات: قيمتها تحوي موقع الذاكرة الفعلي للجملة.
من أجل إستخراج عدد الإشارات، جعلت التوليف يعتمد على حقيقة غير معروفة عند الكثيرين و هي أن الطول و عدد الإشارات يتم تخزينها فعليا في الجملة، قبل النصّ الفعلى وقبل الموقع الذي يشير اليه متغيّر الجملة. الموقع (بالسالب) هو -4 و فيه طول الجملة (قيمة يمكنك استخراجها بسهولة أكبر باستعمال وظيفة Length) و -8 و فيه عدد الإشارات.
تذكّر بأن هذه المعلومات الداخلية المتعلقة بمواقع الذاكرة offsets يمكن أن تتغيّر في النسخ المستقبلية من دلفي؛ أيضا لا يوجد أية ضمانة بأن خصائص مشابهة و غير موثّقة سيتم الاحتفاظ بها مستقبلا.
بتشغيل البرنامج، يجب أن تحصل عل جملتين بنفس المحتوى، نفس موقع الذاكرة، و عدد 2 من الاشارات، كما هو ظاهر في الجزء العلوي من القائمة في الشكل 7.1. الآن إذا قمت بتغيير قيمة احدى هاتين الجملتين (لا يهم أية واحدة منهما)، فإن موقع الذاكرة للجملة المعدّلة سوف يتغيّر. هذا هو تأثير تقنية النسخ عند الكتابة.
الشكل 7.1: مثال StrRef يعرض الحالة الداخلية لجملتين، بما في ذللك العدد الحالي للإشارة
يمكننا فعليا توليد هذا التأثير، المبيّن في القسم الثاني من القائمة في الشكل 7.1، من خلال كتابة التوليف التالي للحدث OnClick للزرّ الثاني:
procedure TFormStrRef.BtnChangeClick(Sender: TObject);
begin
Str1 [2] := 'a';
ListBox1.Items.Add ('Str1 [2] := ''a''');
ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1));
ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2));
end;
لاحظ ان التوليف الخاص بمسار BtnChangeClick لا يمكن تنفيذه إلا بعد مسار BtnAssignClick. و لضمان هذا، يبدأ البرنامج و الزرّ الثاني في حالة خمود disabled (سمة Enabled قيمتها سالبة False)؛ ثم يعيد البرنامج تمكين الزرّ مع نهاية المسار الأول. يمكنك التوسع بحرية في هذا المثال و استخدام وظيفة StringStatus لاستكشاف سلوك الجمل الطويلة في ظروف اخرى متعددة.
جُمل دلفي و PChars في ويندوز
نقطة أخرى مهمة فيما يتعلّق باستخدام الجمل الطويلة و هي: أن هذه الجمل منتهية بصفر null-terminated. هذا يعني أنها متوافقة بالكامل مع الجمل المنتهية بصفر في لغة س و المستخدمة في ويندوز. الجمل المنتهية بصفر هي عبارة عن تتابع لأحرف يلحقها حرف بايت قيمته صفر (أو لاشيء). يمكن التعبير عن هذا في دلفي باستخدام مصفوفة أحرف تبدأ بصفر، و هي نوع البيانات المتبع عادة لبناء الجمل في لغة س. هذا ما يشرح سبب أن مصفوفات الأحرف المنتهية بصفر شائعة الإستخدام في وظائف API في ويندوز (و المبنية على لغة س). فحيث أن جمل باسكال الطويلة متوافقة بالكامل مع الجمل المنتهية بصفر في لغة س، يمكنك ببساطة استخدام الجمل الطويلة و تلبيسها لنوع PChar عندما تحتاج إلى تمرير جملة وظيفة API في ويندوز.
مثال ذلك، لنسخ عنوان نموذج و وضعها في جملة PChar (باستخدام وظيفة API: وهي GetWindowText) ثم نسخها لعنوان زرّ، يمكنك كتابة التوليف التالي:
procedure TForm1.Button1Click (Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
Button1.Caption := S1;
end;
يمكنك ايجاد هذا التوليف في مثال LongStr. لاحظ انك إذا كتبت هذا التوليف و اغفلت عن تخصيص ذاكرة للجملة بواسطة SetLength، فان البرنامج سينهار غالبا. إذا قمت باستخدام PChar من أجل تمرير قيمة (و ليس لإستقبال قيمة كما في التوليف أعلاه)، سيكون التوليف أكثر سهولة، لأنه لا توجد حاجة لتعريف جملة مؤقتة و تمهيدها. سطر التوليف التالي يقوم بتمرير سمة Caption الخاصة بملصق Label كمحدد لوظيفة API، ببساطة بتلبيس نوعها لنوع PChar:
SetWindowText (Handle, PChar (Label1.Caption));
عندما تحتاج لتلبيس جملة عريضة WideString الى نوع يتوافق مع ويندوز، عليك استخدام PWideChar بدلا من PChar لغرض التحويل. الجمل العريضة غالبا ما تستخدم في برامج تستخدم تقنيات OLE و COM.
بعد أن أبرزت الصورة المشرقة، الآن أريد أن أركّز على الشراك المنصوبة. هناك بعض المشاكل التي يمكن ان تبرز عندما تقوم بتحويل جملة طويلة إلى نوعPChar. بصورة أساسية، المشكلة هي أنه بعد هذا التحويل، ستكون مسؤولا عن الجملة و عن محتواها، و لن تساعدك دلفي بشيء. لاحظ التغيير المحدود التالي لجزء توليف البرنامج الأول أعلاه، Button1Click:
procedure TForm1.Button2Click(Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
S1 := S1 + ' is the title'; // this won't work
Button1.Caption := S1;
end;
هذا البرنامج سيتم تحويله، لكنك عند تشغيله، فستكون أمام مفاجأة: سمة Caption للزرّ سيكون لها النص الأصلي لعنوان النافذة، بدون نص ثابت الجملة الذي اضفته له. المشكلة هي أن ويندوز عندما قامت بكتابة الجملة (من خلال استدعاء GetWindowText)، لم تقم بتوصيف طول جملة باسكال الطويلة بطريقة سليمة. لا يزال بمقدور دلفي استخدام هذه الجملة كمخرجات و يمكنها معرفة متى تنتهي هذه الجملة من خلال البحث عن الحرف الصفري المُنهي للجملة، لكنك اذا اتبعتها بأحرف أخري بعد الحرف الصفري، فسوف يتم تخطي هذه الأحرف و اغفالها.
كبف يمكننا تجاوز هذه المشكلة؟ الحلّ هو في اخبار النظام بأن يقوم باعادة تحويل الجملة المرتجعة من استدعاء GetWindowText الى جملة باسكال. عموما، إذا كتبت التوليف التالي:
S1 := String (S1);
فإن النظام سيتجاهله، لأن تحويل نوع بيانات إلى نفسه عملية غير مجدية. للحصول على جملة باسكال طويلة سليمة، تحتاج إلى إعادة تلبيس الجملة إلى نوع PChar ثم دع دلفي تقوم بالتحويل المناسب ثانية إلى جملة.
S1 := String (PChar (S1));
حقيقة، يمكن تخطّي عملية تحويل الجملة، لأن التحويلات من PChar إلى جملة تتم آليا في دلفي. ها هنا التوليف النهائي:
procedure TForm1.Button3Click(Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
S1 := String (PChar (S1));
S1 := S1 + ' is the title';
Button3.Caption := S1;
end;
بديل أخر و هو إعادة توصيف طول جملة دلفي، باستخدام طول جملة PChar، بكتابة:
SetLength (S1, StrLen (PChar (S1)));
سوف تجد ثلاث نسخ من هذا التوليف في مثال LongStr، الذي له ثلاثة أزرار لتنفيذها. عموما، إذا كنت تريد فقط الوصول إلى عنوان النموذج، يمكنك ببساطة استعمال سمة Caption الخاصة بكائن النموذج نفسه. فلا توجد حاجة لكتابة كل هذا التوليف المربك، و الذي كان فقط لأغراض بيان مشاكل تحويل الجملة. هناك حالات عملية تحتاج فيها للإستعانة بوظائف API، و عندها سيكون عليك الأخذ في الإعتبار مثل هذه الحالة المعقدة.
تشكيل الجُمل
باستخدام علامة الموجب (+) و بعض وظائف التحويل (مثل IntToStr) يمكنك بالتأكيد بناء جمل مركبة من القيم الموجودة. عموما توجد عدة وجهات لتشكيل الأرقام، قيم العملة، و جمل أخرى إلى جملة النهائية. يمكنك استخدام وظيفة Format القوية أو واحدة من الوظائف المرافقة لها.
وظيفة Format تتطلب كمحدادات: النصّ الأساسي مع حواجز مكان placeholders (عادة ما تعلّم برمز %) و مصفوفة من القيم، كلّ قيمة خاصة بحاجز مكان. مثلا، لتشكيل رقمين في جملة يمكنك كتابة:
Format ('First %d, Second %d', [n1, n2]);
حيث n1 و n2 قيمتا عدد صحيح. الحاجز الأول يُستبدل بالقيمة الأولى، الثاني يوافق القيمة الثانية، و هكذا. إذا كان نوع المخرجات لحاجز (يشار إليه بحرف بعد رمز %) لا يوافق نوع المحدد ذو العلاقة، سيظهر خطأ وقت تشغيل. إن عدم وجود تفحص للنوع في وقت التجميع يشكل فعلا أكبر عيب في استخدام وظيفة Format.
وظيفة Format تستخدم محدّد مصفوفة مفتوحة (محدد يمكنه أن يحوي أي عدد من القيم)، الأمر الذي سأناقشه مع نهاية هذا الفصل. حاليا، لاحظ فقط الصيغة الشبيهة بالمصفوفة لقائمة القيم التي يتم تمريرها كمحدد ثان.
بجانب استخدام %d، يمكنك استخدام حواجز أخرى معرّفة في هذه الوظيفة و التي تم سردها بايجاز في الجدول 7.1. توفّر هذه الحواجز مخرجات افتراضية حسب نوع البيانات. عموما يمكنك استخدام معيّنات تشكيل أخرى لتغيير المخرجات الافتراضية. معيّن العرض، مثلا، يحدد عددا ثابتا من الأحرف في المخرجات، بينما معين الدقّة يشير إلى عدد الخانات العشرية. مثلا،
Format ('%8d', [n1]);
يحوّل رقم n1 إلى جملة بثمانية أحرف، مع صفّ النص على اليمين (استخدم رمز سالب (-) لصف النصّ لليسار) مالئا الباقي بفراغات.
جدول 7.1: معينات النوع لوظيفة Format
أفضل طريقة لرؤية أمثلة عن هذه التحويلات هي في أن تقوم باختبار تشكيل الجمل بنفسك. لجعل هذا الأمر سهلا قمت بكتابة برنامج FmtTest، و الذي يسمح للمستخدم بتقديم جمل التشكيل للأرقام الصحيحة و أرقام النقطة العائمة. كما تراه في الشكل 7.2، هذا البرنامج يعرض نموذجا مقسما إلى جزئين، الجزء الأيسر للأرقام الصحيحة، و الجزء الأيمن لأرقام النقطة العائمة.
كل جزء لدية خانة كتابة أولى مع القيمة الرقمية المراد تشكيلها لجملة. تحت خانة الكتابة الأولى يوجد زرّ لإنجاز عملية التشكيل عارضا النتيجة في نافذة رسالة. ثم تأتي خانة كتابة أخرى، حيث يمكن كتابة جملة التشكيل. كبديل يمكنك ببساطة لمس أحد أسطر مكوّن القائمة، في الأسفل، لإختيار تشكيل جملة محدد سابقا. في كلّ مرّة تكتب تشكيلا جديدا لجملة، يتم اضافته كعنصر جديد للقائمة ذات العلاقة (لاحظ أنه عند غلق البرنامج تُفقد هذه العناصر الجديدة).
الشكل 7.2: مخرجات قيمة نقطة عائمة من برنامج FMTTest
يستخدم توليف هذا المثال نصوص متحكمات مختلفة لتوليد مخرجاته. هذا واحد من ثلاثة مسارات مرتبطة بزرّ Show:
procedure TFormFmtTest.BtnIntClick(Sender: TObject);
begin
ShowMessage (Format (EditFmtInt.Text,
[StrToInt (EditInt.Text)]));
// if the item is not there, add it
if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then
ListBoxInt.Items.Add (EditFmtInt.Text);
end;
التوليف أساسا يُجري عمليات تشكيل باستخدام نص خانة كتابة EditFmtInt و قيمة متحكم EditInt. إذا كانت جملة التشكيل غير موجودة في القائمة، يتم اضافتها عندئد. أما إذا لمس المستخدم بدلا من ذلك بندا في القائمة، فإن التوليف ينقل تلك القيمة إلى خانة الكتابة.
procedure TFormFmtTest.ListBoxIntClick(Sender: TObject);
begin
EditFmtInt.Text := ListBoxInt.Items [
ListBoxInt.ItemIndex];
end;
ملخّص
تعد الجمل بالتأكيد أكثر أنواع البيانات شيوعا. بالرغم من أنك تستطيع استخدامها بآمان معظم الأحوال بدون ما يدعو لفهم كيفية عملها، فهذا الفصل يجب أن يكون قد أوضح بالظبط سلوك الجمل، جاعلا بالإمكان استخدام كامل قوة هذا النوع من البيانات.
يتم مناولة الجمل في الذاكرة بطريقة حيوية خاصة، كما يحدث مع المصفوفات المفتوحة. هذا هو موضوع الفصل القادم.
الفصل التالي: الذاكرة
لفصل 8
الذاكرة
ملاحظة من المؤلّف: سيغطّي هذا الفصل موضوع مناولة الذاكرة. مناقشا مناطق الذاكرة المختلفة، كما يعرّف المصفوفات الحيوية. مؤقتا فقط الجانب الأخير هو المتوفر.
المصفوفات الحيوية في دلفي 4
تقليديا، كانت لغة باسكال تملك دائما مصفوفات ثابتة الحجم. عندما تقوم بتعريف نوع بيانات مستخدما بنية مصفوفة، يجب عليك تحديد عدد عناصر المصفوفة. كما قد يعلم المبرمجون المتمرسون، كان يوجد عدد من التقنيات يمكنك استخدامها لتنفيذ المصفوفات الحيوية، أهمها استخدام المؤشرات و تخصيص الذاكرة المطلوبة و تحريرها يدويا.
ادخلت دلفي 4 طريقة تنفيذ بسيطة جدا للمصفوفات الحيوية، انتظمت بعد نوع الجمل الطويلة الحيوية التي كنت قد ناقشتها منذ قليل. مثلها مثل الجمل الطويلة، المصفوفات الحيوية يتم تخصيصها و تعدادا اشاراتها آنيا، لكنها تخلو من تقنية النسخ عند الكتابة copy-on-write. هذا لا يشكّل مشكلة كبيرة، حيث يمكنك نزع تخصيص deallocate المصفوفة بجعل متغيرها خاليا nil.
يمكنك الآن ببساطة تعريف مصفوفة بدون الحاجة لتحديد عدد عناصرها ثم تقوم بتخصيصها بحجم معين باستخدام إجراء SetLength. يمكن إستخدام نفس الإجراء لتغيير حجم المصفوفة دون أن تفقد محتوياتها. توجد أيضا إجرائيات أخري لها علاقة بالجمل، مثل وظيفة Copy ، و التي يمكنك استخدامها مع المصفوفات.
فيما يلي مقطع من توليف بسيط، يبرز حقيقة انك يجب أن تقوم بتعريف و تخصيص الذاكرة الخاصة بالمصفوفة قبل أن تبدأ باستعمالها:
procedure TForm1.Button1Click(Sender: TObject);
var
Array1: array of Integer;
begin
Array1 [1] := 100; // error
SetLength (Array1, 100);
Array1 [99] := 100; // OK
...
end;
حالما تحدد فقط عدد العناصر في المصفوفة، يبدأ فهرس المصفوفة دائما من صفر. المصفوفات عامة في باسكال معروفة بإمكانية أن يكون حدّها الأدني غير الصفر و أن تكون فهارسها ليست أعداد صحيحة، خاصيّتان لا تدعمهما المصفوفات الحيوية. لمعرفة حالة المصفوفة الحيوية، يمكنك استخدام وظائف Length و High و Low، كما في أي مصفوفة أخرى. بالنسبة للمصفوفات الحيوية وظيفة Low ترجع دائما قيمة 0، و High ترجع دائما الطول ناقص 1. هذا يعني أنه بالنسبة للمصفوفة الفارغة فإن High ترجع -1(الأمر الذي عندما تتأمل فيه، تجده رقما غريبا، فهو أقل من ذلك المرتجع من Low).
شكل 8.1: نموذج مثال DynArr
بعد هذا التقديم القصير يمكنني أن أريك مثالا بسيطا يدعى DynArr و يظهر في الشكل 8.1. هو بالتأكيد بسيط لأنه لا يوجد أمرا معقّدا فيما يخصّ المصفوفات الحيوية. أنا سأستخدم المثال أيضا لعرض بعض الأخطاء التي قد يقع فيها المبرمجون. يعرّف البرنامج مصفوفتين جامعتين global و يقوم بتمهيد الأول في مناول حدث OnCreate :
var
Array1, Array2: array of Integer;
procedure TForm1.FormCreate(Sender: TObject);
begin
// allocate
SetLength (Array1, 100);
end;
هذا يجعل كل القيم صفرا. توليف التمهيد هذا يجعل من الممكن البدء حالا بقراءة و كتابة قيم المصفوفة، دون أي خوف من أخطاء الذاكرة. (طبعا، بافتراض أنك لن تحاول الوصول إلى عناصر خارج الحدّ العلوي للمصفوفة.) و من أجل تمهيد أفضل، لدى البرنامج زرا يقوم بالكتابة داخل كل خلية في المصفوفة:
procedure TForm1.btnFillClick(Sender: TObject);
var
I: Integer;
begin
for I := Low (Array1) to High (Array1) do
Array1 [I] := I;
end;
زرّ Grow يسمح لك بتعديل حجم المصفوفة بدون أن تفقد محتوياتها. يمكنك إختبار هذا باستعمال قيمة زرّ Get بعد الضغط على زرّ Grow:
procedure TForm1.btnGrowClick(Sender: TObject);
begin
// grow keeping existing values
SetLength (Array1, 200);
end;
procedure TForm1.btnGetClick(Sender: TObject);
begin
// extract
Caption := IntToStr (Array1 [99]);
end;
التوليف الوحيد المعقّد قليلا هو في حدث OnClick لزرّ Alias. البرنامج ينسخ مصفوفة داخل الأخرى بواسطة العامل :=، منشئا بكفاءة رديفا Alias (متغير جديد يشير إلى نفس المصفوفة في الذاكرة). عند هذه النقطة، على أي حال، إذا قمت بتعديل أحد المصفوفتين، ستتأثر الأخرى كذلك، بالنظر إلى أن كلتاهما تشيران إلى نفس منطقة الذاكرة:
procedure TForm1.btnAliasClick(Sender: TObject);
begin
// alias
Array2 := Array1;
// change one (both change)
Array2 [99] := 1000;
// show the other
Caption := IntToStr (Array1 [99]);
مسار btnAliasClick يقوم بعمليتين أخريين. الأولى هي إختبار تساوي على المصفوفتين. هذه العملية لا تختبر العناصر الفعلية للبنيتين و لكن تختبر مناطق الذاكرة التي تشير لها المصفوفتان، فتتفحّص إذا ما كانا المتغيران هما رديفان لنفس المصفوفة في الذاكرة.
procedure TForm1.btnAliasClick(Sender: TObject);
begin
...
if Array1 = Array2 then
Beep;
// truncate first array
Array1 := Copy (Array2, 0, 10);
end;
العملية الثانية هي إستدعاء لوظيفة Copy، و التي ليست فقط تنقل البيانات من مصفوفة للأخرى، لكنها أيضا تستبدل بالمصفوفة الأولى المصفوفة الجديدة التي تم إنشاؤها بواسطة الوظيفة. التأثير هو أن متغير Array1 الآن يشير إلى مصفوفة تحوي 11 عنصرا، لذا فإن الضفط على زرّ Get value أو زرّ Set value سوف يولّد خطأ ذاكرة و يبرز اعتراضا exception (إلا إذا كنت قد أوقفت خيار تفحّص المدى range-checking، في هذه الحالة يظلّ الخطأ لكن الاعتراض لا يتم اظهاره). توليف زرّ Fill يستمر في العمل جيدا حتى بعد هذا التغيير، حيث أن تحديد عناصر المصفوفة التي سيتم تعديلها يتم بمعرفة حدّيها الحاليين.
ملخّص
يغطّي هذا الفصل مؤقتا المصفوفات الحيوية، و هو بالتأكيد عنصرا مهمّا في إدارة الذاكرة، لكنه جزءا فقط من كامل الصورة. المزيد من الموضوعات ستتبع لاحقا.
بنية الذاكرة التي تم وصفها في هذا الفصل هي من صميم برمجة ويندوز، الموضوع الذي سأتولى تقديمه في الفصل التالي (بدون الخوض في كامل موضوع استخدام VCL، مع ذلك).
الفصل التالي: برمجة ويندوز
الفصل 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.
الفصل التالي: المتباينات