На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Обратите внимание:
1. Прежде чем начать новую тему или отправить сообщение, убедитесь, что вы не нарушаете правил форума!
2. Обязательно воспользуйтесь поиском. Возможно, Ваш вопрос уже обсуждали. Полезные ссылки приведены ниже.
3. Темы с просьбой выполнить какую-либо работу за автора в этом разделе не обсуждаются.
4. Используйте теги [ code=cpp ] ...текст программы... [ /code ] для выделения текста программы подсветкой.
5. Помните, здесь телепатов нет. Старайтесь формулировать свой вопрос максимально грамотно и чётко: Как правильно задавать вопросы
6. Запрещено отвечать в темы месячной и более давности без веских на то причин.

Полезные ссылки:
user posted image FAQ Сайта (C++) user posted image FAQ Форума user posted image Наши Исходники user posted image Поиск по Разделу user posted image MSDN Library Online (Windows Driver Kit) user posted image Google

Ваше мнение о модераторах: user posted image B.V.
Модераторы: B.V.
Страницы: (4) [1] 2 3 ... Последняя » все  ( Перейти к последнему сообщению )  
> Как программно выполнить команду консольного приложения? , Win7, XE7
    Посылаю команду консольному приложению, запущенного CreateProcess, которая выполняется только после закрытия хэндла = закрытия этого приложения. Вот рабочий пример (один из нашедшихся в сети):
    ExpandedWrap disabled
        HANDLE hReadIn, hWriteIn, hReadOut, hWriteOut, hWriteInDup, hWriteOutDup;
       
        SECURITY_ATTRIBUTES sa;
        PROCESS_INFORMATION pi;
        STARTUPINFO si;
       
          ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
          sa.nLength = sizeof(SECURITY_ATTRIBUTES);
          sa.bInheritHandle = TRUE;
          sa.lpSecurityDescriptor = NULL;
       
          CreatePipe(&hReadOut, &hWriteOut, &sa, NULL);
          DuplicateHandle(GetCurrentProcess(), hReadOut, GetCurrentProcess(), &hWriteOutDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
          CloseHandle(hReadOut);
       
          CreatePipe(&hReadIn, &hWriteIn, &sa, NULL);
          DuplicateHandle(GetCurrentProcess(), hWriteIn, GetCurrentProcess(), &hWriteInDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
          CloseHandle(hWriteIn);
       
          ZeroMemory(&si, sizeof(STARTUPINFO));
          si.cb=sizeof(STARTUPINFO);
          si.dwFlags     = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
          si.wShowWindow = SW_HIDE;
          si.hStdInput   = hReadIn;
          si.hStdOutput  = hWriteOut;
       
          CreateProcess(NULL, Cons_App.c_str(), NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
       
          WriteFile(hWriteInDup, buf, len, &count, NULL);   //  запись команды
       
          CloseHandle(hWriteInDup);           // команда выполняется только после CloseHandle = закрытия консольного приложения
       
          CloseHandle(hReadOut);
          CloseHandle(hWriteIn);
          CloseHandle(pi.hThread);
          CloseHandle(pi.hProcess);

    Как выполнять команды и читать из командной строки консольного приложения без закрытия самого приложения?
    Спасибо.
    Сообщение отредактировано: vlad2 -
      Цитата vlad2 @
      Посылаю команду консольному приложению, запущенного ...

      А каким образом консольное приложение читает команду ?

      "Как выполнять команды и читать из командной строки консольного приложения без закрытия самого приложения?"
      Интересен также вопрос, по какой причине ты сделал такой вывод..
      Если посмотреть твой исходник:
      ExpandedWrap disabled
          
        ...
            ZeroMemory(&si, sizeof(STARTUPINFO));
            si.cb=sizeof(STARTUPINFO);
            si.dwFlags     = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
            si.wShowWindow = SW_HIDE;
            si.hStdInput   = hReadIn;
            si.hStdOutput  = hWriteOut;
         
            CreateProcess(NULL, Cons_App.c_str(), NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
         
            WriteFile(hWriteInDup, buf, len, &count, NULL);   //  запись команды
         
            CloseHandle(hWriteInDup);           // команда выполняется только после CloseHandle = закрытия консольного приложения
        ...

      то все операции непрерывно следуют одна за другой.
      После WriteFile данные консольным приложением не успевают
      приняться до CloseHandle(hWriteInDup);
      попробуй:
      ExpandedWrap disabled
            WriteFile(hWriteInDup, buf, len, &count, NULL);   //  запись команды
            ::Sleep(10000);// 10 секунд
            CloseHandle(hWriteInDup);           // команда выполняется только после CloseHandle = закрытия консольного приложения
      Сообщение отредактировано: ЫукпШ -
        Цитата ЫукпШ @
        А каким образом консольное приложение читает команду ?

        Первый раз делаю такое приложение, которое работает с консольным. Думаю, что команда должна выполниться после WriteFile. Однако, не получается - ни с ожиданием, никак.
        Выполняется только во время CloseHandle.
        Команда - создание файла, который появляется только после CloseHandle.
          Цитата vlad2 @
          Цитата ЫукпШ @
          А каким образом консольное приложение читает команду ?

          Первый раз делаю такое приложение, которое работает с консольным.

          я задал этот вопрос чтобы взглянуть на исходник. :)
            Закешировалось?
              Цитата ЫукпШ @
              я задал этот вопрос чтобы взглянуть на исходник.

              Вот конкретный работающий код:
              ExpandedWrap disabled
                  UnicodeString Cons_App = L"Python";
                  HANDLE hReadIn, hWriteIn, hReadOut, hWriteOut, hWriteInDup, hWriteOutDup;
                 
                  SECURITY_ATTRIBUTES sa;
                  PROCESS_INFORMATION pi;
                  STARTUPINFO si;
                  try
                  {
                    SetCurrentDir(CurDir);
                 
                    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
                    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
                    sa.bInheritHandle = TRUE;
                    sa.lpSecurityDescriptor = NULL;
                 
                    CreatePipe(&hReadOut, &hWriteOut, &sa, NULL);
                    DuplicateHandle(GetCurrentProcess(), hReadOut, GetCurrentProcess(), &hWriteOutDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
                    CloseHandle(hReadOut);
                 
                    CreatePipe(&hReadIn, &hWriteIn, &sa, NULL);
                    DuplicateHandle(GetCurrentProcess(), hWriteIn, GetCurrentProcess(), &hWriteInDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
                    CloseHandle(hWriteIn);
                 
                    ZeroMemory(&si, sizeof(STARTUPINFO));
                    si.cb=sizeof(STARTUPINFO);
                    si.dwFlags     = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
                    si.wShowWindow = SW_HIDE;
                    si.hStdInput   = hReadIn;
                    si.hStdOutput  = hWriteOut;
                 
                    CreateProcess(NULL, Cons_App.c_str(), NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
                 
                    DWORD count = 0;
                    char buf[] = {"f= open(\"D:\\__aa.txt\",\"w+\")\r\n"};
                 
                    WriteFile(hWriteInDup, buf, sizeof(buf), &count, NULL);   //  запись команды
                 
                //  Sleep(10000);
                 
                //  WriteFile(hWriteInDup, "f.close()\r\n", 11, &count, NULL);   //  запись команды
                 
                    CloseHandle(hWriteInDup);           // команда выполняется только после CloseHandle = закрытия консольного приложения
                 
                    CloseHandle(hReadOut);
                    CloseHandle(hWriteIn);
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);

              Файл появляется вместе с закрытием интерпретатора, а мне надо, чтобы файл образовался, но Питон остался, чтобы дать другие команды и что-то прочитвть из консоли (про чтение пока не думал, пока отлаживаю запись).
                Цитата ЫукпШ @
                Цитата vlad2 @
                Посылаю команду консольному приложению, запущенного ...

                А каким образом консольное приложение читает команду ?

                Хочется посмотреть исходник принимающей стороны.
                Твоего slave-приложения. Консольного, которым ты управляешь.
                ---
                Вызывает сомнения это:
                ExpandedWrap disabled
                  char buf[] = {"f= open(\"D:\\__aa.txt\",\"w+\")\r\n"};

                Когда ты пайпом управляешь консольным приложением, ты фактически
                подменяешь ручной ввод действиями приложения - Мастера.
                Пайп подключает консольный ввод/вывод к другому приложению.
                Что будет, если ввести "\r\n" ?
                я не знаю.
                С клавиатуры мы вводим только '\r'.
                ---
                Запусти управляемое консольное приложение отдельно.
                И делай вручную все необходимые команды.
                Будет ли работать правильным образом ?
                ---
                А что консольное приложение шлёт назад ?
                Когда юзер жмёт на клавиши, он получает эхо на скрин.
                Твоё мастер-приложение тоже должно получить весь этот вывод.
                Сообщение отредактировано: ЫукпШ -
                  Цитата ЫукпШ @
                  И делай вручную все необходимые команды.
                  Будет ли работать правильным образом ?

                  Да, это первое, что проверил: ручками работает, как надо: выполнил команду и ждёт следующей, пока не закрою. Хочу сделать то же самое, но из программы.
                  Цитата ЫукпШ @
                  Что будет, если ввести "\r\n" ?
                  я не знаю.

                  Любая комбинация \r и \n и её отсутствие никак не влияет (о чём это говорит?).
                  Цитата ЫукпШ @
                  Хочется посмотреть исходник принимающей стороны.

                  Консольное приложение - это интерпретатор Python. Вот установщик.
                    Цитата vlad2 @
                    Консольное приложение - это интерпретатор Python. Вот установщик.

                    Не всеми консольными приложениями можно управлять
                    посредством pipe. Да, у меня такое было.
                    Внутрь Питона не влезешь...
                    Что можно попробовать - напиши консольного "кролика"
                    и по-отлаживай твой комплекс с двух сторон.
                    Просто чтобы убедиться, что "Мастер" работает правильно.
                    Передавай "кролику" то же самое, что и Питону и изучай,
                    как идут данные в обе стороны.
                    ---
                    Других идей пока нет.
                    ---
                    Качай ситуацию, придумывай эксперименты.
                    Как юзер вводит информацию ? нажимает клавишу раз в секунду,
                    каждый раз дожидаясь эха на экране.
                    Сделай такой алгоритм - передай данные по-символьно с обязательным
                    ожиданием эха перед вводом следующего символа.
                    ---
                    А, вот я нашёл кусочек своего исходника - pipe я инициализировал иначе:
                    ExpandedWrap disabled
                      //----------------------------------------------------------------------------------
                      bool WINAPI MPIPE::Create(DWORD dwSize)
                      {
                       if(pipe)                                                               return false;
                       
                       SECURITY_DESCRIPTOR sd; ::ZeroMemory(&sd,sizeof(sd));//структура security для пайпов
                       SECURITY_ATTRIBUTES sa; ::ZeroMemory(&sa,sizeof(sa));
                       
                       sa.nLength              = sizeof(sa);
                       sa.lpSecurityDescriptor = NULL;
                       sa.bInheritHandle       = true;       //разрешаем наследование дескрипторов
                       
                       if(WinIsNT())
                       {
                        if(!::InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION)) return false;
                        if(!::SetSecurityDescriptorDacl(&sd, true, NULL, false))              return false;
                        sa.lpSecurityDescriptor = &sd;
                       }
                       
                       if(!::CreatePipe(hReadStd,hWriteStd,&sa,dwSize))                       return false;
                       
                       pipe=true;
                       return true;
                      }
                      //----------------------------------------------------------------------------------
                    Сообщение отредактировано: ЫукпШ -
                      Цитата ЫукпШ @
                      Не всеми консольными приложениями можно управлять

                      Спасибо, ЫукпШ. Прочитал про особенность Питона с предложением, как обойти проблему. Но это помогло только для одной команды. При попытке дать другую (создать ещё один файл), Питон закрывается и всё, вторая команда не выполняется.
                      Из консоли всё работает нормально. Теперь задача, как выполнить несколько команд в этом открытом Питоне.
                        vlad2, а зачем тебе запускать python процессом? Там же есть готовые библиотеки для интеграции скриптов внутрь твоего exe на плюсах.
                          Цитата vlad2 @
                          Прочитал про особенность Питона с предложением, как обойти проблему.
                          Ах, воночто. Ок, пиши ему в консольный ввод.

                          Добавлено
                          Нашёл у себя пример:
                          ExpandedWrap disabled
                              if (CreateProcess(NULL, &commandLine.front(), NULL, NULL, TRUE,
                                                CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | BELOW_NORMAL_PRIORITY_CLASS,
                                                NULL, NULL, &startInfo, &processInfo) == FALSE)
                                logMsg("CreateProcess() failed", GetLastError());
                              else
                              { // при успехе
                                CloseHandle(processInfo.hThread);                                   // HANDLE нитки не нужен
                             
                                while (WaitForSingleObject(processInfo.hProcess, 1000) == WAIT_TIMEOUT)// пока менеджер работает
                                {
                                  if (fs::exists(stop)) continue;                   // досрочного стопа нет, продолжаем ждать
                             
                                  // Ок, прекращаем прогон
                                  if (!AttachConsole(processInfo.dwProcessId))      // присоединяемся к консоли менеджера
                                    logMsg("AttachConsole() failed", GetLastError());
                                  else
                                  {
                                    DWORD      written;
                                    INPUT_RECORD ev[2];                             // создаём два события
                             
                                    ev[0].EventType = ev[1].EventType = KEY_EVENT;  // кнопочные
                                    ev[0].Event.KeyEvent.bKeyDown = TRUE;           // нажали
                                    ev[1].Event.KeyEvent.bKeyDown = FALSE;          // отпустили
                                    ev[0].Event.KeyEvent.wRepeatCount    = ev[1].Event.KeyEvent.wRepeatCount    = 1;// ESC 1 раз
                                    ev[0].Event.KeyEvent.wVirtualKeyCode = ev[1].Event.KeyEvent.wVirtualKeyCode = VK_ESCAPE;
                                    ev[0].Event.KeyEvent.wVirtualScanCode= 0x01;    // скан коды ESC
                                    ev[1].Event.KeyEvent.wVirtualScanCode= 0x81;
                                    ev[0].Event.KeyEvent.uChar.AsciiChar = ev[1].Event.KeyEvent.uChar.AsciiChar = 27; // ASCII
                                    ev[0].Event.KeyEvent.dwControlKeyState=ev[1].Event.KeyEvent.dwControlKeyState=0;
                                    // пишем в консоль события
                                    if (!WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ev, 2, &written))
                                      logMsg("WriteConsoleInput() failed", GetLastError());
                                    FreeConsole();                                  // отсоединяемся от консоли
                                    break;
                                  }
                                }
                                // Ждём завершения менеджера
                                if (WaitForSingleObject(processInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
                                  logMsg("UART manager failure", GetLastError());
                             
                                DWORD exitCode = -1;
                             
                                // Получить код завершения менеджера
                                GetExitCodeProcess(processInfo.hProcess, &exitCode);
                                CloseHandle(processInfo.hProcess);
                             
                                return exitCode;
                              }
                          Мне тут надо было только ESC послать, но никто не запрещает писать туда целые строки.

                          Добавлено
                          Неочевидные вещи:
                          • в CreateProcess() важен флаг CREATE_NEW_PROCESS_GROUP, который создаёт новую группу процессов с собственной консолью;
                          • Id этой консоли совпадает с Id создаваемого процесса;
                          • если в этой группе окажется несколько процессов, они все разделяют одну консоль;
                          • можно посылать любые консольные события; не помню точно, но, вроде бы, кроме ctrl-break; но можно ctrl-c;
                          • не исследовал, но вполне возможно, что прям-таки все поля структуры заполнять не надо; типа скан-кодов; вероятно, зависит от управляемого т.о. процесса.


                          Добавлено
                          Цитата vlad2 @
                          Но это помогло только для одной команды.
                          И вообще, что тебе мешает сразу написать все команды скриптом?
                          Сообщение отредактировано: Qraizer -
                            Цитата Qraizer @
                            И вообще, что тебе мешает сразу написать все команды скриптом?

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

                            Добавлено
                            Цитата shm @
                            зачем тебе запускать python процессом?
                            Пока ничего не знаю об этом, питоновцы живут отдельно.
                              Цитата Qraizer @
                              Нашёл у себя пример
                              Пока довольно трудно разобраться.
                              Видимо, для строки будет что-то типа:
                              ExpandedWrap disabled
                                            INPUT_RECORD ev[2];                             // создаём два события
                                 
                                            ev[0].EventType = ev[1].EventType = KEY_EVENT;  // кнопочные
                                            ev[0].Event.KeyEvent.bKeyDown = TRUE;           // нажали
                                            ev[1].Event.KeyEvent.bKeyDown = FALSE;          // отпустили
                                            ev[0].Event.KeyEvent.wRepeatCount    = ev[1].Event.KeyEvent.wRepeatCount    = 1;  //  1 раз
                                 
                                          AttachConsole(pi.dwProcessId);      // присоединяемся к консоли менеджера
                                 
                                        while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT)// пока менеджер работает
                                        {
                                          if (fs::exists(stop)) continue;                   // досрочного стопа нет, продолжаем ждать         ???
                                 
                                          // Ок, прекращаем прогон
                                //        AttachConsole(pi.dwProcessId);      // присоединяемся к консоли менеджера
                                 
                                           for (int k = 0; k < sizeof(buf); ++k)
                                           {
                                            ev[0].Event.KeyEvent.wVirtualKeyCode = ev[1].Event.KeyEvent.wVirtualKeyCode = BYTE(buf[k]);
                                            ev[0].Event.KeyEvent.wVirtualScanCode= VkKeyScan(buf[k]);    // скан коды
                                            ev[1].Event.KeyEvent.wVirtualScanCode= 0x81;                                                    //  ???
                                            ev[0].Event.KeyEvent.uChar.AsciiChar = ev[1].Event.KeyEvent.uChar.AsciiChar = 27; // ASCII      //  ???
                                            ev[0].Event.KeyEvent.dwControlKeyState=ev[1].Event.KeyEvent.dwControlKeyState=0;                //  ???
                                 
                                            // пишем в консоль события
                                            WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ev, 2, &count);
                                            }
                                            FreeConsole();                                  // отсоединяемся от консоли
                                            break;
                                        }
                                        // Ждём завершения менеджера
                                        if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
                                          logMsg("UART manager failure", GetLastError());
                                Ну, в примере хватает не относящегося к записи в консоль, да. Просто это логически цельный фрагмент. Если из него что-то вырезать, то получится некорректно спроектированный код, хотя и решающий задачу.
                                Давай сокращу чуть. Но учти, что опущенное тоже важно.
                                ExpandedWrap disabled
                                    if (CreateProcess(NULL, &commandLine.front(), NULL, NULL, TRUE,
                                                      CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | BELOW_NORMAL_PRIORITY_CLASS,
                                                      NULL, NULL, &startInfo, &processInfo) == FALSE)
                                      /* сообщить об ошибке запуска менеджера */;
                                    else
                                    { // при успехе запуска
                                      CloseHandle(processInfo.hThread);                                   // HANDLE нитки не нужен
                                   
                                      while (/* проверяем, что менеджер не завершился сам по себе */ )
                                      {
                                        if (/* проверяем, что не поступило сигнала о принудительном завершении менеджера */) continue;
                                   
                                        /* Ок, сигналим менеджеру, чтоб выходил. Он это понимает, если ему в консоли нажали ESC.
                                           Т.к. консольного окна мы его лишили, пишем ему туда ESC программно. */
                                        if (!AttachConsole(processInfo.dwProcessId))      // присоединяемся к консоли менеджера
                                          /* сообщить об ошибке подачи сигнала менеджеру о выходе */;
                                        else
                                        {
                                          DWORD      written;
                                          INPUT_RECORD ev[2];                             // создаём два события
                                   
                                          ev[0].EventType = ev[1].EventType = KEY_EVENT;  // кнопочные
                                          ev[0].Event.KeyEvent.bKeyDown = TRUE;           // нажали
                                          ev[1].Event.KeyEvent.bKeyDown = FALSE;          // отпустили
                                          ev[0].Event.KeyEvent.wRepeatCount    = ev[1].Event.KeyEvent.wRepeatCount    = 1;// ESC 1 раз
                                          ev[0].Event.KeyEvent.wVirtualKeyCode = ev[1].Event.KeyEvent.wVirtualKeyCode = VK_ESCAPE;
                                          ev[0].Event.KeyEvent.wVirtualScanCode= 0x01;    // скан коды ESC
                                          ev[1].Event.KeyEvent.wVirtualScanCode= 0x81;
                                          ev[0].Event.KeyEvent.uChar.AsciiChar = ev[1].Event.KeyEvent.uChar.AsciiChar = 27; // ASCII
                                          ev[0].Event.KeyEvent.dwControlKeyState=ev[1].Event.KeyEvent.dwControlKeyState=0;
                                          // пишем в консоль два события
                                          if (!WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ev, 2, &written))
                                            /* сообщить об ошибке подачи сигнала менеджеру о выходе */;
                                          FreeConsole();                                  // отсоединяемся от консоли
                                          break;
                                        }
                                      }
                                      // Ждём, пока менеджер не слопает ESC и выйдет
                                      if (WaitForSingleObject(processInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
                                        /* вообще-то, тут баги быть не должно */;
                                      CloseHandle(processInfo.hProcess);                  // не забываем чистить ресурсы


                                Добавлено
                                Если хочется больше конкретики. Есть некий менеджер, который знает своё дело. Он является консольным приложением, его запустили, передав в командной строке параметры, остальные он берёт из окружения сам – и вперёд, на очередные несколько минут он занят работой. Потом завершается, положив в файлы и в консоль результаты и логи. Ещё он умеет по ESC прерываться досрочно. Я же представляю собой робота, который тихонько работает себе в фоне на этой же машине и никого не трогает. И его задача принимать сетевые запросы на обслуживание этим менеджером, формировать параметры командной строки для него, перенаправить его консольный вывод в файлы, усиленно перед этим процессом делая вид, что с ним работают как обычно через консоль. (Естественно, я привёл лишь малую часть кода.) Т.о. люди получают возможность работать на этой машине по сети, а не только непосредственно за ней или по RDP.
                                Я запускаю дочерний процесс, который делает своё дело и выходит сам. Теоретически, я его и ждать-то не должен, запустил и хай работает себе. Но тут дело в том, что помимо сетевого запроса на запуск может прийти и запрос на прерывание ранее запущенного. Поэтому, во-первых, приходится ждать, пока менеджер не завершится сам, и если до этого времени пришёл запрос на его прерывание, передать ему эту команду. Как? Правильно, "нажав" ему ESC. Потому что таков штатный механизм прерывания.
                                Неважно, как конкретно организован рабочий цикл с дочерним процессом. У меня вот так. Тебе, думаю, достаточно взять за основу вон ту подготовку пары событий по каждой кнопке и циклом фигачить эти пары туда не знаю куда. Как? Я не знаю всего твоего кода, обрамление уж на твоей совести. Поэтому на всякий случай и привёл часть моего обрамления, где есть... ну, важные моменты организации диалога.
                                Сообщение отредактировано: Qraizer -
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0643 ]   [ 17 queries used ]   [ Generated: 28.03.24, 11:47 GMT ]