Форум программистов, компьютерный форум, киберфорум
C/C++: WinAPI
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/9: Рейтинг темы: голосов - 9, средняя оценка - 5.00
 Аватар для blbulyan
12 / 10 / 3
Регистрация: 31.10.2017
Сообщений: 126

Перехват клавиатуры Win32API

03.11.2018, 18:48. Показов 1868. Ответов 3

Студворк — интернет-сервис помощи студентам
Вообщем требуется решить такую задачу. Моя программа(Автокликер) должна начать перехватывать клавиатуру после нажатия горячей комбинации Ctrl+Shift+Alt+Z(будет обрабатывать цифры 0-9 и клавишу Enter, ПРИЧЁМ ГЛОБАЛЬНО, ТО ЕСТЬ НЕ ЗАВИСИМО В ФОКУСЕ МОЯ ПРОГРАММА ИЛИ НЕТ!), это нужно для того чтобы считать новый интервал кликов (ну чтобы настройки каждый раз не открывать), обработка и создание такой горячей клавиши есть, вызываю RegisterHotKey, а в окне ловлю сообщение WM_HOTKEY, с этим всё понятно, с этим проблем нет. А вот с перехватом клавиатуры у меня проблемы, я делаю это с помощью хуков, регистрирую хук вот так:
C++
1
PP.hHookReadMiliSecondsOfClicks = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, hInst, NULL);
PP.hHookReadMiliSecondsOfClicks - Имеет тип данных HHOOK
PP определено вот так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ProgrammParameters {
    HWND GlobalHWND = NULL;
    bool GlobalLeftButtonClick = true; // щёлкать левой кнопкой мыши если выбран переключатель IDC_RADIO_LEFT_BUTTON_CLICK в IDD_SETTINGS 
    bool GlobalRightButtonClick = false;// щёлкать правой кнопкой мыши если выбран переключатель IDC_RADIO_RIGHT_BUTTON_CLICK в IDD_SETTINGS 
    bool ClickMouse = false; // эта переменная становиться true если нажато Ctrl + Alt + Shift + C, и false при повторном нажатии и отвечает за то, будут ли происходить щелчки мышью или нет
    LRESULT GlobalMiliSecondsOfClicks = 10; // глобальная переменная которая содержит интервал кликов
    static enum TimerIds {
        TimerClickId
    }; // в этом перечислении сожержаться идентификаторы таймеров, идентификатр таймера TimerClickId связан с таймером который отправляет сообщение WM_TIMER в соотвествии с интервалом GlobalMiliSecondsOfClicks
    bool TheApplicationHasATrayIcon = false;// эта переменная равна true если приложение имеет иконку в трее
    WORD MaxMiliSeconds = 1000;// максимальное значение интервала кликов
    HHOOK hHookReadMiliSecondsOfClicks = NULL;
    bool HookReadMilisecondsOfClicksIsRegistred = false;
} PP;
- глобальная структура, в самом верху файла Autoclicker.cpp(главный файл проекта)
KeyboardProc - это KeyBoardProc определена вот так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, LPARAM lParam) {
    static TSTRING StringValueMilisecondsOfClick;
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (code < 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }
    else if (code == HC_ACTION) {
        switch (wParam) {
            case WM_KEYDOWN:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                        StringValueMilisecondsOfClick += _TEXT('0');
                        break;
                    case 0x61:
                    case 0x31:
                        StringValueMilisecondsOfClick += _TEXT('1');
                        break;
                    case 0x62:
                    case 0x32:
                        StringValueMilisecondsOfClick += _TEXT('2');
                        break;
                    case 0x63:
                    case 0x33: 
                        StringValueMilisecondsOfClick += _TEXT('3');
                        break;
                    case 0x64:
                    case 0x34:
                        StringValueMilisecondsOfClick += _TEXT('4');
                        break;
                    case 0x65:
                    case 0x35: 
                        StringValueMilisecondsOfClick += _TEXT('5');
                        break;
                    case 0x66:
                    case 0x36:
                        StringValueMilisecondsOfClick += _TEXT('6');
                        break;
                    case 0x67:
                    case 0x37:
                        StringValueMilisecondsOfClick += _TEXT('7');
                        break;
                    case 0x68:
                    case 0x38: 
                        StringValueMilisecondsOfClick += _TEXT('8');
                        break;
                    case 0x69:
                    case 0x39:
                        StringValueMilisecondsOfClick += _TEXT('9');
                        break;
                    case VK_RETURN:{
                        int MilisecondsOfClicks = _ttoi(StringValueMilisecondsOfClick.c_str());
                        if (MilisecondsOfClicks < MIN_MILISECONDS || MilisecondsOfClicks > PP.MaxMiliSeconds) {
                            TCHAR ErrorText[100];
                            wsprintf(ErrorText, _TEXT("Введённое вами число должно быть больше либо равно чем %u и меньше либо равно чем %u"), MIN_MILISECONDS, PP.MaxMiliSeconds);
                            MessageBox(PP.GlobalHWND, ErrorText, _TEXT("Ошибка! Неверное число"), MB_OK | MB_ICONERROR);
                            StringValueMilisecondsOfClick.clear();
                        }
                        else {
                            PP.GlobalMiliSecondsOfClicks = MilisecondsOfClicks;
                            UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                            PP.HookReadMilisecondsOfClicksIsRegistred = false;
                            StringValueMilisecondsOfClick.clear();
                            UpdateWindow(PP.GlobalHWND);
                        }
                        break;
                    }
                    
                }
                break;
        }
    }
}
hInst - HINSTANCE моего приложения.
Но этот код работает, но не совсем так как хотелось, например когда пользователь вводит 0, выводиться сообщение об ошибке(так и должно быть), но оно тут же закрывается как будто я нажал на Enter (так не должно быть).
Ещё раз моя последовательность действий:
1. Запускаю программу.
2. нажимаю Ctrl+Shift+Alt+Z
3. Ввожу 0.
4. Получаю сообщение об ошибке которое тут же закрывается, как будто я нажал на Enter(хотя так быть не должно, оно должно появиться и не исчезнуть, пока я что-нибудь не сделаю)
Я так подозреваю что проблема в KeyboardProc, возможно сообщения остаются в очереди, но такого быть не должно моя программа должна перехватывать клавиши, обрабатывать их нажатие, а остальные программы должны думать что нажатий вообще не было, то есть она должна их "съедать".
Вот ссылка на репозиторий на GitHub с полным проектом
Пожалуйста помогите. Проект создавался в Visual Studio 2017, написан на C++ и WinAPI.(смотреть в файл Autoclicker.cpp)

