Что такое код getdlgitemint

Содержание

GetDlgItemInt

Important:
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

This function translates the text of a specified control in a dialog box into an integer value.

Parameters hDlg [in] Handle to the dialog box that contains the control of interest. nIDDlgItem [in] Dialog item identifier that specifies the control whose text is to be translated. lpTranslated [out] Long pointer to a Boolean variable that receives a function success/failure value (TRUE indicates success, FALSE indicates failure).

This parameter is optional: it can be NULL. In that case, the function returns no information about success or failure. bSigned [in] Boolean that specifies whether the function should examine the text for a minus sign at the beginning and return a signed integer value if it finds one. TRUE specifies that this should be done, FALSE that it should not.

If the function succeeds, the variable pointed to by lpTranslated is set to TRUE, and the return value is the translated value of the control text.

If the function fails, the variable pointed to by lpTranslated is set to FALSE, and the return value is zero. Note that, since zero is a possible translated value, a return value of zero does not by itself indicate failure.

If lpTranslated is NULL, the function returns no information about success or failure.

If the bSigned parameter is TRUE, specifying that the value to be retrieved is a signed integer value, cast the return value to an int type. To get extended error information, call GetLastError.

The GetDlgItemInt function retrieves the text of the specified control by sending the control a WM_GETTEXT message. The function translates the retrieved text by stripping any extra spaces at the beginning of the text and then converting the decimal digits. The function stops translating when it reaches the end of the text or encounters a nonnumeric character.

If the bSigned parameter is TRUE, the GetDlgItemInt function checks for a minus sign (–) at the beginning of the text and translates the text into a signed integer value. Otherwise, the function creates an unsigned integer value.

The GetDlgItemInt function returns zero if the translated value is greater than INT_MAX (for signed numbers) or UINT_MAX (for unsigned numbers).

Text strings with more than 48 characters cannot be translated.

Runs on Versions Defined in Include Link to
Windows CE OS 1.0 and later Winuser.h Dlgmgr.lib

Note This API is part of the complete Windows CE OS package as provided by Microsoft. The functionality of a particular platform is determined by the original equipment manufacturer (OEM) and some devices may not support this API.

Есть ли что-нибудь подобное GetDlgItemInt но для окна, которая была создана? Win32 Api

Использование C ++ Win32 API, я создал окно (CreateWindow ()) вместо диалогового окна. Существуют ли какие-либо команды, подобные «GetDlgItemInt» или «SetDlgItemInt», который используется для получения и установки данных в окне редактирования для Win 32 API вместо этого? В противном случае мне придется сделать диалоговое окно или сделать кучу коды для преобразования типа INT в строку обратно.

Идея заключается в том, что пользователь определяет ширину и высоту окна, набрав в двух диалоговых окнах редактирования в окне я создал. Есть хорошие легкие уроки, которые в основном говорят мне, как сделать это через диалоговое окно, но я хотел бы знать, если есть аналогичные функции, которые я могу использовать, которые не зависят от диалогового окна?

Я надеюсь что-то вроде этого .

Но, как вы можете себе представить, я не могу использовать xVal или yVal в CreateWindow (), потому что я получаю ошибку компиляции о том, что я не могу конвертировать из INT в CHAR *

Вы можете использовать GetDlgItemInt, просто указать Int ID для параметра HMENU в CreateWindow.

Простейший способ сделать это:

Смотрите также страницу MSDN об использовании Edit Controls.

Для получения данных обратно, используйте GetWindowText, чтобы получить строку, а затем разобрать, что с помощью какой строки к междунар функции синтаксического анализа вы хотите (например. Strtol, atoi, sscanf и т.д.)

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

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

Проблема с GetDlgItemInt и SetDlgItemInt

Обратите внимание на следующий код:

То, что я пытался сделать, это создать графический интерфейс для калькулятора, который вычисляет будущую стоимость фонда. Я хочу, чтобы все переменные, как удваивается. Но часть кода «GetDlgItemInt» явно предназначены для целых чисел, и я не знаю, как изменить его, чтобы получить двойной. Пожалуйста, помогите мне с этим. Заранее спасибо.

Эквивалентная функция , которая возвращает double может быть:

Вы можете добавить проверку на наличие ошибок в случае, если строка содержит не-цифровых символов и т.д.

Не лучше хранить эти ценности в качестве членов CTaskDialog?
( К сожалению забыл , что сделал G (S) etDlgItem)

Что такое код getdlgitemint

Профиль
Группа: Участник
Сообщений: 6
Регистрация: 22.2.2008

Репутация: нет
Всего: нет

Код
k=GetDlgItemInt(hdwnd,ID_EB1,NULL,TRUE);
b=GetDlgItemInt(hdwnd,ID_EB2,NULL,TRUE);

Профиль
Группа: Завсегдатай
Сообщений: 2243
Регистрация: 8.5.2006

Репутация: 8
Всего: 146

586
Дата 22.2.2008, 20:58 (ссылка) | (нет голосов) Загрузка .
Цитата(I_Lammer @ 22.2.2008, 19:19 )
А КАКИМ ОБРАЗОМ МНЕ СОСЧИТАТЬ ВЕЩЕСТВЕННОЕ ЧИСЛО С ПОЛЯ ВВОДА В ДИАЛОГЕ

GetDlgItemText + функция перевода из строки в double (напр. atof)
Альтернатив нету, т.к. ф-ция GetDlgItemInt сделана для дополнения к стилю ES_NUMBER.

Это сообщение отредактировал(а) 586 — 22.2.2008, 21:03

Профиль
Группа: Участник
Сообщений: 6
Регистрация: 22.2.2008

Репутация: нет
Всего: нет

