Действительно ли NtDll экспортирует функции времени выполнения C, и могу ли я использовать их в своем приложении?

Я просматривал таблицу экспорта NtDll на моем компьютере с Windows 10 и обнаружил, что она экспортирует стандартные функции времени выполнения C, такие как memcpy, sprintf, strlen, так далее.

Означает ли это, что я могу вызывать их динамически во время выполнения через LoadLibrary а также GetProcAddress? Гарантируется ли это для каждой версии Windows?

Если это так, то можно полностью удалить библиотеку времени выполнения C (просто используя функции CRT из NtDll), что делает мою программу меньше?

3 ответа

Решение

Нет абсолютно никаких причин для вызова этих недокументированных функций, экспортируемых NtDll. Windows экспортирует все основные функции времени выполнения C как документированные оболочки из стандартных системных библиотек, а именно Kernel32. Если вы абсолютно не можете связаться с C Runtime Library *, то вам следует вызывать эти функции. Для памяти у вас есть основной HeapAlloc а также HeapFree (или возможно VirtualAlloc а также VirtualFree), ZeroMemory, FillMemory, MoveMemory, CopyMemory и т. д. Для работы со строками все важные функции CRT находятся с префиксом l: lstrlen, lstrcat, lstrcpy, lstrcmp и т. д. wsprintf (и его брат wvsprintf), который не только имеет другой префикс, но также не поддерживает значения с плавающей запятой (сама Windows не имела кода с плавающей запятой в первые дни, когда эти функции были впервые экспортированы и задокументированы.) Существует множество других вспомогательных функций. тоже что копируют функционал в ЭЛТ, вроде IsCharLower, CharLower, CharLowerBuff, так далее.

Вот старая статья базы знаний, которая документирует некоторые эквиваленты Win32 для функций времени выполнения C. Вероятно, существуют другие важные функции Win32, которые вам, вероятно, понадобятся, если вы повторно реализуете функциональность CRT, но это прямые замены.

Некоторые из них абсолютно необходимы инфраструктуре операционной системы и будут вызываться внутри любой реализации CRT. Эта категория включает в себя такие вещи, как HeapAlloc а также HeapFree, которые несут ответственность операционной системы. Библиотека времени выполнения только оборачивает их, предоставляя приятный стандартный интерфейс C и некоторые другие тонкости поверх подробностей уровня ОС. Другие, такие как функции манипуляции со строками, являются просто экспортируемыми оболочками вокруг внутренней версии CRT для Windows (за исключением того, что это действительно старая версия CRT, исправленная в некоторый момент истории, за исключением, возможно, серьезных дыр в безопасности, которые были исправлены с годами). Третьи почти лишние или кажутся ZeroMemory а также MoveMemory, но на самом деле экспортируются, чтобы их можно было использовать из сред, в которых нет библиотеки времени выполнения C, например классической Visual Basic (VB 6).

Интересно также отметить, что многие из "простых" функций библиотеки C Runtime реализованы компилятором Microsoft (и других поставщиков) как встроенные функции со специальной обработкой. Это означает, что они могут быть высоко оптимизированы. По сути, соответствующий объектный код выдается встроенным, прямо в двоичный файл вашего приложения, избегая необходимости потенциально дорогого вызова функции. Разрешение компилятору генерировать встроенный код для чего-то вроде strlen, который вызывается постоянно, почти наверняка приведет к лучшей производительности, чем необходимость оплачивать вызов функции для одного из экспортируемых API-интерфейсов Windows. Компилятор не может "встроить" lstrlen; он вызывается так же, как и любая другая функция. Это возвращает вас к классическому компромиссу между скоростью и размером. Иногда меньший двоичный файл быстрее, но иногда это не так. Отсутствие связи с CRT приведет к созданию меньшего двоичного файла, так как он использует вызовы функций, а не встроенные реализации, но, вероятно, не даст более быстрый код в общем случае.

* Однако по разным причинам вам действительно следует ссылаться на библиотеку времени выполнения C, поставляемую в комплекте с вашим компилятором, не в последнюю очередь это обновления безопасности, которые можно распространять на все версии операционной системы через обновленные версии библиотек времени выполнения., У вас должна быть действительно веская причина не использовать CRT, например, если вы пытаетесь создать самый маленький исполняемый файл в мире. И отсутствие этих функций будет только первым из ваших препятствий. CRT обрабатывает много вещей для вас, о которых вам обычно даже не нужно думать, таких как запуск и запуск процесса, настройка стандартной среды C или C++, анализ аргументов командной строки, запуск статических инициализаторов, реализация конструкторов. и деструкторы (если вы пишете на C++), поддержка структурированной обработки исключений (SEH, которая также используется для исключений C++) и так далее. У меня есть простое приложение на C для компиляции без зависимости от CRT, но это заняло немало хлопот, и я, конечно, не рекомендовал бы его для чего-то серьезного. Мэтью Уилсон давно написал статью об Избегании библиотеки времени выполнения Visual C++. Он в значительной степени устарел, потому что фокусируется на среде разработки Visual C++ 6, но большая часть большого изображения все еще актуальна. Я отчетливо помню, что Мэтт Пьетрек написал статью об этом в журнале Microsoft тоже давно, но я нигде не могу найти его копию в Интернете (все ссылки кажутся мертвыми).

Если вас беспокоит только необходимость распространения DLL-библиотеки C Runtime Library вместе с вашим приложением, вы можете рассмотреть статическое связывание с CRT. Это встраивает код в ваш исполняемый файл и устраняет необходимость в отдельных DLL. Опять же, это раздувает ваш исполняемый файл, но делает его проще для развертывания без необходимости установки или даже ZIP-файла. Естественно, большое предостережение заключается в том, что вы не сможете воспользоваться дополнительными обновлениями безопасности для библиотек CRT; Вы должны перекомпилировать и распространить приложение, чтобы получить эти исправления. Для игрушечных приложений без других зависимостей я часто выбираю статическую ссылку; в противном случае динамическое связывание все еще является рекомендуемым сценарием.

В NtDll есть несколько функций времени выполнения C. Согласно Windows Internals они ограничены функциями манипуляции со строками. Есть и другие эквиваленты, такие как использование HeapAlloc вместо malloc, так что вы можете сойти с рук в зависимости от ваших требований.

Хотя эти функции признаны публикациями Microsoft и уже много лет используются программистами ядра, они не являются частью официального Windows API, и вы не должны использовать их для чего-либо, кроме игрушечных или демонстрационных программ, поскольку их присутствие и функция могут менять.

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

Означает ли это, что я могу вызывать их динамически во время выполнения через LoadLibrary и GetProcAddress?

да. еще больше - почему бы не использовать ntdll.lib (или ntdllp.lib) для статической привязки к ntdll? и после этого вы можете напрямую вызывать эти функции без какого-либо GetProcAddress

Гарантируется ли это для каждой версии Windows?

от nt4 до win10 существует много функций времени выполнения C в ntdll, но их набор отличается. обычно это растет от версии к версии. но некоторые из них менее функциональны, чем msvcrt.dll. скажем например printf из ntdll не поддерживается формат с плавающей запятой, но в целом функционал такой же

можно вообще отбросить библиотеку времени выполнения C (просто используя функции CRT из NtDll), что делает мою программу меньше?

да, это возможно на 100%.

Другие вопросы по тегам