Добавлено через 2 часа 23 минуты
Первую проблему я решил, функция KeyboardProc должна иметь такой вид:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, LPARAM lParam) {
    static TSTRING StringValueMilisecondsOfClick;
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (code == HC_ACTION) {
        switch (wParam) {
            case WM_KEYDOWN:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                        StringValueMilisecondsOfClick += _TEXT('0');
                        return TRUE;
                    case 0x61:
                    case 0x31:
                        StringValueMilisecondsOfClick += _TEXT('1');
                        return TRUE;
                    case 0x62:
                    case 0x32:
                        StringValueMilisecondsOfClick += _TEXT('2');
                        return TRUE;
                    case 0x63:
                    case 0x33: 
                        StringValueMilisecondsOfClick += _TEXT('3');
                        return TRUE;
                    case 0x64:
                    case 0x34:
                        StringValueMilisecondsOfClick += _TEXT('4');
                        return TRUE;
                    case 0x65:
                    case 0x35: 
                        StringValueMilisecondsOfClick += _TEXT('5');
                        return TRUE;
                    case 0x66:
                    case 0x36:
                        StringValueMilisecondsOfClick += _TEXT('6');
                        return TRUE;
                    case 0x67:
                    case 0x37:
                        StringValueMilisecondsOfClick += _TEXT('7');
                        return TRUE;
                    case 0x68:
                    case 0x38: 
                        StringValueMilisecondsOfClick += _TEXT('8');
                        return TRUE;
                    case 0x69:
                    case 0x39:
                        StringValueMilisecondsOfClick += _TEXT('9');
                        return TRUE;
                    case VK_RETURN:{
                        int MilisecondsOfClicks = _ttoi(StringValueMilisecondsOfClick.c_str());
                        if (MilisecondsOfClicks < MIN_MILISECONDS || MilisecondsOfClicks > PP.MaxMiliSeconds) {
                            TCHAR ErrorText[100];
                            wsprintf(ErrorText, _TEXT("Введённое вами число должно быть больше либо равно чем %u и меньше либо равно чем %u"), MIN_MILISECONDS, PP.MaxMiliSeconds);
                            MessageBox(PP.GlobalHWND, ErrorText, _TEXT("Ошибка! Неверное число"), MB_OK | MB_ICONERROR);
                            StringValueMilisecondsOfClick.clear();
                        }
                        else {
                            PP.GlobalMiliSecondsOfClicks = MilisecondsOfClicks;
                            UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                            PP.HookReadMilisecondsOfClicksIsRegistred = false;
                            StringValueMilisecondsOfClick.clear();
                            UpdateWindow(PP.GlobalHWND);
                        }
                        return TRUE;
                    }
                    
                }
                break;
        }
    
    }
    else if (code > 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }
}
Но возникла другая проблема, вот в чём она заключается:
При нажатии Ctrl+Shift+Alt+Z, Ctrl+Alt+Shift остаётся нажатой даже после отжатия Ctrl+Shift+Alt+Z, как это проявляется, ну если нажать на иконку любого приложения на панели задач, то оно запускаеться от администратора, а если нажать Ctrl+Alt+Shift до первого действия, то эффект пропадает. Как это исправить?
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
03.11.2018, 18:48
Ответы с готовыми решениями:

Перехват нажатий клавиатуры
Добрый день! Мы вообще то пишем на java, но необходим кусочек кода на С++, который бы вызывал основную программу. Пока задача минимальна:...

Глобальный перехват события клавиатуры
Использую пример создания хука для глобального перехвата сообщений http://www.rsdn.ru/article/baseserv/hookdll.xml Пример рабочий...

Реализовать перехват событий клавиатуры (хуки)
Здравствуйте Уважаемые форумчане.Несколько дней бьюсь с проблемой,и решил попросить Вас помочь с ее решением. Проблема следующая:Я сделал...

3
 Аватар для blbulyan
12 / 10 / 3
Регистрация: 31.10.2017
Сообщений: 126
09.01.2019, 19:56  [ТС]
В ходе моих исследований я выяснил:
1. Клавиши залипают тогда, когда хук на перехват клавиатуры регистрируется в момент когда клавиши нажаты физически, ну то есть, если быстро нажать Ctrl+Alt+Shift+Z, то требуемый функционал срабатывает и клавиши не залипают, из этого можно сделать вывод, для того чтобы это исправить следует добавить проверку нажаты ли клавиши физически, если нажаты то ждать пока отпустят, если отпущены то продолжать.
Вот пример исправленного фрагмента кода(здесь представлена обработка горячей клавиши Ctrl+Alt+Shift+Z):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
case HotKeys::HotKeySetIntervalOfClicksId:
                    // данный участок кода предоствращает баг с залипанием клавиш Ctrl, Alt, Shift
                    LabelBeginForCheckKeyStates:
                    if (CheckBit(GetAsyncKeyState(VK_CONTROL), 16) == true || CheckBit(GetAsyncKeyState(VK_SHIFT), 16) == true || CheckBit(GetAsyncKeyState(VK_MENU), 16)) {// данное условие проверяет нажата какая-либо из клавиш Ctrl, Alt, Shift
                        Sleep(500);
                        goto LabelBeginForCheckKeyStates;
                    }
                    else {
                        goto LabelEndForCheckKeyStates;
                    }
                    LabelEndForCheckKeyStates:
                    if (PP.HookReadMilisecondsOfClicksIsRegistred == true) {
                        UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                        return (LRESULT)FALSE;
                    }
                    //конец данного участка
                    PP.hHookReadMiliSecondsOfClicks = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, hInst, NULL);
                    if (PP.hHookReadMiliSecondsOfClicks == NULL) {
                        DWORD RHKError = GetLastError();
                        LPTSTR BufferForFormatMessage = nullptr;
                        DWORD FMResult = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, RHKError, LANG_SYSTEM_DEFAULT, (LPTSTR)&BufferForFormatMessage, NULL, nullptr);
                        if (FMResult == 0) {
                            MessageBox(hWnd, _TEXT("Не удалось выполнить функцию SetWindowsHookEx!"), _TEXT("Ошибка при выполнении функции SetWindowsHookEx!"), MB_OK | MB_ICONERROR);
                            MessageBox(hWnd, _TEXT("Ошибка! Не удалось узнать причину возникновения ошибки при выполнении функции SetWindowsHookEx!"), _TEXT("Мне не удалось причину возникновения ошибки!"), MB_OK | MB_ICONERROR);
                                    
                        }
                        else {
                            MessageBox(hWnd, BufferForFormatMessage, _TEXT("Не удалось выполнить функцию SetWindowsHookEx!"), MB_OK | MB_ICONERROR);
                        }
                        if (LocalFree((HLOCAL)BufferForFormatMessage) != 0) {
                            MessageBox(hWnd, _TEXT("Не удалось освободить буфуер при обработке ошибки выполнения функции SetWindowsHookEx!"), _TEXT("Ошибка!"), MB_OK | MB_ICONERROR);
                        }
                    }
                    else {
                        
                    }
                    return (LRESULT)TRUE;