I_Lammer
Дата 22.2.2008, 21:07 (ссылка) | (нет голосов) Загрузка .
Цитата
GetDlgItemText + функция перевода из строки в double (напр. atof)
Альтернатив нету, т.к. ф-ция GetDlgItemInt сделана для дополнения к стилю ES_NUMBER.

Профиль
Группа: Участник
Сообщений: 451
Регистрация: 1.5.2007

Репутация: 1
Всего: 37

Dronchik
Дата 26.2.2008, 18:38 (ссылка) | (нет голосов) Загрузка .
Цитата(I_Lammer @ 22.2.2008, 19:19 )
А КАКИМ ОБРАЗОМ МНЕ СОСЧИТАТЬ ВЕЩЕСТВЕННОЕ ЧИСЛО С ПОЛЯ ВВОДА В ДИАЛОГЕ?
Код
_CRTIMP double __cdecl atof(const char *);
_CRTIMP int __cdecl atoi(const char *);
_CRTIMP long __cdecl atol(const char *);

ЗЫ: Если покапаться в файле stdlib.h то можно очень много полезных функций найти. имхо

Это сообщение отредактировал(а) Dronchik — 26.2.2008, 18:39

Вместо слов делай дело, от которого ты фанатеешь. (с)

Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 85
Всего: 196

bsa
Дата 26.2.2008, 23:15 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Участник
Сообщений: 6
Регистрация: 22.2.2008

Репутация: нет
Всего: нет

586, Dronchik, Да, я уже дописал свою мегопрограмму, Всем спасиб за советы., они мне помогли

I_Lammer
Дата 27.2.2008, 08:00 (ссылка) | (нет голосов) Загрузка .
Google
Дата 13.11.2020, 06:28 (ссылка)

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • FAQ раздела лежит здесь!

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, bsa.

GetDlgItemInt

Описание: function GetDlgItemInt(Dlg: HWnd; IDDlgItem: Integer; Translate: LPBool; Signed:

Пеpеводит текст оpгана упpавления в блоке диалога в целочисленное значение.

Пpедшествующие нули отсекаются.

Паpаметpы:

Dlg: Идентификатоp блока диалога.

IDDlgItem: Идентификатоp элемента.

Translate: Возвpащенное значение Bool; 0 — в случае ошибки пеpевода.

Signed: Обpабатывать считанное значение как значение со знаком.

Возвpащаемое значение:

См. также: wm_GetText функция находится в файле user32.dll

Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1

Я добрался до кода широко известного клиента мгновенных сообщений Miranda IM. Вместе с различными плагинами это достаточно большой проект, размер которого составляет около 950 тысяч строк кода на C и C++. И, как в любом солидном проекте с историей развития, в нем имеется немалое количество ошибок и опечаток.

Рассматривая дефекты в различных приложениях, я заметил некоторые закономерности. И сейчас на примере дефектов, найденных в Miranda IM, я попробую сформулировать некоторые рекомендации, которые позволят избежать многих ошибок и опечаток ещё на этапе написания кода.

Для анализа Miranda IM я использовал (да, вы уже угадали) анализатор PVS-Studio 4.14. Код проекта Miranda IM весьма качественен, что подтверждается популярностью этой программы. Я и сам являюсь пользователем этого клиента и не имею к нему претензий в плане качества. Проект собирается в Visual Studio с Warning Level 3 (/W3), а количество комментариев составляет 20% от всего текста программы.

1. Избегайте функции memset, memcpy, ZeroMemory и им аналогичные

Мы начнем с ошибок, возникающих при использовании низкоуровневых функций работы с памятью такими, как memset, memcpy, ZeroMemory и им подобных.

Рекомендую всячески избегать этих функций. Понятно, что не стоит буквально следовать моему совету и заменять все эти функции на циклы. Но я видел такое огромное количество ошибок, связанных с использованием этих функций, что очень рекомендую быть аккуратными с ними и использовать только по необходимости. На мой взгляд есть только два случая, когда использование этих функций оправдано:

1) Обработка больших массивов. То есть там, где оптимизированный алгоритм функции даст выигрыш по сравнению с обработкой элементов в простом цикле.

2) Обработка большого количества маленьких массивов. Также имеет смысл с точки зрения улучшения производительности.

Во всех остальных случаях лучше попробовать обойтись без них. Например, я считаю эти функции излишними в такой программе, как Miranda. Никаких ресурсоёмких алгоритмов или огромных массивов в ней нет. Следовательно, использование функций memset/memcpy проистекает только из удобства написания короткого кода. Однако, эта простота очень обманчива и, сэкономив несколько секунд на написании кода, можно неделями вылавливать нечётко проявляющуюся ошибку порчи памяти. Рассмотрим несколько примеров кода, взятых из проекта Miranda IM.

V512 A call of the ‘memcpy’ function will lead to a buffer overflow or underflow. tabsrmm utils.cpp 1080

Копируем только кусок строки. Ошибка банальна до безобразия, но от этого не перестаёт быть ошибкой. Скорее всего, раньше здесь использовалась строка, состоящая из ‘char’. Затем перешли на Unicode строки, а поменять константу забыли.

Если изначально использовать для копирования строки функции, которые для этого и предназначаются, то такая ошибка просто не сможет возникнуть. Представим, что было написано так:

Тогда при переходе к Unicode строкам, число 7 изменять не надо:

Я не говорю, что это идеальный код. Но он уже намного лучше, чем использование CopyMemory. Рассмотрим другой пример.

V568 It’s odd that the argument of sizeof() operator is the ‘& ImgIndex’ expression. clist_modern modern_extraimage.cpp 302

