Суровая действительность разработки на C и C++ для Windows такова: для этой платформы никогда не существовало качественной, нативной реализации стандартной библиотеки этих языков. Стандартная библиотека должна абстрагировать механизмы базовой системы ради упрощения разработки переносимого программного обеспечения. С и C++ на Windows очень плохо состыкованы с интерфейсами операционной системы. В результате большая часть переносимых, или, так сказать, «почти всегда переносимых» программ, которые отлично работают практически везде, в Windows оказываются едва заметно «поломанными», в особенности — за пределами англоговорящего мира. Причины этого почти наверняка связаны с политикой тех или иных компаний, с искусственными ограничениями, а не с техническими особенностями систем, что лишь усугубляет положение. Эта статья посвящена рассказу о проблемах Windows-разработки на C и C++ и о том, как они выражаются. Здесь же будут представлены некоторые простые методы борьбы с этими проблемами при разработке переносимого ПО.



Существует множество реализаций компилятора С. Как такое может быть, что все они не в порядке, даже те, что были созданы одними из первых? Библиотека времени выполнения Microsoft C определила то, как стандартная библиотека C должна работать в Windows, а все остальные реализации ради совместимости следовали за ней. Исключением я считаю платформу Cygwin и её главный форк — MSYS2, несмотря на то, что они не унаследовали описываемых недостатков. Они, в ходе эволюции, так сильно изменились, что, по сути, представляют собой совершенно новые платформы, которые нельзя назвать полностью соответствующими «обычной» Windows.

На практике стандартные библиотеки C++ реализованы на базе стандартной библиотеки C. Именно поэтому у C++ имеются те же проблемы, что и у C. CPython избегает этих проблем. Хотя эта реализация Python и написана на C, на Windows она обходит неправильную стандартную библиотеку C и напрямую обращается к проприетарным интерфейсам. Реализации других языков программирования, вроде gc в случае с Go, попросту создаются не на базе механизмов C, вместо этого, с самого начала, делая всё как нужно, поступая так, как библиотекам времени выполнения C стоило бы поступать уже давно.

Если вы работаете над единственным крупным проектом, обход библиотек времени выполнения C — это не так уж и сложно. И вы, вероятно, уже так и поступаете, обращаясь к важному функционалу платформы. Вам, по правде, даже и не нужна библиотека времени выполнения C. Но если вы заняты разработкой множества небольших программ (как я), то написание особого кода для их поддержки в Windows быстро станет основной частью вашей работы. И, откровенно говоря, обеспечение поддержки Windows не стоит подобных усилий. Я пришёл к тому, что чаще всего просто принимаю то, что мне предлагают по умолчанию, хотя и знаю, что это приведёт к проблемам в Windows.

Прежде чем мы перейдём к деталям, хочу кое-что предложить тем, кто хочет легко и быстро решить вышеозначенные проблемы при работе с набором инструментов Mingw-w64, включая w64devkit. Это — моя библиотека libwinsane. Благодаря ей ваши консольные программы, написанные на C и C++, будут работать правильно. Она решает все проблемы, о которых идёт речь в этой статье, за исключением одной. При этом для применения этой библиотеки менять ваш исходный код не нужно. Достаточно просто связать её с вашей программой.

Что конкретно работает неправильно?


Существует две разновидности Windows API: «узкий», с суффиксом A (ANSI), и «широкий» (Unicode, UTF-16) с суффиксом W. Первый — это устаревший API, где активная кодовая страница отображает 256 байт на 256 конкретных символов (поддерживается до 256 символов). На типичных компьютерах, настроенных на работу с европейскими языками, это означает применение кодовой страницы Windows-1252. Грубо говоря, внутри Windows используется кодировка UTF-16, а вызовы, выполняемые через «узкий» интерфейс используют активную кодовую страницу для перевода «узких» строк в «широкие». В результате у обращений к «узкому» API есть лишь ограниченный доступ к системе.

