Программирование на Си в MS-DOS

Всё, что касается программирования на старых языках или для старых систем

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 01 авг 2026, 16:23

Да это-то надо адаптировать... ;) По идее, это должно быть процедурой коммандной строки, вызываемой из основной программы.
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение luzga » 01 авг 2026, 17:45

Prizrak писал(а):Да это-то надо адаптировать

Программа получает параметры из командной строки.
Запуск программы без параметров, выведет подсказку.

Пример запуска:
POLYPHON /A 1 440 180 /B 2 554 180
Изображение
 Развернуть: Main.cpp
Код: Выделить всё
// Main.cpp
//
// Программа реализует двухканальный псевдополифонический бипер, два
// логических процесса или голоса, играющих разные ноты одновременно
// за счет перехвата системного таймера Irq 0, Int 8.
// Звук воспроизведения через динамик ПК (PC Speaker).
//
// Программа получает параметры из командной строки.
// Пример запуска:
// POLYPHON /A 1 440 180 /B 2 554 180

#include <dos.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>

// Старый вектор прерывания таймера.
void interrupt (*old_timer)(...);
// Структура для процесса, голоса воспроизведения.
typedef struct {
unsigned long duration;   // Длительность в тиках таймера.
unsigned frequency;      // Частота звука Гц.
unsigned long counter;   // Текущий счетчик смены фазы.
int active;         // Флаг активности канала.
} Voice;

Voice voice1 = {0, 0, 0, 0};
Voice voice2 = {0, 0, 0, 0};

// Текущая, выводимая частота на динамик.
unsigned current_freq;
int toggle_channel;

void interrupt new_timer(...)
{
   // Функция, устанавливает новое прерывание таймера,
   // вызывается 18.2 раза в секунду или чаще при перенастройке.

   // Вызвать оригинальный, системный обработчик DOS,
   // чтобы часы не отставали.
   old_timer();

   // Чередование каналов.
   if (voice1.active && voice2.active) {
      toggle_channel = !toggle_channel;
      current_freq = toggle_channel ? voice1.frequency : voice2.frequency;
   }
   else if (voice1.active)
      current_freq = voice1.frequency;
   else if (voice2.active)
      current_freq = voice2.frequency;
   else
      current_freq = 0;

   // Управление динамиком.
   if (current_freq > 0)
   {
      unsigned div = 1193180L / current_freq;
      outportb(0x43, 0xB6); // Канал 2, режим 3, меандр.
      outportb(0x42, div & 0xFF);
      outportb(0x42, div >> 8);

      // Включить динамик, биты 0 и 1 порта 0x61
      outportb(0x61, inportb(0x61) | 3);
   }
   else
   {
      // Выключить динамик.
      outportb(0x61, inportb(0x61) & 0xFC);
   }

   // Уменьшить счетчики длительности.
   if (voice1.active)
   {
      if (--voice1.duration == 0)
         voice1.active = 0;
   }
   if (voice2.active)
   {
      if (--voice2.duration == 0)
         voice2.active = 0;
   }
}

void play_voice(int channel, unsigned freq, unsigned long dur)
{
   // Функция запуска ноты
   // в первом или втором процессе...

   disable(); // Запретить прерывания при изменении общей структуры.
   if (1 == channel)
   {
      voice1.frequency = freq;
      voice1.duration = dur;
      voice1.active = 1;
   }
   else
   {
      voice2.frequency = freq;
      voice2.duration = dur;
      voice2.active = 1;
   }
   enable();
}