Здесь хотелось обнулить массив, состоящий из 64-указателей. Но вместо этого мы обнулим только первый элемент. Эта же ошибка, кстати, присутствует ещё раз в другом файле. Скажем спасибо Copy-Paste:

V568 It’s odd that the argument of sizeof() operator is the ‘& ImgIndex’ expression. clist_mw extraimage.c 295

Корректный вариант кода должен выглядеть следующим образом:

Кстати, взятие адреса у массива может только дополнительно запутать того, кто читает код. Здесь взятие адреса не имеет никакого эффекта. И код может быть написан так:

V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 258

Снова, вместо вычисления размера массива, мы вычисляем размер указателя. Правильным выражением будет «sizeof(rowOptTA)». Для очистки массива я предлагаю следующий вариант такого кода:

Я уже привык, что подобные строки любят размножаться по программе копированием:

V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 308

V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 438

Вы думаете, что это всё касательно низкоуровневой работы с масивами? Нет, не всё. Смотрите дальше, бойтесь и бейте по рукам любителей написать memset.

V512 A call of the ‘memset’ function will lead to a buffer overflow or underflow. clist_modern modern_image_array.cpp 59

В этот раз размер копируемых данных вычислен корректно. Вот только перепутан местами второй и третий аргумент. Как следствие, мы заполняем 0 элементов. Корректный вариант:

Как переписать этот фрагмент кода более изящно, я не знаю. Вернее, его нельзя переписать красиво, не затрагивая другие участки программы и структуры данных.

Возникает вопрос, а как же обойтись без memset при работе с такими структурами, как OPENFILENAME:

Очень просто. Создать обнуленную структуру можно так:

2. Внимательно следите, работаете вы со знаковым или беззнаковым типом

Проблема путаницы между signed и unsigned типами может на первый взгляд показаться надуманной. Но этот вопрос зря недооценивается программистами.

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

Предлагаю больше так не делать и каждый раз анализировать ситуацию, когда знаковый тип встречается с беззнаковым. И, вообще, быть внимательным к тому, какой тип имеет выражение или что возвращает функция. Теперь несколько примеров по рассматриваемой теме.

V547 Expression ‘wParam >= 0’ is always true. Unsigned type value is always >= 0. clist_mw cluiframes.c 3140

В коде программы имеется функция id2pos, которая возвращает значение ‘-1’ в случае ошибки. С этой функцией всё в порядке. В другом месте результат работы функции id2pos используется следующим образом:

Проблема в том, что переменная wParam имеет беззнаковый тип. Следовательно, условие ‘wParam>=0’ всегда истинно. Если функция id2pos вернет ‘-1’, то условие проверки недопустимых значений не сработает, и мы начнем использовать отрицательный индекс.

Я почти уверен, что вначале был написан следующий код:

if (wParam>=0 && wParam =0 && (int)wParam = 0. scriver msgoptions.c 458

И это действительно так. Выражение «GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) >= SRMSGSET_LIMITNAMESLEN_MIN» всегда истинно.

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

3. Избегайте большого количества вычислений в одной строке

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

Чаще всего ошибки возникают там, где программист, с целью создать компактный код, собирает в одну строку сразу несколько действий. При незначительном выигрыше в изящности он существенно увеличивает риск опечатки или того, что не учтёт побочные действия. Рассмотрим пример:

V567 Undefined behavior. The ‘s’ variable is modified while being used twice between sequence points. msn ezxml.c 371

Здесь возникает неопределенное поведение (undefined behavior). Этот код может корректно функционировать в течение долгого периода жизни программы. Но нет никакой гарантии, что он также будет вести себя после смены версии компилятора или ключей оптимизации. Компилятор в праве вначале вычислить ‘++s’, а затем взывать функцию ‘strspn(s, EZXML_WS)’. Или, наоборот, вначале он может вызвать функцию, а уже затем увеличить значение переменной ‘s’.

Приведу другой пример, почему не стоит пытаться собрать всё в одну строку. В Miranda IM некоторые ветви исполнения программы отключены/включены с помощью вставок вида ‘&& 0’. Примеры:

С приведенными сравнениями всё ясно и они хорошо заметны. А теперь представьте, что вы встречаете фрагмент показанный ниже. Код я отформатировал. В программе это ОДНА строка.

V560 A part of conditional expression is always false: 0. clist_modern modern_clui.cpp 2979

Если здесь нет ошибки, то всё равно непросто вспомнить и найти в этой строке слово FALSE. Вы его нашли? Согласитесь — непростая задача. А если это ошибка? Тогда вообще нет шансов найти её, просто просматривая код. Подобные выражения очень полезно выносить отдельно. Пример:

А я сам, пожалуй, написал бы это длинным, но более понятным способом:

Да, это длиннее, но зато легко читается, а слово FALSE лучше заметно.

4. Выравнивайте в коде всё, что возможно

Выравнивание кода уменьшает вероятность допустить опечатку или ошибки при Copy-Paste. Если ошибка все же будет сделана, то найти её в процессе обзора кода будет в несколько раз проще. Рассмотрим пример кода.

V537 Consider reviewing the correctness of ‘maxX’ item’s usage. clist_modern modern_skinengine.cpp 2898

Монолитный фрагмент кода, читать который совершенно не интересно. Отформатируем его:

Это не самый показательный пример, но согласитесь, теперь стало намного проще заметить, что два раза используется переменная maxX.

Мою рекомендацию по выравниванию не стоит понимать буквально и везде строить столбцы кода. Во-первых, это требует дополнительного времени при написании и правке кода. Во-вторых, это может привести к другим ошибкам. В следующем примере, как раз желание сделать красивый столбик привело к возникновению ошибки в коде Miranda IM.

