الخميس، 11 أغسطس 2011

كلمات: استدعاء إجراءات سي

إمكانات كثيرة يريدها المبرمج: رسومات ثلاثية الأبعاد، مؤثرات صوتية، اتصال بالإنترنت، تعامل مع نظام التشغيل... حين اضفت إمكانيات الGUI كان لابد أن أغير المفسر interpreter، لأن مفسر كلمات كان حلقة الاتصال الوحيدة بين اللغة وبين العالم الخارجي. هل سيحدث نفس الشيء مع كل الإمكانيات الأخرى؟

لا ينبغي تغيير مفسر اللغة كل مرة يُحتاج فيها لإضافة إمكانية، لذلك كان لابد من تغيير هذا الوضع...

[ملاحظة هامة: لن تعمل هذه الأمثلة إلا على إصدار اغسطس 2011 أو أحدث. يمكنك تحميل أحدث إصدارة من هنا أو هنا].

كل اللغات المعروفة تقريباً فيها إمكانية التعامل مع الدوال الخارجية (Foreign function interface) أو FFI، وهذا في أغلب الأحيان يساوي إمكانية للتعامل مع مكتبات الربط DLL المكتوبة بالسي. هذه الإمكانية الآن موجودة في كلمات أيضاً. مثال بسيط على هذا؛ دالة MessageBox:
مكتبة "user32.dll" :

دالة رسالة برمز "MessageBoxW" ( مشير.سي،
نص.سي،
نص.سي،
صحيح32.سي ) صحيح32.سي
نهاية
م = رسالة ( 0 ، "هذه العملية سوف تدمر كل شيء!!"،
"هل أنت متأكد؟" ، 3 )

هذه أول مرة نرى فيها Type declarations في كلمات! نحن لا نستخدمها إلا في حالة الـFFI لكن من يدري؟ قد نراها مستقبلاً في أدوار أخرى...

لاحظ في المثال السابق أن نوع القيمة القيمة الراجعة من الدالة (وهو صحيح32.سي) مكتوب في آخر السطر المعرفة فيه الدالة نفسها. ماذا لو كنا نريد استدعاء void function؟ في تلك الحالة نعرف إجراء بدلا من دالة ولا نذكر القيمة الراجعة.

قبل أن نكمل هذا مرجع سريع لأنواع البيانات المستخدمة في التعامل مع إجراءات السي:
النوع في لغة سي
النوع في كلمات
int
صحيح32.سي
float
طفوي.سي
long
طويل.سي
double
مزدوج.سي
char
حرفي.سي
char *
نص.آسكي.سي
wchar_t *
نص.سي
void *
مشير.سي

لاحظ أن نص.آسكي.سي يعمل مع الحروف الانجليزية، بينما نص.سي يقبل حروف اليونيكود - ومنها الحروف العربية، والموضوع يعتمد بالطبع على نوع البيانات الذي تقبله الدالة: مثلاً في ويندوز الدالة MessageBoxA تستخدم آسكي بينما MessageBoxW تستخدم يونيكود.

ها هنا مثال آخر، لكن هذه المرة على نسخة كلمات على لينكس، يوضح استخدام دالة getenv:
مكتبة "libc.so.6" :

دالة بيئة برمز "getenv" ( نص.آسكي.سي ) نص.آسكي.سي
نهاية
م = بيئة ( "PATH" )
اطبع م
يحاول مفسر كلمات التصرف "بعقل" قدر الإمكان، فمثلاً قام بتحويل النص المقدم للدالة من يونيكود (وهو الصيغة الداخلية لكل نصوص كلمات) إلى آسكي قبل استدعاء الدالة. نفس الشيء يحدث لو كانت الدالة تريد قيمة عدد حقيقي وأرسل لها مثلاً عدد صحيح.

ماذا عن الدوال التي تأخد قيماً أعقد من integer أو نص؟ تعال نتخيل أن لدينا مكتبة DLL فيها دالة اسمها TestPoint تأخذ عاملاً بهذا الشكل:
struct POINT
{
long x; long y;
};

لا يمكن في تلك الحالة الاكتفاء بإرسال كائن بفصيلة مصنوعة بكلمات، لأن المفسر سيعجز عن معرفة طريقة نقل (أو تسيير، marshalling) كل بيان من كلمات إلى مكانه الصحيح في الذاكرة لتتمكن دالة السي من استخدامه.

هذا بسبب أن معلومات POINT موجودة لدى مترجم السي وحده، ولو أردنا استخدامها من كلمات سيكون علينا إعادة تعريف هذه المعلومات:
فصيلة نقطة :

له س تسييره طويل.سي
له ص تسييره طويل.سي
نهاية

مكتبة "simple_dll.dll" :
دالة اختبر.نقطة برمز "TestPoint" ( نقطة ) صحيح32.سي
نهاية