2. Баг с закрытием MessageBox при неверном вводе числа я таки не исправил, для того чтобы его исправить нужно перестроить логику KeyboardProc, в дальнейшем я приведу пример с исправлеными багами.
Почему же MessageBox закрывался сам? Да всё просто, если проверить, то он закрывается только тогда, когда вы отпускаете клавишу Enter, в KeyboardProc я перехватывал только нажатие клавиш, в том числе и Enter, если же перехватывать оба сообщения и перестроить логику таким образом, чтобы основные действия выполнялись когда пользователь отпускает клавишу, то ошибка пропадает, вот пример исправленной KeyboardProc:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, _In_ LPARAM lParam) {// функция для перехвата клавиатуры, перехват будет происходить при зажатии Ctrl+Alt+Shift+Z, будут перехватываться клавиши с цифрами 0-9, это нужно для того чтобы ввести новый интервал кликов не открывая настройки автокликера
    static TSTRING StringValueMilisecondsOfClick;
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (code == HC_ACTION) {
        switch (wParam) {
            case WM_KEYUP:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                        StringValueMilisecondsOfClick += _TEXT('0');
                        return TRUE;
                    case 0x61:
                    case 0x31:
                        StringValueMilisecondsOfClick += _TEXT('1');
                        return TRUE;
                    case 0x62:
                    case 0x32:
                        StringValueMilisecondsOfClick += _TEXT('2');
                        return TRUE;
                    case 0x63:
                    case 0x33: 
                        StringValueMilisecondsOfClick += _TEXT('3');
                        return TRUE;
                    case 0x64:
                    case 0x34:
                        StringValueMilisecondsOfClick += _TEXT('4');
                        return TRUE;
                    case 0x65:
                    case 0x35: 
                        StringValueMilisecondsOfClick += _TEXT('5');
                        return TRUE;
                    case 0x66:
                    case 0x36:
                        StringValueMilisecondsOfClick += _TEXT('6');
                        return TRUE;
                    case 0x67:
                    case 0x37:
                        StringValueMilisecondsOfClick += _TEXT('7');
                        return TRUE;
                    case 0x68:
                    case 0x38: 
                        StringValueMilisecondsOfClick += _TEXT('8');
                        return TRUE;
                    case 0x69:
                    case 0x39:
                        StringValueMilisecondsOfClick += _TEXT('9');
                        return TRUE;
                    case VK_BACK:
                        if (StringValueMilisecondsOfClick.size() > 0) {
                            StringValueMilisecondsOfClick.pop_back();
                        }
                        return TRUE;
                    case VK_RETURN:{
                        int MilisecondsOfClicks = _ttoi(StringValueMilisecondsOfClick.c_str());
                        if (MilisecondsOfClicks < MIN_MILISECONDS || MilisecondsOfClicks > PP.MaxMiliSeconds) {
                            TCHAR ErrorText[100];
                            wsprintf(ErrorText, _TEXT("Введённое вами число должно быть больше либо равно чем %u и меньше либо равно чем %u"), MIN_MILISECONDS, PP.MaxMiliSeconds);
                            MessageBox(PP.GlobalHWND, ErrorText, _TEXT("Ошибка! Неверное число"), MB_OK | MB_ICONERROR);
                            StringValueMilisecondsOfClick.clear();
                        }
                        else {
                            UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                            PP.GlobalMiliSecondsOfClicks = MilisecondsOfClicks;
                            PP.HookReadMilisecondsOfClicksIsRegistred = false;
                            StringValueMilisecondsOfClick.clear();
                            InvalidateRect(PP.GlobalHWND, NULL, TRUE);
                            
                        }
                        return TRUE;
                    }
                    default:
                        return CallNextHookEx(NULL, code, wParam, lParam);
                }
                break;
            case WM_KEYDOWN:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                    case 0x61:
                    case 0x31:
                    case 0x62:
                    case 0x32:
                    case 0x63:
                    case 0x33:
                    case 0x64:
                    case 0x34:
                    case 0x65:
                    case 0x35:
                    case 0x66:
                    case 0x36:
                    case 0x67:
                    case 0x37:
                    case 0x68:
                    case 0x38:
                    case 0x69:
                    case 0x39:
                    case VK_BACK:
                    case VK_RETURN:
                        return TRUE;
                }
                break;
            default:
                return CallNextHookEx(NULL, code, wParam, lParam);
        }
    }
    else if (code > 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }
}
0
134 / 26 / 8
Регистрация: 09.02.2017
Сообщений: 175
10.01.2019, 02:52
Вы исправили свои проблемы или еще что-то осталось?
0
 Аватар для blbulyan