Кодировка UTF-8 изобретена в 1992 году, она была стандартизирована в январе 1993 года. В последующие годы мир Unix принял эту кодировку из-за её обратной совместимости с существующими интерфейсами. Программы могли читать и записывать Unicode-данные, могли пользоваться Unicode-путями, обрабатывать Unicode-аргументы, считывать значения Unicode-переменных окружения и устанавливать их значения. При этом в программах ничего не нужно было менять. В наши дни кодировка UTF-8 стала самым распространённым форматом кодирования текстовой информации. Во многом это так благодаря развитию Всемирной паутины.

В июле 1993 года Microsoft, с выходом Windows NT 3.1, представила «широкий» API Windows, сделав ставку на кодировку UCS-2 (позже — UTF-16), а не на UTF-8. Это, как оказалось, было ошибкой, так как UTF-16 практически во всём уступает UTF-8. Правда, надо признать, что тогда некоторые проблемы не были особенно очевидными.

Главная проблема заключается в том, что стандартные библиотеки C и C++ подключены лишь к «узкому» интерфейсу Windows. Стандартная библиотека, а значит — и типичное переносимое приложение на Windows, не может обрабатывать ничего кроме ASCII-кода. Это приводит к тому, что эти программы не могут выполнять следующие действия в том случае, если они предусматривают применение символов, отличных от ASCII-символов:

  • Принимать аргументы.
  • Читать и устанавливать значения переменных окружения.
  • Работать с путями.
  • Считывать и выводить данные в консоли.

Выполнение любого из этих действий требует вызова проприетарных функций. При этом Windows рассматривается в роли особой целевой платформы. Это часть того, что делает портирование программ на Windows весьма неприятным занятием. Разумное решение этой проблемы могло бы выглядеть как организация поддержки UTF-8 библиотекой времени выполнения C и подключение её к «широкому» API. Ещё один вариант решения проблемы заключается в том, что «узкий» API можно было бы перевести на UTF-8, постепенно отказываясь от концепции применения старой кодовой страницы. Это, в теории, то, что представляет собой «кодовая страница» UTF-8, хотя подобные решения оказываются работоспособными не всегда. При резком переходе на UTF-8 возникли бы проблемы с совместимостью. Но до совсем недавнего времени такая возможность не была представлена даже неким, условно говоря, «переключателем». Почему в Windows не может быть такого «переключателя» включив который можно обеспечить программам возможность нормально работать? Работать так же хорошо, как и на других платформах.

Как решить почти все проблемы поддержки Unicode?


В 2019 году Microsoft представила возможность, позволяющую программам при запуске запрашивать UTF-8 в роли их активной кодовой страницы. Большее, чем раньше, количество функций «узкого» API получило поддержку UTF-8. Это похоже на тот «переключатель», о котором я мечтал, но мою радость несколько омрачает то, что для реализации этих возможностей надо особенным образом встраивать в бинарники некоторый объём неприглядного XML-кода. Но теперь у нас, по крайней мере, появилась некая стандартная возможность работать с UTF-8.

При использовании Mingw-64 это означает необходимость создания такого файла ресурсов:

#include <winuser.h>
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "utf8.xml"

Далее, компилируем это с помощью windres:

$ windres -o manifest.o manifest.rc

То, что получилось, связываем с программой. Это, удивительным образом, обычно работает! Программы могут получать доступ к Unicode-аргументам, могут работать с переменными окружения, с путями (в том числе — с помощью fopen). В общем, всё работает так же, как, уже десятилетия, работает на других платформах. Так как активная кодовая страница устанавливается в момент загрузки программы, это событие происходит до конструирования argv (из GetCommandLineA), и именно поэтому всё это и работоспособно.

В качестве альтернативы можно создать так называемую «параллельную сборку» (SxS, side-by-side assembly), поместив этот XML-код в файл с тем же именем, что и у EXE-файла, но с расширением .manifest (после расширения .exe), а после этого положив этот файл около EXE-файла. Пользуясь этим приёмом, стоит помнить о существовании SxS-кеша (WinSxS). Изменения в соответствующих файлах могут быть видны лишь через некоторое время после их выполнения.

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

SetConsoleOutputCP(CP_UTF8);

Это, конечно, так себе занятие, но, по крайней мере, теперь всё не так плохо, как раньше. Правда, эта настройка имеет отношение лишь к выводу данных. То есть — программы могут писать в консоль, пользуясь UTF-8, но не читать из консоли. К сожалению, возможность чтения UTF-8-текстов из консоли всё ещё не работает. При установке кодовой страницы для входных данных нам сообщают об успешном проведении операции, но этим всё и ограничивается.