const char* MakeAppname(char* argv) {
   static char Exename[128];
   if (!Exename[0]) {
      strcpy(Exename,argv);
      char* svPath=Exename;
      char* svName=Exename;
      do {
         if ('\\'==*svPath||'/'==*svPath)
            svName=svPath+1;
      } while (*svPath++);
      strcpy(Exename,svName);
      if (strchr(Exename,'.'))
         *strchr(Exename,'.')=0;
   } return Exename;
}
void Examples(char* argv) {
   printf("%s /A 1 440 180 /B 2 554 180\n", MakeAppname(argv));
   exit(0);
}
void Usage(char* argv) {
   printf(" Usage: %s [/A 1 440 180] [/B 2 554 180]\n",
   MakeAppname(argv));
   puts(" [/EXAMPLES]");
   exit(0);
}

int main(int argc, char* argv[]) {
   if (argc<2)
      Usage(argv[0]);
   int a1, b1, c1;
   int a2, b2, c2;
   a1 = b1 = c1 = 0;
   a2 = b2 = c2 = 0;
   for (int x=1; x<argc; ++x) {
      if ('/' == argv[x][0] ||
         '-' == argv[x][0]) {
         if ('A' == argv[x][1] ||
            'a' == argv[x][1]) {
            if (x + 3 < argc) {
               a1 = atoi(argv[x + 1]);
               b1 = atoi(argv[x + 2]);
               c1 = atoi(argv[x + 3]);
               x += 3;
            } else {
               printf("Missing argument for /A\n");
               Usage(argv[0]);
               return 0;
            }
         } else if ('B' == argv[x][1] ||
            'b' == argv[x][1]) {
            if (x + 3 < argc) {
               a2 = atoi(argv[x + 1]);
               b2 = atoi(argv[x + 2]);
               c2 = atoi(argv[x + 3]);
               x += 3;
            } else {
               printf("Missing argument for /B\n");
               Usage(argv[0]);
               return 0;
            }
         } else if (0 == strcmp(argv[x], "/EXAMPLES")) {
            Examples(argv[0]);
            return 0;
         } else {
            printf("Bad option: \"%s\"\n",argv[x]);
            Usage(argv[0]);
            return 0;
         }
      } else {
         printf("Bad argument: \"%s\"\n",argv[x]);
         Usage(argv[0]);
         return 0;
      }
   }

   clrscr();
   printf("PC Speaker Ї®«Ёд®­Ёп. „«п ўл室 , ­ ¦¬ЁвҐ «оЎго Є« ўЁиг...\n");

   // Сохранить старый и установить новый
   // обработчик прерывания таймера.
   old_timer = getvect(8);
   setvect(8, new_timer);

   // Параллельный запуск двух разных частот полифония.

   // Голос 1
   play_voice(a1, b1, c1);
   //play_voice(1, 440, 180); // 440 Гц, нота Ля, Играть 10 секунд, 18 тиков = 1 сек.
   
   // Голос 2
   play_voice(a2, b2, c2);
   //play_voice(2, 554, 180); // 554 Гц, нота До-диез

   // Ожидание нажатия клавиши...
   while (!kbhit())
   {
      // Цикл программы...
   }
   getch();

   // Восстановить системный таймер и выключить звук.
   setvect(8, old_timer);
   outportb(0x61, inportb(0x61) & 0xFC);

   return 0;
}
Вложения
Polyphon.zip
(22.72 Кб) Скачиваний: 6
Последний раз редактировалось luzga 02 авг 2026, 06:04, всего редактировалось 2 раз(а).
Аватара пользователя
luzga
Мастер Даунгрейда
 
Сообщения: 478
Зарегистрирован: 04 сен 2025, 19:35

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 01 авг 2026, 23:01

Спасибо! У меня под DosBox'ом, программа пиликает до нажатия Enter, а не по длительности нот - поэтому не удается понять, какая у них длительность и реально ли одновременно они играются. Это проблема DosBox'а или программы? :geek:
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение luzga » 02 авг 2026, 06:01