V536 Be advised that the utilized constant value is represented by an octal form. Oct: 037, Dec: 31. msn msn_mime.cpp 192

Желая сделать красивую колонку чисел, легко увлечься и вписать в начале ‘0’, сделав константу восьмеричной.

Уточняю рекомендацию. Выравнивайте в коде всё, что возможно. Но не выравнивайте числа, дописывая нули.

5. Не размножайте строку более, чем один раз

Копирование строк при программировании неизбежно. Но можно подстраховать себя, не вставляя строку из буфера обмена сразу несколько раз. В большинстве случаев лучше скопировать строку, затем отредактировать. Вновь скопировать и отредактировать. И так далее. Так трудней забыть что-то изменить в строке или изменить её неправильно. Рассмотрим пример кода:

V525 The code containing the collection of similar blocks. Check items ‘1316’, ‘1319’, ‘1318’, ‘1323’, ‘1323’, ‘1317’, ‘1321’ in lines 954, 955, 956, 957, 958, 959, 960. clist_modern modern_clcopts.cpp 954

Скорее всего, никакой настоящей ошибки здесь нет. Просто два раза работаем с элементом IDC_ALWAYSPRIMARY. Тем не менее, ошибиться в подобных блоках из скопированных строк весьма легко.

6. Выставляйте высокий уровень предупреждений у компилятора и используйте статические анализаторы

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

Тем не менее, многие из этих ошибок можно обнаружить ещё на этапе написания кода. В первую очередь — это предупреждения компилятора. А во вторую — отчеты от ночного запуска статических анализаторов кода.

Сейчас кто-то скажет, что это плохо скрываемая реклама. Но на самом деле это просто ещё одна рекомендация, которая позволяет сократить количество ошибок. Если я нашел ошибки, используя статический анализ, и не могу сказать, как избежать их появления в коде, то значит рекомендацией как раз и является использование анализаторов кода.

Рассмотрим некоторые примеры ошибок, которые могут быть оперативно выявлены анализаторами исходного кода:

V560 A part of conditional expression is always true: 0x01000. tabsrmm tools.cpp 1023

Допущена опечатка. Вместо оператора ‘&’ используется оператор ‘&&’. Как здесь подстраховаться при написании кода, я не знаю. Корректный вариант условия:

V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *str != ‘\0’. clist_modern modern_skinbutton.cpp 282

V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *endstr != ‘\0’. clist_modern modern_skinbutton.cpp 283

В этом коде всего лишь забыты две звездочки ‘*’ для разыменования указателей. Результат может быть фатальным. Этот код предрасположен к access violation. Корректный вариант кода:

Здесь вновь сложно дать совет, кроме использования специальных инструментов для проверки кода.

V514 Dividing sizeof a pointer ‘sizeof (text)’ by another value. There is a probability of logical error presence. clist_modern modern_cachefuncs.cpp 567

На первый взгляд все хорошо. В функцию передается текст и его длина, посчитанная с помощью макроса SIZEOF. На самом деле макрос следует назвать COUNT_OF, но не в этом дело. Беда в том, что мы пытаемся посчитать количество символов в указателе. Здесь вычисляется «sizeof(LPTSTR) / sizeof(TCHAR)». Человек такие подозрительные места замечает плохо, а вот компилятор и статический анализатор хорошо. Исправленный вариант кода:

V560 A part of conditional expression is always true: 0x29. icqoscar8 fam_03buddy.cpp 632

Здесь уместна рекомендация писать константу в условии на первом месте. Вот такой код просто не скомпилируется:

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

Если программист отказывается от такого стиля сравнений, то ему остается или полагаться на компилятор/анализатор, или рисковать.

Кстати, несмотря на то, что про эту ошибку все знают, она не самая редкая. Ещё три примера из Miranda IM, где анализатор PVS-Studio выдал предупреждение V559:

Анализ кода позволяет также выявить если и не ошибки, то очень подозрительные места в коде. Например, в Miranda IM указатели используют далеко не только как указатели. Если в одних местах кода такие игры выглядят нормально, то в других пугают. Пример кода, который меня крайне настораживает:

V542 Consider inspecting an odd type cast: ‘char *’ to ‘char’. clist_modern modern_toolbar.cpp 586

Фактически мы проверяем, что адрес строки не кратен 256. Мне не понятно, что на самом деле хотели написать в условии. Возможно, это даже правильно, но сильно сомнительно.

Анализом кода можно найти немало некорректных условий. Пример:

V501 There are identical sub-expressions ‘user->statusMessage’ to the left and to the right of the ‘&&’ operator. jabber jabber_chat.cpp 214

И так далее и так далее. Я могу ещё долго приводить другие примеры, но это не имеет смысла. Важно то, что большое количество ошибок можно обнаружить с помощью статического анализа на самых ранних этапах.

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

Статический анализ представляет больший интерес в процессе разработки программы, а не в качестве разовых проверок. Множество ошибок и опечаток находится в процессе тестирования и создания юнит-тестов. Но если часть из этих ошибок можно найти ещё на этапе написания кода, то это будет колоссальный выигрыш времени и сил. Обидно два часа отлаживать программу, чтобы потом заметить лишнюю точку с запятой ‘;’ после оператора ‘for’. Такую ошибку можно часто обезвредить, потратив 10 минут на статический анализ измененных в процессе работы файлов.

Заключение

В этой статье я поделился только некоторыми мыслями по поводу того, как допускать меньше ошибок при программировании на Си++. Зреют и другие мысли, о которых я постараюсь написать в следующих статьях и заметках.