SetConsoleCP(CP_UTF8);  // не работает

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

Преобразование текстовых потоков


Ещё одна давняя проблема программирования для Windows на C и C++ заключается в наличии отличающихся друг от друга «текстовых» и «двоичных» потоков, унаследованных от DOS. В основном это означает автоматическое преобразование символов перевода строки (CRLF и LF). Стандарт C это недвусмысленно позволяет, но на Unix-подобных платформах никогда не делалось различия между текстовыми и двоичными потоками.

Стандарт, кроме того, указывает на то, что стандартные потоки ввода, вывода и ошибок открываются в виде текстовых потоков, при этом нет переносимого способа переключить поток в двоичный режим. Это серьёзный недостаток стандарта. В Unix-подобных системах это неважно, но в Windows это означает, что программа не может читать или писать двоичные данные при работе со стандартными потоками, не вызывая нестандартные функции. Это означает ещё и то, что чтение стандартных потоков и запись в них выполняются медленно. Часто это становится узким местом программ, если только создатель программы не обойдёт этот недостаток.

Лично я предпочитаю писать двоичные данные в стандартный поток вывода, в том числе — видеоданные, и иногда пользуюсь двоичными фильтрами, которые тоже читают двоичные входные данные. Я делаю это так часто, что, вероятно, в половине моих C-программ, в функции main, имеется такой фрагмент кода, обеспечивающий их правильную работу в Windows:

    #ifdef _WIN32
    int _setmode(int, int);
    _setmode(0, 0x8000);
    _setmode(1, 0x8000);
    #endif

Эта магическая формула устанавливает стандартные потоки ввода и вывода в библиотеке времени выполнения C в двоичный режим. При этом не нужны никакие заголовочные файлы, код получается компактным, простым и самодостаточным.

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

Библиотека libwinsane


В начале статьи я говорил о моей библиотеке libwinsane. Она позволяет исправить все вышеописанные проблемы путём простого связывания её с программой. Сюда входит использование раздела .rsrc XML-манифеста, настройка консоли на вывод UTF-8-текстов, перевод стандартных потоков в двоичный режим. Всё это выполняется до вызова функции main (с помощью конструктора GCC). Я называю мою разработку «библиотекой», но это, на самом деле, всего лишь объектный файл. Эта разработка не может быть представлена в виде статической библиотеки, так как она должна быть связана с программой, несмотря на то, что в коде программы она нигде не упоминается.

Вот программа:

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    char *arg = argv[argc-1];
    size_t len = strlen(arg);
    printf("%zu %s\n", len, arg);
}

В обычных условиях её компилируют и запускают так:

C:\>cc -o example example.c
C:\>example π
1 p

Как всегда, Unicode-аргументы по-тихому ужимаются до одного байта. А теперь свяжем эту программу с libwinsane:

C:\>gcc -o example example.c libwinsane.o
C:\>example π
2 π

Это приведёт к тому, что в Windows программа заработает так же, как на любой другой платформе.

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

С какими проблемами вы сталкивались, программируя для Windows на C и C++?