В том, что одновременно играются - да. Я менял частоту и это было слышно.
1 - номер канала
440 - частота
180 - время игры, 10 секунд (18 тиков = 1 сек.)
Всё остальное мне непонятно, это уже вам разбираться.
Можете модифицировать подсказку, чтобы выводился пример с обозначением параметров. И ещё, можно заменить функцию strcmp() на strcmpi(), чтобы регистр букв не проверялся при сравнивании, а то при вводе строчными, программа говорит вы, что-то не то вводите.
Также, если Borland выдаёт предупреждение о несоответствии переменных, можно сделать так
unsigned div = (unsigned)(1193180L / current_freq);
А для информативности, можно добавить, пояснения:
// Голос 1
play_voice(
a1,// канал
b1,// частота
c1 // время игры
);

В общем, вот, прикладываю один текст
 Развернуть: Main.cpp
Код: Выделить всё
// Main.cpp
//
// Программа реализует двухканальный псевдополифонический бипер, два
// логических процесса или голоса, играющих разные ноты одновременно
// за счет перехвата системного таймера Irq 0, Int 8.
// Звук воспроизведения через динамик ПК (PC Speaker).
//
// Программа получает параметры из командной строки.
// Пример запуска:
// POLYPHON /A 1 440 180 /B 2 554 180

#include <dos.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>

// Старый вектор прерывания таймера.
void interrupt (*old_timer)(...);
// Структура для процесса, голоса воспроизведения.
typedef struct {
unsigned long duration;   // Длительность в тиках таймера.
unsigned long frequency;// Частота звука Гц.
unsigned long counter;// Текущий счетчик смены фазы.
int active;   // Флаг активности канала.
} Voice;

Voice voice1 = {0, 0, 0, 0};
Voice voice2 = {0, 0, 0, 0};

// Текущая, выводимая частота на динамик.
unsigned long current_freq;
int toggle_channel;

void interrupt new_timer(...)
{
   // Функция, устанавливает новое прерывание таймера,
   // вызывается 18.2 раза в секунду или чаще при перенастройке.

   // Вызвать оригинальный, системный обработчик DOS,
   // чтобы часы не отставали.
   old_timer();

   // Чередование каналов.
   if (voice1.active && voice2.active) {
      toggle_channel = !toggle_channel;
      current_freq = toggle_channel ? voice1.frequency : voice2.frequency;
   }
   else if (voice1.active)
      current_freq = voice1.frequency;
   else if (voice2.active)
      current_freq = voice2.frequency;
   else
      current_freq = 0;

   // Управление динамиком.
   if (current_freq > 0)
   {
      unsigned div = (unsigned)(1193180L / current_freq);
      outportb(0x43, 0xB6); // Канал 2, режим 3, меандр.
      outportb(0x42, div & 0xFF);
      outportb(0x42, div >> 8);

      // Включить динамик, биты 0 и 1 порта 0x61
      outportb(0x61, inportb(0x61) | 3);
   }
   else
   {
      // Выключить динамик.
      outportb(0x61, inportb(0x61) & 0xFC);
   }

   // Уменьшить счетчики длительности.
   if (voice1.active)
   {
      if (--voice1.duration == 0)
         voice1.active = 0;
   }
   if (voice2.active)
   {
      if (--voice2.duration == 0)
         voice2.active = 0;
   }
}

void play_voice(int channel, unsigned long freq, unsigned long dur)
{
   // Функция запуска ноты
   // в первом или втором процессе...

   disable(); // Запретить прерывания при изменении общей структуры.
   if (1 == channel)
   {
      voice1.frequency = freq;
      voice1.duration = dur;
      voice1.active = 1;
   }
   else
   {
      voice2.frequency = freq;
      voice2.duration = dur;
      voice2.active = 1;
   }
   enable();
}