Уже стало традицией, что после подобной статьи, кто-то спрашивает, а сообщили ли вы разработчикам программы/библиотеки о найденных ошибках. Заранее отвечу на вопрос, отправили ли мы баг репорт по проекту Miranda IM.

Нет, не отправили. Это слишком ресурсоёмкая задача. В статье показана только малая часть от того, что было найдено. В проекте около ста фрагментов, где стоит поправить код, и более ста, где я просто не знаю, ошибка это или нет. Однако, перевод этой статьи будет отправлен авторам Miranda IM и им будет предложена бесплатная версия анализатора PVS-Studio. Если они проявят интерес, то смогут сами проверить исходный код и исправить то, что сочтут нужным.

Ещё стоит пояснить, почему я часто не берусь судить ошибка тот или иной участок кода или нет. Пример неоднозначного кода:

V523 The ‘then’ statement is equivalent to the ‘else’ statement. scriver msglog.c 695

Вот два одинаковых фрагмента кода. Возможно, это ошибка. А возможно, сейчас в любой ветке нужен одинаковый набор действий. И код написан специально так, чтобы потом его было просто модифицировать. Здесь необходимо знать программу, чтобы разобраться ошибочно это место или нет.

GetDlgItemInt

Правила форума «C/C++: Для новичков»
Important:
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

This function translates the text of a specified control in a dialog box into an integer value.

Parameters hDlg [in] Handle to the dialog box that contains the control of interest. nIDDlgItem [in] Dialog item identifier that specifies the control whose text is to be translated. lpTranslated [out] Long pointer to a Boolean variable that receives a function success/failure value (TRUE indicates success, FALSE indicates failure).

This parameter is optional: it can be NULL. In that case, the function returns no information about success or failure. bSigned [in] Boolean that specifies whether the function should examine the text for a minus sign at the beginning and return a signed integer value if it finds one. TRUE specifies that this should be done, FALSE that it should not.

If the function succeeds, the variable pointed to by lpTranslated is set to TRUE, and the return value is the translated value of the control text.

If the function fails, the variable pointed to by lpTranslated is set to FALSE, and the return value is zero. Note that, since zero is a possible translated value, a return value of zero does not by itself indicate failure.

If lpTranslated is NULL, the function returns no information about success or failure.

If the bSigned parameter is TRUE, specifying that the value to be retrieved is a signed integer value, cast the return value to an int type. To get extended error information, call GetLastError.

The GetDlgItemInt function retrieves the text of the specified control by sending the control a WM_GETTEXT message. The function translates the retrieved text by stripping any extra spaces at the beginning of the text and then converting the decimal digits. The function stops translating when it reaches the end of the text or encounters a nonnumeric character.

If the bSigned parameter is TRUE, the GetDlgItemInt function checks for a minus sign (–) at the beginning of the text and translates the text into a signed integer value. Otherwise, the function creates an unsigned integer value.

The GetDlgItemInt function returns zero if the translated value is greater than INT_MAX (for signed numbers) or UINT_MAX (for unsigned numbers).

Text strings with more than 48 characters cannot be translated.

Runs on Versions Defined in Include Link to
Windows CE OS 1.0 and later Winuser.h Dlgmgr.lib

Note This API is part of the complete Windows CE OS package as provided by Microsoft. The functionality of a particular platform is determined by the original equipment manufacturer (OEM) and some devices may not support this API.

Что такое код getdlgitemint

Возвращаемое значение
Определяет транслируемое значение текста элемента диалогового окна. Так как 0 допустимое возвращаемое значение, lpTrans должен использоваться, чтобы обнаружить ошибки. Если возвращаемое значение желательно со знаком, нужно его привести к типу int.
Функция возвращается 0, если транслируемый номер больше чем 32,767 (для чисел со знаком) или 65,535 (для чисел без знака).
Когда ошибки происходят, типа столкновения с нечисловыми символами или превышение вышеупомянутого максимума GetDlgItemInt копирует 0 по адресу указанному в lpTrans. Если ошибок нет lpTrans получает значение отличное от нуля. Если lpTrans NULL, GetDlgItemInt не предупреждает относительно ошибок.

Параметры
nID
Определяет целочисленный идентификатор управления диалогового окна, которое нужно транслировать.
lpTrans
Укажите на Булеву переменную, которая должна получить транслируемый флажок.
bSigned
Определяет со знаком ли значение, которое будет получено

Замечания
Возвращает текст элемента управления идентифицированного nID. Она транслирует текст определенного элемента управления в данном диалоговом окне в целочисленное значение удаляя любые дополнительные пробелы в начале текста и преобразуя десятичные цифры. Она останавливает трансляцию когда это достигает конца текста или сталкивается с любым нечисловым символом. Если bSigned TRUE, GetDlgItemInt проверяет знак «минус» (-) в начале текста и транслирует текст в число со знаком. Иначе оно создает значение без знака. Она посылает WM_GETTEXT сообщение элементу управлению.

Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1

Я добрался до кода широко известного клиента мгновенных сообщений Miranda IM. Вместе с различными плагинами это достаточно большой проект, размер которого составляет около 950 тысяч строк кода на C и C++. И, как в любом солидном проекте с историей развития, в нем имеется немалое количество ошибок и опечаток.

Рассматривая дефекты в различных приложениях, я заметил некоторые закономерности. И сейчас на примере дефектов, найденных в Miranda IM, я попробую сформулировать некоторые рекомендации, которые позволят избежать многих ошибок и опечаток ещё на этапе написания кода.

Для анализа Miranda IM я использовал (да, вы уже угадали) анализатор PVS-Studio 4.14. Код проекта Miranda IM весьма качественен, что подтверждается популярностью этой программы. Я и сам являюсь пользователем этого клиента и не имею к нему претензий в плане качества. Проект собирается в Visual Studio с Warning Level 3 (/W3), а количество комментариев составляет 20% от всего текста программы.