Комментарии (56)


  1. vilgeforce
    20.01.2022 16:42
    +3

    А можно ссылочку, где A-API признается устаревшим самими авторами? Залез в MSDN - не вижу ничего про deprecated в CreateFileA, например...


    1. vitalijs
      20.01.2022 16:53
      +6

      Вы не видите потому что это не правда


      1. vilgeforce
        20.01.2022 16:54
        +1

        А я уж подумал все, придется мне теперь только с W-версиями работать ;-)


        1. kovserg
          20.01.2022 20:04

          Придётся работать еще и с CreateFile2


          1. vilgeforce
            21.01.2022 13:13

            О, такого зверя я еще не встречал :-)


    1. rat1
      20.01.2022 17:22
      +6

      Просто если хочешь многоязыковую поддержку - забудь про них и все. Они не устарели, но большинство A-функции - это тупо прослойки между W-функциями и системой (не все). А это снижение производительности и узкая заточенность под твою страну. Если ты живешь в 2000-м - милости просим, используй A-функции, если ты давно переехал в Unicode-мир, то незачем.


      1. vilgeforce
        20.01.2022 17:53
        +2

        Ну тут смотреть надо... Так-то все эти A- и W- тоже прокладка между пользовательским кодом и всякими там Nt- и Zw- APIшками ;-)


      1. DistortNeo
        20.01.2022 18:03
        -3

        Немного не соглашусь. Современный мир — это кросс-платформенные приложения и доминирование UTF-8.

        И лично я не вижу принципиальной разницы между конвертированием UTF-8 в UTF-16 для вызова W-функций и вызовов A-функций с конвертацией внутри системной библиотеки.

        Я бы, наоборот, похоронил кодовые страницы и сделал UTF-8 единственной возможной кодировкой для A-функций, вдохнув тем самым в них новую жизнь. А W-функции оставил исключительно для Windows-специфичных задач.


        1. rat1
          20.01.2022 18:38
          +2

          В случае с UTF-8 есть определённые проблемы при работе с символами, т.к. 1 символ может быть как 1 байт, так и 4. Могут быть проблемы с производительностью при анализе строки


          1. DistortNeo
            20.01.2022 18:50
            +6

            А c UTF-16 таких проблем, что, нет?


            1. rat1
              20.01.2022 18:57
              -6

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


              1. Tujh
                20.01.2022 18:58
                +10

                Все символы по два байта в UCS2, но не в UTF16, где тоже существуют последовательности из нескольких код-поинтов.


                1. rat1
                  20.01.2022 21:18
                  -3

                  Вы что-то путаете, ucs-2 старая схема, которая сейчас не используется. utf16 и ucs-2 идентичны для 65000 символов. 65000 вполне хватает для большинства кодировок. И по сути нет необходимости в расширении. У utf-8 проблемы начинаются сразу, например, с русских символов.


                  1. DistortNeo
                    20.01.2022 21:36

                    Во-первых, использование символов с кодами, большими 0xFFFF, уже давно стало повсеместным — это эмодзи. Во-вторых, 1 символ ≠ 1 кодпоинт.


                    И никаких проблем с UTF-8 нет, вы просто работаете с байтовым представлением строки. А в случае с UTF-16 — с двухбайтовым. Но в обоих случаях напрямую работать с символами вы не можете.


                    1. firehacker
                      21.01.2022 03:39
                      +2

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


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


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


                      1. Videoman
                        21.01.2022 13:35
                        +3

                        Дело не только в эмодзи. Там еще куча всего: индексы, префиксы, умлаут, кратка и т.д. Даже в UTF-32, где код-поинт равен код-юниту, символ всё равно может состоять из нескольких код-поинтов и запросто занимать больше 4х байт.


                    1. rat1
                      21.01.2022 17:14

                      Значит я что-то путаю)


                  1. Playa
                    20.01.2022 21:42

                    Суррогатные пары...


                  1. Tujh
                    21.01.2022 11:04
                    +2

                    Об этом на сайте самих M$ много и хорошо написано, начинать можно от сюда: https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms904394(v=msdn.10)?redirectedfrom=MSDN

                    Фишка в том, что СЕ поддерживала только W-функции и не имела слоя совместимости с А-функциями.

                    65000 вполне хватает для большинства кодировок

                    ответ с той же страницы

                    Surrogates provide additional character support for the languages that need more than the 65,536 characters in the 16-bit Unicode code space. For example, the Chinese speaking community alone uses over 55,000 characters.


                1. Dmitriy00
                  21.01.2022 14:55
                  -2

                  свыше 2 байт практически ненужные символы (типа вымерших языков)


          1. permeakra
            20.01.2022 18:57
            +4

            Эти проблемы возникают при работе с любым юникодом. Потому что unicode codepoint (т.е. то, что программисты называют multibyte character) это ни разу не обязательно один символ на экране. Потому что есть combining characters и лигатуры. Последнее вообще делает количество символов в конкретной строке шрифтозависимым.


            1. DistortNeo
              20.01.2022 19:35

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


        1. mayorovp
          21.01.2022 13:11
          +1

          В идеале да, неплохо бы сделать A-функции работающими с UTF-8, хотя бы в рамках одного процесса, и забыть о проблемах.


          А вот в реальности вы русские буквы не можете прочитать из консоли через ReadFile когда кодовую страницу 65001 используете, так что костыли будут в программах ещё долго.


      1. KanuTaH
        20.01.2022 20:41

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

        Как бы в обозримом будущем не пришлось забыть как раз-таки про W функции:

        https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page

        Наконец-то может свершиться, и мейнстримом в Windows станет UTF-8, как везде, а не эта убогая система с A/W функциями.


    1. Tujh
      20.01.2022 18:14
      +2

      Оно не устарело, просто есть нюанс, что A-функции вызывались напрямую в системах 9х, а вот W-функции в этих же операционках были прослойками, транслирующими unicode строки в однобайтные и вызывающими A-варианты.

      В NT-системах (а все M$ операционки, начиная с 2000, XP и далее - это ядро NT) всё наоборот, W-функции являются основными и родными для ядра, а А-функции лишь прослойки для обратной совместимости в которых однобайтная строка будет преобразована в wide-вариант с применением настроек системы "по умолчанию". Поэтому и существует настройка в недрах системы как "использовать кодировку для не unicode-программ", что порой приводит к неоднозначным результатам, когда преобразование выполняется не как ожидает программист, при этом не во всех системах.


  1. vitalijs
    20.01.2022 16:53
    +3

    Читал читал, но так и не понял что это за такая стандартная библиотека которая не поддерживает Unicode. Можете пояснить что под этим подразумевается?


    1. DistortNeo
      20.01.2022 17:47
      +3

      Вот эта библиотека:
      https://en.cppreference.com/w/c/header

      Эта библиотека кросс-платформенная и расчитана на работу с POSIX-совместимыми операционными системами. Но Windows таковой не является, из-за чего и вылезают описанные в статье спецэффекты.

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

      Соответственно, поддержки Unicode в стандартной библиотеке нет просто потому, что эта поддержка не нужна. А использование UTF-8 в Unix-подобных операционных системах — это не более, чем негласное соглашение.

      Ну а Windows пошла своим путём. Придумала файловую систему, где имена файлов — это UCS2 (а затем и UTF-16), а однобайтовый API стал зависим от кодовой страницы. Полноценный переход однобайтового API на UTF-8 начался лишь недавно.


  1. Sabirman
    20.01.2022 17:20
    -7

    Недавно наткнулся на то что std::map в Visual Studio C++ крайне медленный. Причём в C# он работает прекрасно.


    1. rat1
      20.01.2022 18:39
      +2

      Заюзай std::unordered_map )


      1. igand
        21.01.2022 14:55

        unordered_map тоже не идеален. Для интереса сравнил с гугловским dense_hash_map. Вставка 10 миллионов чисел, затем их же поиск. Результаты на стареньком ноутбуке с Core i5-3317U 1.7GHz:

        unordered_set - 7.15 секунд

        dense_hash_map - 1.75 секунды


        1. Videoman
          21.01.2022 15:07
          +1

          А что тут удивительного. Любой STL контейнер проиграет почти любому специально заточенному контейнеру под те или иные данные и условия. На реализацию STL накладываются следующие условия:

          • интерфейс
          • универсальность по данным
          • гарантии по итераторам
          • гарантии по исключениям


    1. mayorovp
      21.01.2022 13:13
      +2

      Э-э-э, а где вы в C# нашли std::map?


  1. crea7or
    20.01.2022 17:42

    Bы серьёзно? std::basic_string<T> ... std::basic_string_view<T>. А UCS-2 в отличии от UTF-8 было просто удобнее читать и считать символы/байты (wchar_t / 2) - не надо анализировать токены.


    1. DistortNeo
      20.01.2022 17:50
      +2

      Это раньше UCS-2, а теперь там UTF-16. И те же сложности, что и с UTF-8.


  1. Videoman
    20.01.2022 18:10
    +2

    А можно поподробнее объяснить какую проблему мы решаем в С++? Вот прямо сейчас посмотрел как работает С++ Runtime от Microsoft внутри: там std::cout после себя передает консоли просто байты, не интерпретируя их каким-то особым образом.
    Вот уже лет 5, с тех пор как мы решили перевели под Windows свои консольные приложения в UTF-8, всё что нужно сделать, это компилировать в режиме wide chars и поставить:

    ::SetConsoleOutputCP(CP_UTF8)
    После этого просто выводим строки std::string в которых лежит UTF-8 напрямую в std::cout и нет никаких проблем. std::string также всё равно какая там кодировка, главное не работать с код-юнитами (char), как с символами.


    1. Paulus
      21.01.2022 02:53
      +2

      Несколько лет назад мы делали проект для израильтян. В GUI всё было хорошо, а вот в консоли нет. Как обычно, если что-то не работает -- читай документацию, оказалось тогда консоль Windows не работала с языками, где пишут справа налево, по определению. Теперь что-нибудь изменилось?


      1. kovserg
        21.01.2022 11:51

        А некоторые еще и сверху вниз пишут.


    1. mayorovp
      21.01.2022 13:15

      А теперь попробуйте в этой кодировке прочитать из консоли что-нибудь.


      1. Videoman
        21.01.2022 13:41
        +1

        Я в курсе. Мы же с вами вроде как прочитали статью и именно её обсуждаем. Цитирую:

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


        1. mayorovp
          21.01.2022 14:44

          А библиотека эту проблему не решает? Тогда она и правда не нужна.


          1. Videoman
            21.01.2022 14:58

            Вот что автор пишет по этому поводу:

            Если вам нужно читать Unicode-данные из консоли в интерактивном режиме — вам не остаётся ничего кроме обхода библиотеки времени выполнения C, так как вышеописанный механизм всё ещё неработоспособен.
            Т.е. звучит это так, что с библиотекой, что без библиотеки, мы имеем один и тот же результат. А заголовок «Капля здравого смысла...» ????????‍♂️.


  1. ryo_oh_ki
    20.01.2022 18:35
    +1

    Эта магическая формула устанавливает стандартные потоки ввода и вывода в библиотеке времени выполнения C в двоичный режим.

    Ничего магического, и можно прямо в Unicode переключить как ввод так и вывод:

    _setmode( _fileno( stdin ), _O_U16TEXT );
    _setmode( _fileno( stdout ), _O_U16TEXT );


  1. Travisw
    20.01.2022 18:49

    Надо было считать данные с edit в программу написанной на winapi, а там значения не char*, а wchar* потом их перевести в char*, и вывести в cout, а wchar* выводится в консоль через wcout


  1. oleg-m1973
    20.01.2022 22:31

    С какими проблемами вы сталкивались, программируя для Windows на C и C++?

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


  1. Daddy_Cool
    21.01.2022 15:34
    +1

    "библиотекам времени выполнения" - это же  "runtime library"! Не сразу дошло. Но википедия говорит, что так можно.


    1. mayorovp
      21.01.2022 16:23
      +3

      Э-э-э, а как ещё можно перевести "runtime library"?


  1. anonymous
    00.00.0000 00:00


    1. old2ev
      22.01.2022 01:19
      -2

      Предположим у нас есть Posix-совместимый код на C или C++ (такого кода очень много, сомневаться не стоит). И у нас появляется задача - сделать код полностью кроссплатформеным. Большая часть дела и так сделана, данный код пашет абсолютно где угодно - Linux, FreeBSD, Mac OS, Android и IOS ибо Posix-совместимо (по большей части). Но Windows по правилам не играет - Windows хочет быть долбанутым пациентом дурки. И из-за этого у нас есть два стула:

      • оборачивать весь код макросами для совместимости и писать под Windows используя наиболее близкое к Posix окружение(Cygwin/MinGW и т.п.);

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

      Казалось бы "первый стул удобней", но то тут то там возникают весьма интересные проблемы из разряда отсутствия поддержки именно UTF-8 в функциях работы с файлами начиная с обычных Сишного fopen, закначивая всем std::filesystem. Posix окружения нет, а его очень сильно не хватает поэтому и существет целый набор эти костылей к Windows которые раз за разом шлефуются. По итогу, если ты используешь что-то что плохо работает в MinGW или Cygwin то добро пожаловать в MSVC - пиши в два раза больше кода.

      Мне лично, да и большенству больше чем уверен плевать, что там за тёрки у Microsoft с их CRL и прочую их закрытую муть, если эта муть работает нормально. Программистам нужна переносимость на уровне исходного кода которая была в C и C++ изначально и без лишнего пердолинга с макросами для совместимости. Врятли кому-то хочется тратить лишние человекочасы на перенос кода на одну "не такую как все" платформу.


      1. Videoman
        22.01.2022 17:11

        Насчет fopen согласен, но это все С. Насчет С++ и std::filesystem — вы что-то не так делает, потому-что внутри он полностью юникодный и скорее всего у вас проблема где-то на входе или выходе. Идеологически правильно, в С++20 и дальше, передавать std::filesystem строки std::u8string, std::u16string, std::u32string. Это не просто строки с разным размером типа CharT. Стандартом подразумевается что это именно UTF-8/16/32 строки.
        До С++20, т.к. нет поддержки std::u8string, с помощью специального адаптера нужно явно указать что строка в кодировке UTF-8:

        const std::string uft8in = "...."; // UTF-8 строка
        const std::path some_path = std::u8path(uft8in); // явно указываем что принимаем UTF-8
        const std::string uft8out = some_path.u8string(); // явно указываем что хотим UTF-8

        Думаю то, что у вас сейчас работает на Linux, это просто следствие того, что там везде подразумевается UTF-8, но это не строгое соответствие стандарту С++.


  1. buratino
    22.01.2022 11:30
    +3

    Суровая действительность разработки на C и C++ для Windows такова: для этой платформы никогда не существовало качественной, нативной реализации стандартной библиотеки этих языков.

    Заявляет автор. А претензии идут к кодировке текста и кодовым страницам. Однако позвольте.. У Кернигана и Ричи никакой кодировки не было в принципе. Точнее, была одна на все - и на исходники, и на вывод сообщений. И, соответвенно, в stdlib никакой кодировки и кодовой страницы. И в, дай бог памяти, MSC 6.0, первом компиляторе для Windows, на уровне именно стандарных библиотек ничего этого не было. Вся поддержка окошек и пользовательского интерфейса шла через нестандарные для C/С++ библиотеки.

    Говоря о командной строке Windows нельзя не заметить кросивое

    Microsoft Windows [Version 10.0.22000.318]
    (c) Корпорация Майкрософт (Microsoft Corporation). Все права защищены.

    C:>chcp
    Текущая кодовая страница: 866

    C:>example π αβγ

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


    1. mayorovp
      22.01.2022 18:10

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

      А что, не должны?)


      1. buratino
        22.01.2022 22:11

        Microsoft Windows [Version 6.1.7601]
        (c) Корпорация Майкрософт (Microsoft Corp.), 2009. Все права защищены.

        C:>chcp
        Текущая кодовая страница: 866

        C:>example ? ???

        W7

        При этом

        int _tmain(int argc, _TCHAR* argv[])
        { _setmode(_fileno(stdout), _O_U8TEXT);	
        	wprintf(L"example π αβγ\n");

        вполне себе в консоль выводит


        1. mayorovp
          23.01.2022 00:04

          Это вы просто привели поведение в W7. Но это не ответ на вопрос "как консоль должна работать"!


          1. buratino
            23.01.2022 00:22

            какой смысл в этом вопросе? Я не разработчик консолей, вы скорей всего тоже. Вот у нас есть данность - в W7 консоль работает так, в W10-11 - немного иначе, а пользоваться в це всей этой радостью с бубном и плясками - вот так, или вот эдак.


        1. DistortNeo
          23.01.2022 00:06

          Наверное, это связано с тем, что wprintf в конечном итоге вызывает WriteConsoleW ? Принцип всё есть файл в Windows не работает.


          1. Videoman
            23.01.2022 13:52

            Нет. Вы просто путаете разные API. Есть отдельный API консоли, где вы указываете handle именно консоли. Их может быть несколько у приложения. Там можно выводить текс в разных позициях, в разных режимах, разным цветом и т.д… Также есть API стандартного ввода-вывода, которому все равно что передается, он передает байты и там всё есть файл: ReadFile/WriteFile.