const char* MakeAppname(char* argv) {
   static char Exename[128];
   if (!Exename[0]) {
      strcpy(Exename,argv);
      char* svPath=Exename;
      char* svName=Exename;
      do {
         if ('\\'==*svPath||'/'==*svPath)
            svName=svPath+1;
      } while (*svPath++);
      strcpy(Exename,svName);
      if (strchr(Exename,'.'))
         *strchr(Exename,'.')=0;
   } return Exename;
}
void Examples(char* argv) {
   printf("%s /A 1 440 180 /B 2 554 180\n", MakeAppname(argv));
   exit(0);
}
void Usage(char* argv) {
   printf(" Usage: %s [/A 1 440 180] [/B 2 554 180]\n",
      MakeAppname(argv));
   printf(" [/EXAMPLES]\n");
   exit(0);
}

int main(int argc, char* argv[]) {
   if (argc<2)
      Usage(argv[0]);
   int a1, b1, c1;
   int a2, b2, c2;
   a1 = b1 = c1 = 0;
   a2 = b2 = c2 = 0;
   for (int x=1; x<argc; ++x) {
      if ('/' == argv[x][0] ||
         '-' == argv[x][0]) {
         if ('A' == argv[x][1] ||
            'a' == argv[x][1]) {
            if (x + 3 < argc) {
               a1 = atoi(argv[x + 1]);
               b1 = atoi(argv[x + 2]);
               c1 = atoi(argv[x + 3]);
               x += 3;
            } else {
               printf("Missing argument for /A\n");
               Usage(argv[0]);
               return 0;
            }
         } else if ('B' == argv[x][1] ||
            'b' == argv[x][1]) {
            if (x + 3 < argc) {
               a2 = atoi(argv[x + 1]);
               b2 = atoi(argv[x + 2]);
               c2 = atoi(argv[x + 3]);
               x += 3;
            } else {
               printf("Missing argument for /B\n");
               Usage(argv[0]);
               return 0;
            }
         } else if (0 == strcmpi(argv[x], "/EXAMPLES")) {
            Examples(argv[0]);
            return 0;
         } else {
            printf("Bad option: \"%s\"\n",argv[x]);
            Usage(argv[0]);
            return 0;
         }
      } else {
         printf("Bad argument: \"%s\"\n",argv[x]);
         Usage(argv[0]);
         return 0;
      }
   }

   clrscr();
   printf("PC Speaker Ї®«Ёд®­Ёп. „«п ўл室 , ­ ¦¬ЁвҐ «оЎго Є« ўЁиг...\n");

   // Сохранить старый и установить новый
   // обработчик прерывания таймера.
   old_timer = getvect(8);
   setvect(8, new_timer);

   // Параллельный запуск двух разных частот полифонии.

   // Голос 1
   play_voice(
      a1,// канал
      b1,// частота
      c1 // время игры
      );
   //play_voice(1, 440, 180); // 1 канал, 440 Гц, нота Ля, Играть 10 секунд, 18 тиков = 1 сек.
   
   // Голос 2
   play_voice(a2, b2, c2);
   //play_voice(2, 554, 180); // 2 канал, 554 Гц, нота До-диез, Играть 10 секунд

   // Ожидание нажатия клавиши...
   while (!kbhit())
   {
      // Цикл программы...
   }
   getch();

   // Восстановить системный таймер и выключить звук.
   setvect(8, old_timer);
   outportb(0x61, inportb(0x61) & 0xFC);

   return 0;
}
Последний раз редактировалось luzga 02 авг 2026, 06:02, всего редактировалось 1 раз.
Аватара пользователя
luzga
Мастер Даунгрейда
 
Сообщения: 478
Зарегистрирован: 04 сен 2025, 19:35

Re: Программирование на Си в MS-DOS

Сообщение inc1001 » 02 авг 2026, 08:21

Prizrak писал(а):...не удается понять, какая у них длительность и реально ли одновременно они играются.

В архитектуре x86 для генерации звука спикера используется канал 2 системного таймера с одним выходным сигналом. Полифония на спикере аппаратно не поддерживается.