12 / 10 / 3
Регистрация: 31.10.2017
Сообщений: 126
15.01.2019, 19:54  [ТС]
Всё что было я исправил. Спасибо за ваш интерес к этой теме.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
15.01.2019, 19:54
Помогаю со студенческими работами здесь

Реализовать функцию на win32api
void CreateM() // Опис функції що створює повідомлення ресурсу пам'яті { BOOL bResLow; BOOL bResHigh; HANDLE hObjectLow =...

Нужна помощь Win32API
Помогите,плиз. Нужно сделать так, чтобы символ вводимый с клавиатуры , отображался в созданном окне. Окно создал а вот с событием WM_CHAR...

Приложение Win32api не работает на других пк
Есть приложение созданное с применением только Win32api . Не могу понять почему не работает на других пк? Выдает такую ошибку: ...

Реализация Docking Window Win32Api
Помогите разобраться в реализации Не знаю даже с чего начать. В интернете информации на данную тему очень мало ((((

Копирование текста в переменную win32api
Здравствуйте у меня есть диалог который должен считать количество переменных) Загружайте картинки на форум. помогите как и с...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
4
Ответ Создать тему
Новые блоги и статьи
Воспроизведение звукового файла с помощью SDL3_mixer при касании экрана Android
8Observer8 26.01.2026
Содержание блога SDL3_mixer - это библиотека я для воспроизведения аудио. В отличие от инструкции по добавлению текста код по проигрыванию звука уже содержится в шаблоне примера. Нужно только. . .
Установка Android SDK, NDK, JDK, CMake и т.д.
8Observer8 25.01.2026
Содержание блога Перейдите по ссылке: https:/ / developer. android. com/ studio и в самом низу страницы кликните по архиву "commandlinetools-win-xxxxxx_latest. zip" Извлеките архив и вы увидите. . .
Вывод текста со шрифтом TTF на Android с помощью библиотеки SDL3_ttf
8Observer8 25.01.2026
Содержание блога Если у вас не установлены Android SDK, NDK, JDK, и т. д. то сделайте это по следующей инструкции: Установка Android SDK, NDK, JDK, CMake и т. д. Сборка примера Скачайте. . .
Использование SDL3-callbacks вместо функции main() на Android, Desktop и WebAssembly
8Observer8 24.01.2026
Содержание блога Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples, то вы увидите, что все примеры используют следующие четыре обязательные функции, а. . .
моя боль
iceja 24.01.2026
Выложила интерполяцию кубическими сплайнами www. iceja. net REST сервисы временно не работают, только через Web. Написала за 56 рабочих часов этот сайт с нуля. При помощи perplexity. ai PRO , при. . .
Модель сукцессии микоризы
anaschu 24.01.2026
Решили писать научную статью с неким РОманом
http://iceja.net/ математические сервисы
iceja 20.01.2026
Обновила свой сайт http:/ / iceja. net/ , приделала Fast Fourier Transform экстраполяцию сигналов. Однако предсказывает далеко не каждый сигнал (см ограничения http:/ / iceja. net/ fourier/ docs ). Также. . .
http://iceja.net/ сервер решения полиномов
iceja 18.01.2026
Выкатила http:/ / iceja. net/ сервер решения полиномов (находит действительные корни полиномов методом Штурма). На сайте документация по API, но скажу прямо VPS слабенький и 200 000 полиномов. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru