الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

رحلة في أعماق الدالة getch

بدأه مصطفى 36a2 في 5 نوفمبر 2013 · 0 رد · 940 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1

السلام عليكم ورحمة الله وبركاته
كنت أرغب بالسؤال عن طريقة عمل getch في أعمق مستواها وصولاً إلى كود الأسمبلي
وأرغب في وضع السؤال هنا ليحاول حلّه الأخوة الأعضاء ..
ولكن نويت إيجاده بنفسي وإليكم ما وصلت إليه .. (أرجو التصحيح في حال الخطأ )

أولا : وظيفة هذه الدالة مطابقة لوظيفة المهمة 7 في المقاطعة 21 في 16 بت .. ولكن ما الحال هنا في 32 بت ؟ مختلف تماااماً
ماذا تتوقّع :) يا عيني طلع بداخلها دوال من مكتبة windows !
وهذا سبب عدم الاعتراف بها في المكتبة القياسية :)
الكود كما ورد في  الملف getch.c  في فيجوال 6

#ifdef _MT
int __cdecl _getch_lk (
#else  /* _MT */
int __cdecl _getch (
#endif  /* _MT */
        void
        )
{
        INPUT_RECORD ConInpRec;
        DWORD NumRead;
        CharPair *pCP;
        int ch = 0;                     /* single character buffer */
        DWORD oldstate;

        /*
         * check pushback buffer (chbuf) a for character
         */
        if ( chbuf != EOF ) {
            /*
             * something there, clear buffer and return the character.
             */
            ch = (unsigned char)(chbuf & 0xFF);
            chbuf = EOF;
            return ch;
        }

        if (_coninpfh == -1)
            return EOF;

        /*
         * _coninpfh, the handle to the console input, is created the first
         * time that either _getch() or _cgets() or _kbhit() is called.
         */

        if ( _coninpfh == -2 )
            __initconin();

        /*
         * Switch to raw mode (no line input, no echo input)
         */
        GetConsoleMode( (HANDLE)_coninpfh, &oldstate );
        SetConsoleMode( (HANDLE)_coninpfh, 0L );

        for ( ; ; ) {

            /*
             * Get a console input event.
             */
            if ( !ReadConsoleInput( (HANDLE)_coninpfh,
                                    &ConInpRec,
                                    1L,
                                    &NumRead )
                 || (NumRead == 0L) )
            {
                ch = EOF;
                break;
            }

            /*
             * Look for, and decipher, key events.
             */
            if ( (ConInpRec.EventType == KEY_EVENT) &&
                 ConInpRec.Event.KeyEvent.bKeyDown ) {
                /*
                 * Easy case: if uChar.AsciiChar is non-zero, just stuff it
                 * into ch and quit.
                 */

                if ( ch = (unsigned char)ConInpRec.Event.KeyEvent.uChar.AsciiChar )
                    break;

                /*
                 * Hard case: either an extended code or an event which should
                 * not be recognized. let _getextendedkeycode() do the work...
                 */
                if ( pCP = _getextendedkeycode( &(ConInpRec.Event.KeyEvent) ) ) {
                    ch = pCP->LeadChar;
                    chbuf = pCP->SecondChar;
                    break;
                }
            }
        }


        /*
         * Restore previous console mode.
         */
        SetConsoleMode( (HANDLE)_coninpfh, oldstate );

        return ch;
}

سنستدعي الدالة getch في البرنامج التالي

#include<conio.h>int main(){    getch();    return 0;}

وسنلاحق عملها خطوة خطوة :
بعد أن يتم تعريف المتغيرات التالية:

        INPUT_RECORD ConInpRec;
        DWORD NumRead;
        CharPair *pCP;
        int ch = 0;                     /* single character buffer */
        DWORD oldstate;

النوع CharPair معرّف في نفس المكتبة كما يلي :

typedef struct {
        unsigned char LeadChar;
        unsigned char SecondChar;
        } CharPair;

 إنشاء ملف والاحتفاظ بمقبضه (عمليا نحن لا نأخذ مقبض ملف بل مقبض الكونسول .. حيث تقوم الدالة CreateFile بإنشاء ملفات وpipes وكذلك Conoles وأمور أخرى )

_coninpfh = (int)CreateFile( "CONIN$",
                                     GENERIC_READ | GENERIC_WRITE,
                                     FILE_SHARE_READ | FILE_SHARE_WRITE,
                                     NULL,
                                     OPEN_EXISTING,
                                     0,
                                     NULL );

ثم تغيير حالة الكونسول كي لا تطبع الدخل

/*
         * Switch to raw mode (no line input, no echo input)
         */
        GetConsoleMode( (HANDLE)_coninpfh, &oldstate );
        SetConsoleMode( (HANDLE)_coninpfh, 0L );

يتم الدخول في حلقة لا نهائية (وهي سبب توقّف كل شيء بانتظار حضرتك تضغط زر)
ثم تتم قراءة دخل الكونسول (حدث القراءة) (الكود التالي داخل شرط ,, ولكن يمكن إزالته)

/*
             * Get a console input event.
             */
            ReadConsoleInput( (HANDLE)_coninpfh,
                                    &ConInpRec,
                                    1L,
                                    &NumRead );

ومكان انتظار الحرف هو الشرط التالي

 if ( (ConInpRec.EventType == KEY_EVENT) &&
                 ConInpRec.Event.KeyEvent.bKeyDown )

سنفترض أننا ضغطنا زر الحرف m ولنر ماذا سيحدث
بعد دورة إضافية للحلقة سيكون الشرط محققا وهو وجود حدث في لوحة المفاتيح وهذا الحدث هو زر مضغوط
وعند اختبار الشرط

if ( ch = (unsigned char)ConInpRec.Event.KeyEvent.uChar.AsciiChar )
                    break;

سيكون الجواب قيمة مغايرة للصفر لأن الحرف المضغوط هو من حروف الآسكي
وسيتم الخروج من الحلقة وتنفيذ

/*
         * Restore previous console mode.
         */
        SetConsoleMode( (HANDLE)_coninpfh, oldstate );

ثم أخيراً .. إعادة الحرف

return ch;

ملاحظة : لو قمنا بضغط ctrl أو alt وهي أزرار تحكم ليس لها قيمة آسكي .. فلن تنتهي الرحلة بسهولة
بل سندخل في الشرط

if ( pCP = _getextendedkeycode( &(ConInpRec.Event.KeyEvent) ) ) {
                    ch = pCP->LeadChar;
                    chbuf = pCP->SecondChar;
                    break;
                }

والذي يرجع قيمة صفر إن لم يترافق زر التحكم مع محرف آخر ..
الرحلة السابقة من أقصر الرحلات .. فمثلاً بمجرّد إغلاقك لقوس الـmain ستتم سلسلة استدعاءات أطول بكثيير من التي ذكرناها اليوم ...
للتأكد من أن ما قمنا به صحيح سنطبّق كل ما سبق في كودنا الخاص ودون استدعاء getch

#include<windows.h>
typedef struct {
        unsigned char LeadChar;
        unsigned char SecondChar;
        } CharPair;
int main()
{
    INPUT_RECORD ConInpRec;
    DWORD NumRead;
    CharPair *pCP;
    int ch = 0;                     /* single character buffer */
    DWORD oldstate;

    int _coninpfh = (int)CreateFile( "CONIN$",
                                     GENERIC_READ | GENERIC_WRITE,
                                     FILE_SHARE_READ | FILE_SHARE_WRITE,
                                     NULL,
                                     OPEN_EXISTING,
                                     0,
                                     NULL );
    GetConsoleMode( (HANDLE)_coninpfh, &oldstate );
    SetConsoleMode( (HANDLE)_coninpfh, 0L );

    while(1)
    {
        /*
        * Get a console input event.
        */
        ReadConsoleInput( (HANDLE)_coninpfh,
                                        &ConInpRec,
                                        1L,
                                        &NumRead );
        if ( ch = (unsigned char)ConInpRec.Event.KeyEvent.uChar.AsciiChar )
                    break;
    }
    return 0;
}

وياللعجب :) كل شيء يعمل :D
لا تنسونا من الدعاء

والله ولي التوفيق

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 5 نوفمبر 2013 في 08:34 — السبب: التصحيح والإضافة

2

مواضيع مشابهة