Псевдополифония в предложенной luzga программе воспроизводит ноты попеременно, перепрограммируя частоту канала 2 каждые 1/18,2 сек.
Как это воспринимается на слух человеком? Можно сравнить, если увеличить число "одновременно" воспроизводимых нот до 4 или более.
luzga писал(а):Программа реализует двухканальный псевдополифонический бипер, два логических процесса или голоса, играющих разные ноты одновременно за счет перехвата системного таймера Irq 0, Int 8.

Алгоритм интересный, если ограничиться двумя нотами. Так то int 8 всегда вызывает пустое пользовательское прерывание 1Ch (можно изменение частоты звука перенести в него, как вариант).
inc1001
Даунгрейдер
 
Сообщения: 6
Зарегистрирован: 02 фев 2025, 21:36

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 02 авг 2026, 11:35

Понятно, процедуру можно пока отложить - потом надо будет разбираться уже на профессиональном уровне с кодом C и править под свои нужды... ;) Мне, например, не нужна выдача сообщения при исполнении каждого аккорда, и нужны другие длительности нот. Кроме того, аккорд в произведении, может доходить запросто хотя бы до тетра-аккорда...
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 07 авг 2026, 13:37

Кстати, уважаемый luzga, объясните пожалуйста, сколько звуков в C позволено проигрывать адекватно одновременно, с чисто технической точки зрения? Партитуры-то бывают довольно толстые, вплоть до симфонического оркестра... ;)

PS. Или расскажите кто-нибудь другой, коллеги, раз уж так вышло... :oops:
Последний раз редактировалось Prizrak 07 авг 2026, 13:41, всего редактировалось 1 раз.
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение september2489 » 07 авг 2026, 14:10

Prizrak писал(а):сколько звуков в C позволено проигрывать адекватно одновременно, с чисто технической точки зрения

Не знаю насчёт С, но обычно в аккорде различают 3-6 звуков. Если аккорд играется арпеджио (последовательно), то можно различить 6-12 звуков (в зависимости от темпа). Вот и думайте, нужно ли вам одновременно более 5 звуков играть. С другой стороны, если вы хотите какой-то сложный тембр получить, то наверно это имеет смысл.
Аватара пользователя
september2489
Мастер Даунгрейда
 
Сообщения: 476
Зарегистрирован: 16 дек 2024, 21:52
Железо: Lenovo ThinkPad T60p, Asus A9Rp

Re: Программирование на Си в MS-DOS

Сообщение uav » 07 авг 2026, 14:31

Prizrak, PC Speaker - монофоническое, можно сказать, вообще двоичное устройство. Грубо говоря, на него подаётся либо 0, либо 1, нет никакой регулировки громкости, нельзя подать сигнал заданной амплитуды. Можно просто включать и выключать его с заданной частотой.
Соответственно, если Вам нужно что-то отличное от синусоиды, то нужно использовать широтно-импульсную модуляцию. Тут вот разъясняется, например:
https://habr.com/ru/companies/ruvds/articles/951514/
Аватара пользователя
uav
Мастер Даунгрейда
 
Сообщения: 3236
Зарегистрирован: 22 дек 2008, 14:21

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 07 авг 2026, 14:47

september2489 писал(а):
Prizrak писал(а):сколько звуков в C позволено проигрывать адекватно одновременно, с чисто технической точки зрения

Не знаю насчёт С, но обычно в аккорде различают 3-6 звуков. Если аккорд играется арпеджио (последовательно), то можно различить 6-12 звуков (в зависимости от темпа). Вот и думайте, нужно ли вам одновременно более 5 звуков играть. С другой стороны, если вы хотите какой-то сложный тембр получить, то наверно это имеет смысл.

Да не, подождите, аккорды-то там уже позволены - даже всякие брассы и пицикато, в принципе, играются последовательно. Я имею в виду "оркестровку", т.е. имитацию одновременного синхронного исполнения партитуры ансамблем или оркестром. Ансамбли-то обычно маленькие, а вот оркестры бывают и большие. Заодно можно клавишные имитировать, где действительно можно взять одновременный аккорд, хоть в 4 руки. Смычковые-то уже есть, после позволения суб-тактов.
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 07 авг 2026, 14:56

uav писал(а):Prizrak, PC Speaker - монофоническое, можно сказать, вообще двоичное устройство. Грубо говоря, на него подаётся либо 0, либо 1, нет никакой регулировки громкости, нельзя подать сигнал заданной амплитуды. Можно просто включать и выключать его с заданной частотой.
Соответственно, если Вам нужно что-то отличное от синусоиды, то нужно использовать широтно-импульсную модуляцию. Тут вот разъясняется, например:
https://habr.com/ru/companies/ruvds/articles/951514/

Так, а что это должно означать на практике? Насколько я понимаю, параллельный звук на спикере имитируется каким-то сложным переходом от одной частоты к другой за кратчайший промежуток времени. Меня интересует, собственно, сколько отдельных звуков так можно взять без ущерба для звучания конкретно на C. Паскаль-то и того не может, чего может эта программка, имитирующая сигнал старого телефона...
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение uav » 07 авг 2026, 15:00

Prizrak, так в C для этого вообще нет инструментов нормальных. Там только sound(частота) и nosound. Так можно только одну ноту одновременно играть.
А для большего нужно напрямую программировать оборудование. Что, собственно, в программе luzga и делается. Но это уже и не C как таковой.
Последний раз редактировалось uav 07 авг 2026, 15:02, всего редактировалось 1 раз.
Аватара пользователя
uav
Мастер Даунгрейда
 
Сообщения: 3236
Зарегистрирован: 22 дек 2008, 14:21

Re: Программирование на Си в MS-DOS

Сообщение IBMForever » 07 авг 2026, 15:08

Prizrak писал(а):Спасибо! У меня под DosBox'ом, программа пиликает до нажатия Enter, а не по длительности нот - поэтому не удается понять, какая у них длительность и реально ли одновременно они играются. Это проблема DosBox'а или программы? :geek:

Лично мне удобнее запускать Turbo C v2.01 и через него же в файлах запускать скомпилированный exe через Dos shell.
Хотя под семёрку иногда ругается, что полный экран не поддерживается.
Аватара пользователя
IBMForever
Мастер Даунгрейда
 
Сообщения: 301
Зарегистрирован: 24 авг 2022, 00:23
Железо: IBM PC 286 AT

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 07 авг 2026, 15:13

uav писал(а):Prizrak, так в C для этого вообще нет инструментов нормальных. Там только sound(частота) и nosound. Так можно только одну ноту одновременно играть.
А для большего нужно напрямую программировать оборудование. Что, собственно, в программе luzga и делается. Но это уже и не C как таковой.

Вы имеете в виду Паскаль? Там-то именно sound и nosound... В той программе есть прямые обращения к API спикера?
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Re: Программирование на Си в MS-DOS

Сообщение Prizrak » 07 авг 2026, 15:31

IBMForever писал(а):
Prizrak писал(а):Спасибо! У меня под DosBox'ом, программа пиликает до нажатия Enter, а не по длительности нот - поэтому не удается понять, какая у них длительность и реально ли одновременно они играются. Это проблема DosBox'а или программы? :geek:

Лично мне удобнее запускать Turbo C v2.01 и через него же в файлах запускать скомпилированный exe через Dos shell.
Хотя под семёрку иногда ругается, что полный экран не поддерживается.

У меня-то DosBox на Ubuntu и на виртуалке WinXPx32-Coding, где всякие средства разработки. Хост-системы 64-битные, но иногда проверяю DOS-программы и на виртуалках под 32-битными системами.
Аватара пользователя
Prizrak
Мастер Даунгрейда
 
Сообщения: 275
Зарегистрирован: 09 окт 2024, 19:20
Откуда: Минск
Железо: Ноутбук HP, планшетник Android

Пред.След.

Вернуться в Программирование

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1