ن = نقطة جديد
س ن = 130
ص ن = 180
ذ = اختبر.نقطة ( ن )
اطبع ذ
هنا فصيلة نقطة هي فصيلة عادية جدا، فقط أضفنا معلومات عن كيفية استخدام قيم س، ص عند ارسال نقطة لدالة سي. يمكننا - إن أردنا - أن نضيف المزيد من الfields أو الmethods لفصيلتنا، ولكن عند تقديمها لدالة سي لن يتم التعامل إلا مع البيانات المعرف لها "تسيير".

هيا نكتب الآن مثالاً أطول يستخدم Windows API. سوف نكتب برنامجاً يرسم مستطيلاً أسوداً في منتصف الشاشة - في منتصف الشاشة الحقيقية وليس نافذة كلمات.

أولاً نكتب هذا البرنامج بلغة سي ثم "نترجمه" خطوة خطوة:
HDC dc = GetDC(0); // Retrieve device context of whole screen
HBRUSH brush = GetStockObject(BLACK_BRUSH);
RECT r;
r.left = 800;
r.top = 600;
r.right = 1000;
r.bottom = 700;
FillRect(dc, &r, brush);

يستخدم البرنامج هنا ثلاث دوال سي: GetDC، GetStockObject، FillRect

ويستخدم أيضاً نوع بيانات جديد هو RECT. فلنعرفه أولاً:
فصيلة مستطيل :

له يسار تسييره طويل.سي
له قمة تسييره طويل.سي
له يمين تسييره طويل.سي
له قاع تسييره طويل.سي
نهاية
الآن نعرف الدوال المطلوبة:
مكتبة "user32.dll" :

دالة مجال.رسم برمز "GetDC" ( مشير.سي ) مشير.سي
    دالة املأ.مستطيل برمز "FillRect" ( مشير.سي ، مشير ( مستطيل ) ، مشير.سي ) صحيح32.سي

نهاية

مكتبة "gdi32.dll" :
دالة عنصر.رسم.جاهز برمز "GetStockObject" ( صحيح32.سي ) مشير.سي
نهاية
لاحظ أننا في الدالة FillRect لا نرسل مستطيلاً، بل نرسل مشير pointer إلى مستطيل..وحين استدعينا الدالة في السي لم نرسل المستطيل r بل أرسلنا عنوانه في الذاكرة r&..سينعكس هذا على كود كلمات أيضاً.

لاحظ أيضاً أننا في الأنواع تحت صنف HANDLE مثل HDC أو HBRSUH نستخدم مشير.سي

(تسيير..مشير..لقد صارت اللغة سياسية أكثر من اللازم. الإصدارة القادمة لابد من أمر اسمه أجندة لتكتمل الصورة)

الآن نكتب برنامجنا:
ط = مستطيل جديد

يسار ط = 800
قمة ط = 600
يمين ط = 1000
قاع ط = 700

م = مجال.رسم ( 0 )
الفرشاة = عنصر.رسم.جاهز ( 4 )
املأ.مستطيل ( م ، عنوان ( ط ) ، الفرشاة )
عند تنفيذ البرنامج سوف يظهر مستطيل أسود على الشاشة. قد لا يظهر المستطيل كاملاً لو تقاطع مع نافذة البرنامج، لأن تلك النافذة ترسم نفسها باستمرار.

ليس أكثر عرض شيق في العالم، كما أن إمكانية FFI في صورتها الحالية لم تكتمل، ومليئة بالأخطاء، وتسبب memory leaks مثل المصفاة...لكن الكود، حتى في تلك الصورة، تثبت أننا نسير في الطريق الصحيح: لقد بدأت لغة كلمات تقترب من اللغات الإحترافية. من يدري ماذا يمكن أن يُعمل بها الآن..ربما يضيف أحد مكتبة تستخدم OpenGL.

أو ربما تتصل بقواعد بيانات. أو يصمم بها برامج شبكية. لا أدري - لقد صار الباب مفتوحاً لغيري الآن :)

هناك تعليقان (2):

غير معرف يقول...

هل فى خطه ان كلمات تبقى interpreted ؟
او حتى يبقى ليها eval function أو interactive shell ؟

انا كنت بفكر فى حاجه زى كده http://tryruby.org لتعليم اللغة

Mohamed Samy يقول...

أجل، هناك خطة لعمل نسخة بالفلاش أو HTML5 من الVM المبنية عليها كلمات مما يتيح عمل موقع كالذي وصفته.

في المستقبل أريد أيضاً عمل سايت مثل youtube يعرض عليه الأطفال برامجهم مثل ما يحدث في لغة Scratch.

المشكلة أنني حالياً الشخص الوحيد الذي يعمل في كلمات/التفكير الحوسبي، ولذلك كل مشروع ينتظر دوره :(