1. Избегайте функции memset, memcpy, ZeroMemory и им аналогичные

Мы начнем с ошибок, возникающих при использовании низкоуровневых функций работы с памятью такими, как memset, memcpy, ZeroMemory и им подобных.

Рекомендую всячески избегать этих функций. Понятно, что не стоит буквально следовать моему совету и заменять все эти функции на циклы. Но я видел такое огромное количество ошибок, связанных с использованием этих функций, что очень рекомендую быть аккуратными с ними и использовать только по необходимости. На мой взгляд есть только два случая, когда использование этих функций оправдано:

1) Обработка больших массивов. То есть там, где оптимизированный алгоритм функции даст выигрыш по сравнению с обработкой элементов в простом цикле.

2) Обработка большого количества маленьких массивов. Также имеет смысл с точки зрения улучшения производительности.

Во всех остальных случаях лучше попробовать обойтись без них. Например, я считаю эти функции излишними в такой программе, как Miranda. Никаких ресурсоёмких алгоритмов или огромных массивов в ней нет. Следовательно, использование функций memset/memcpy проистекает только из удобства написания короткого кода. Однако, эта простота очень обманчива и, сэкономив несколько секунд на написании кода, можно неделями вылавливать нечётко проявляющуюся ошибку порчи памяти. Рассмотрим несколько примеров кода, взятых из проекта Miranda IM.

V512 A call of the ‘memcpy’ function will lead to a buffer overflow or underflow. tabsrmm utils.cpp 1080

Копируем только кусок строки. Ошибка банальна до безобразия, но от этого не перестаёт быть ошибкой. Скорее всего, раньше здесь использовалась строка, состоящая из ‘char’. Затем перешли на Unicode строки, а поменять константу забыли.

Если изначально использовать для копирования строки функции, которые для этого и предназначаются, то такая ошибка просто не сможет возникнуть. Представим, что было написано так:

Тогда при переходе к Unicode строкам, число 7 изменять не надо:

Я не говорю, что это идеальный код. Но он уже намного лучше, чем использование CopyMemory. Рассмотрим другой пример.

V568 It’s odd that the argument of sizeof() operator is the ‘& ImgIndex’ expression. clist_modern modern_extraimage.cpp 302

Здесь хотелось обнулить массив, состоящий из 64-указателей. Но вместо этого мы обнулим только первый элемент. Эта же ошибка, кстати, присутствует ещё раз в другом файле. Скажем спасибо Copy-Paste:

V568 It’s odd that the argument of sizeof() operator is the ‘& ImgIndex’ expression. clist_mw extraimage.c 295

Корректный вариант кода должен выглядеть следующим образом:

Кстати, взятие адреса у массива может только дополнительно запутать того, кто читает код. Здесь взятие адреса не имеет никакого эффекта. И код может быть написан так:

V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 258

Снова, вместо вычисления размера массива, мы вычисляем размер указателя. Правильным выражением будет «sizeof(rowOptTA)». Для очистки массива я предлагаю следующий вариант такого кода:

Я уже привык, что подобные строки любят размножаться по программе копированием:

V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 308

V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 438

Вы думаете, что это всё касательно низкоуровневой работы с масивами? Нет, не всё. Смотрите дальше, бойтесь и бейте по рукам любителей написать memset.

V512 A call of the ‘memset’ function will lead to a buffer overflow or underflow. clist_modern modern_image_array.cpp 59

В этот раз размер копируемых данных вычислен корректно. Вот только перепутан местами второй и третий аргумент. Как следствие, мы заполняем 0 элементов. Корректный вариант:

Как переписать этот фрагмент кода более изящно, я не знаю. Вернее, его нельзя переписать красиво, не затрагивая другие участки программы и структуры данных.

Возникает вопрос, а как же обойтись без memset при работе с такими структурами, как OPENFILENAME:

Очень просто. Создать обнуленную структуру можно так:

2. Внимательно следите, работаете вы со знаковым или беззнаковым типом

Проблема путаницы между signed и unsigned типами может на первый взгляд показаться надуманной. Но этот вопрос зря недооценивается программистами.

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

Предлагаю больше так не делать и каждый раз анализировать ситуацию, когда знаковый тип встречается с беззнаковым. И, вообще, быть внимательным к тому, какой тип имеет выражение или что возвращает функция. Теперь несколько примеров по рассматриваемой теме.

V547 Expression ‘wParam >= 0’ is always true. Unsigned type value is always >= 0. clist_mw cluiframes.c 3140

В коде программы имеется функция id2pos, которая возвращает значение ‘-1’ в случае ошибки. С этой функцией всё в порядке. В другом месте результат работы функции id2pos используется следующим образом:

Проблема в том, что переменная wParam имеет беззнаковый тип. Следовательно, условие ‘wParam>=0’ всегда истинно. Если функция id2pos вернет ‘-1’, то условие проверки недопустимых значений не сработает, и мы начнем использовать отрицательный индекс.

Я почти уверен, что вначале был написан следующий код:

if (wParam>=0 && wParam =0 && (int)wParam = 0. scriver msgoptions.c 458

И это действительно так. Выражение «GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) >= SRMSGSET_LIMITNAMESLEN_MIN» всегда истинно.

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

3. Избегайте большого количества вычислений в одной строке

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

Чаще всего ошибки возникают там, где программист, с целью создать компактный код, собирает в одну строку сразу несколько действий. При незначительном выигрыше в изящности он существенно увеличивает риск опечатки или того, что не учтёт побочные действия. Рассмотрим пример:

V567 Undefined behavior. The ‘s’ variable is modified while being used twice between sequence points. msn ezxml.c 371

Здесь возникает неопределенное поведение (undefined behavior). Этот код может корректно функционировать в течение долгого периода жизни программы. Но нет никакой гарантии, что он также будет вести себя после смены версии компилятора или ключей оптимизации. Компилятор в праве вначале вычислить ‘++s’, а затем взывать функцию ‘strspn(s, EZXML_WS)’. Или, наоборот, вначале он может вызвать функцию, а уже затем увеличить значение переменной ‘s’.

Приведу другой пример, почему не стоит пытаться собрать всё в одну строку. В Miranda IM некоторые ветви исполнения программы отключены/включены с помощью вставок вида ‘&& 0’. Примеры:

С приведенными сравнениями всё ясно и они хорошо заметны. А теперь представьте, что вы встречаете фрагмент показанный ниже. Код я отформатировал. В программе это ОДНА строка.

V560 A part of conditional expression is always false: 0. clist_modern modern_clui.cpp 2979

Если здесь нет ошибки, то всё равно непросто вспомнить и найти в этой строке слово FALSE. Вы его нашли? Согласитесь — непростая задача. А если это ошибка? Тогда вообще нет шансов найти её, просто просматривая код. Подобные выражения очень полезно выносить отдельно. Пример:

А я сам, пожалуй, написал бы это длинным, но более понятным способом:

Да, это длиннее, но зато легко читается, а слово FALSE лучше заметно.

4. Выравнивайте в коде всё, что возможно

Выравнивание кода уменьшает вероятность допустить опечатку или ошибки при Copy-Paste. Если ошибка все же будет сделана, то найти её в процессе обзора кода будет в несколько раз проще. Рассмотрим пример кода.

V537 Consider reviewing the correctness of ‘maxX’ item’s usage. clist_modern modern_skinengine.cpp 2898

Монолитный фрагмент кода, читать который совершенно не интересно. Отформатируем его:

Это не самый показательный пример, но согласитесь, теперь стало намного проще заметить, что два раза используется переменная maxX.

Мою рекомендацию по выравниванию не стоит понимать буквально и везде строить столбцы кода. Во-первых, это требует дополнительного времени при написании и правке кода. Во-вторых, это может привести к другим ошибкам. В следующем примере, как раз желание сделать красивый столбик привело к возникновению ошибки в коде Miranda IM.

V536 Be advised that the utilized constant value is represented by an octal form. Oct: 037, Dec: 31. msn msn_mime.cpp 192

Желая сделать красивую колонку чисел, легко увлечься и вписать в начале ‘0’, сделав константу восьмеричной.

Уточняю рекомендацию. Выравнивайте в коде всё, что возможно. Но не выравнивайте числа, дописывая нули.

5. Не размножайте строку более, чем один раз

Копирование строк при программировании неизбежно. Но можно подстраховать себя, не вставляя строку из буфера обмена сразу несколько раз. В большинстве случаев лучше скопировать строку, затем отредактировать. Вновь скопировать и отредактировать. И так далее. Так трудней забыть что-то изменить в строке или изменить её неправильно. Рассмотрим пример кода:

V525 The code containing the collection of similar blocks. Check items ‘1316’, ‘1319’, ‘1318’, ‘1323’, ‘1323’, ‘1317’, ‘1321’ in lines 954, 955, 956, 957, 958, 959, 960. clist_modern modern_clcopts.cpp 954

Скорее всего, никакой настоящей ошибки здесь нет. Просто два раза работаем с элементом IDC_ALWAYSPRIMARY. Тем не менее, ошибиться в подобных блоках из скопированных строк весьма легко.

6. Выставляйте высокий уровень предупреждений у компилятора и используйте статические анализаторы

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

Тем не менее, многие из этих ошибок можно обнаружить ещё на этапе написания кода. В первую очередь — это предупреждения компилятора. А во вторую — отчеты от ночного запуска статических анализаторов кода.

Сейчас кто-то скажет, что это плохо скрываемая реклама. Но на самом деле это просто ещё одна рекомендация, которая позволяет сократить количество ошибок. Если я нашел ошибки, используя статический анализ, и не могу сказать, как избежать их появления в коде, то значит рекомендацией как раз и является использование анализаторов кода.

Рассмотрим некоторые примеры ошибок, которые могут быть оперативно выявлены анализаторами исходного кода:

V560 A part of conditional expression is always true: 0x01000. tabsrmm tools.cpp 1023

Допущена опечатка. Вместо оператора ‘&’ используется оператор ‘&&’. Как здесь подстраховаться при написании кода, я не знаю. Корректный вариант условия:

V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *str != ‘\0’. clist_modern modern_skinbutton.cpp 282

V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *endstr != ‘\0’. clist_modern modern_skinbutton.cpp 283

В этом коде всего лишь забыты две звездочки ‘*’ для разыменования указателей. Результат может быть фатальным. Этот код предрасположен к access violation. Корректный вариант кода:

Здесь вновь сложно дать совет, кроме использования специальных инструментов для проверки кода.

V514 Dividing sizeof a pointer ‘sizeof (text)’ by another value. There is a probability of logical error presence. clist_modern modern_cachefuncs.cpp 567

На первый взгляд все хорошо. В функцию передается текст и его длина, посчитанная с помощью макроса SIZEOF. На самом деле макрос следует назвать COUNT_OF, но не в этом дело. Беда в том, что мы пытаемся посчитать количество символов в указателе. Здесь вычисляется «sizeof(LPTSTR) / sizeof(TCHAR)». Человек такие подозрительные места замечает плохо, а вот компилятор и статический анализатор хорошо. Исправленный вариант кода:

V560 A part of conditional expression is always true: 0x29. icqoscar8 fam_03buddy.cpp 632

Здесь уместна рекомендация писать константу в условии на первом месте. Вот такой код просто не скомпилируется:

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

Если программист отказывается от такого стиля сравнений, то ему остается или полагаться на компилятор/анализатор, или рисковать.

Кстати, несмотря на то, что про эту ошибку все знают, она не самая редкая. Ещё три примера из Miranda IM, где анализатор PVS-Studio выдал предупреждение V559:

Анализ кода позволяет также выявить если и не ошибки, то очень подозрительные места в коде. Например, в Miranda IM указатели используют далеко не только как указатели. Если в одних местах кода такие игры выглядят нормально, то в других пугают. Пример кода, который меня крайне настораживает:

V542 Consider inspecting an odd type cast: ‘char *’ to ‘char’. clist_modern modern_toolbar.cpp 586

Фактически мы проверяем, что адрес строки не кратен 256. Мне не понятно, что на самом деле хотели написать в условии. Возможно, это даже правильно, но сильно сомнительно.

Анализом кода можно найти немало некорректных условий. Пример:

V501 There are identical sub-expressions ‘user->statusMessage’ to the left and to the right of the ‘&&’ operator. jabber jabber_chat.cpp 214

И так далее и так далее. Я могу ещё долго приводить другие примеры, но это не имеет смысла. Важно то, что большое количество ошибок можно обнаружить с помощью статического анализа на самых ранних этапах.

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

Статический анализ представляет больший интерес в процессе разработки программы, а не в качестве разовых проверок. Множество ошибок и опечаток находится в процессе тестирования и создания юнит-тестов. Но если часть из этих ошибок можно найти ещё на этапе написания кода, то это будет колоссальный выигрыш времени и сил. Обидно два часа отлаживать программу, чтобы потом заметить лишнюю точку с запятой ‘;’ после оператора ‘for’. Такую ошибку можно часто обезвредить, потратив 10 минут на статический анализ измененных в процессе работы файлов.

Заключение

В этой статье я поделился только некоторыми мыслями по поводу того, как допускать меньше ошибок при программировании на Си++. Зреют и другие мысли, о которых я постараюсь написать в следующих статьях и заметках.

Уже стало традицией, что после подобной статьи, кто-то спрашивает, а сообщили ли вы разработчикам программы/библиотеки о найденных ошибках. Заранее отвечу на вопрос, отправили ли мы баг репорт по проекту Miranda IM.

Нет, не отправили. Это слишком ресурсоёмкая задача. В статье показана только малая часть от того, что было найдено. В проекте около ста фрагментов, где стоит поправить код, и более ста, где я просто не знаю, ошибка это или нет. Однако, перевод этой статьи будет отправлен авторам Miranda IM и им будет предложена бесплатная версия анализатора PVS-Studio. Если они проявят интерес, то смогут сами проверить исходный код и исправить то, что сочтут нужным.

Ещё стоит пояснить, почему я часто не берусь судить ошибка тот или иной участок кода или нет. Пример неоднозначного кода:

V523 The ‘then’ statement is equivalent to the ‘else’ statement. scriver msglog.c 695

Вот два одинаковых фрагмента кода. Возможно, это ошибка. А возможно, сейчас в любой ветке нужен одинаковый набор действий. И код написан специально так, чтобы потом его было просто модифицировать. Здесь необходимо знать программу, чтобы разобраться ошибочно это место или нет.

Есть ли что-то вроде GetDlgItemInt, но для созданного окна? Win32 Api

Используя C++ Win32 API, я создал окно (CreateWindow()) вместо диалогового окна. Существуют ли какие-либо команды, похожие на «GetDlgItemInt» или «SetDlgItemInt», которые используются для получения и настройки данных в окне редактирования для API Win 32? В противном случае мне придется сделать диалоговое окно или сделать кучу кода для преобразования INT в строку, а затем обратно.

Идея состоит в том, что пользователь указывает ширину и высоту окна, введя два диалоговых окна редактирования в появившемся окне. Есть неплохие учебные пособия, которые в основном говорят мне, как это сделать через диалоговое окно, но я хотел бы знать, есть ли подобные функции, которые я могу использовать, которые не зависят от диалогового окна?

Я надеюсь что-то вроде этого .

Но, как вы можете себе представить, я не могу использовать xVal или yVal в CreateWindow(), потому что я получаю ошибку компиляции, указав I не может конвертировать из INT в CHAR *

Создан 23 дек. 10 2010-12-23 07:19:05 ChiggenWingz

Я действительно не понимаю вас. Вы начинаете с обсуждения GetDlgItemInt, а затем вы показываете код, который содержит только вызов CreateWindow? В любом случае API GetDlgItemInt будет работать для любого окна hwnd, если у окна hwnd есть дочерний элемент с указанным идентификатором. – mrsheen 06 янв. 11 2011-01-06 04:19:06

2 ответа

простой способ сделать это:

Смотрите также this MSDN page по использованию Edit Controls.

Для получения данных обратно, используйте GetWindowText, чтобы получить строку, а затем разобрать, что с помощью какой строки к междунар функции синтаксического анализа вы хотите (например. Strtol, atoi, sscanf и т.д.)

В то время как вы делаете нужно преобразовать вручную между int и string, это не так много кода, всего несколько дополнительных строк, поэтому гораздо меньше хлопот, чем конвертация для использования диалога.

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

Создан 15 мар. 11 2011-03-15 22:57:01 BrendanMcK

Илон Маск рекомендует:  Что такое код asp closelogfiles
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL