Было много вопросов о том, как определить основную нить процесса. Распространенным ответом было то, что «главного» потока не существует и все потоки эквивалентны.
Это неверный и весьма теоретический ответ. Основной поток — это первый поток, создаваемый процессом. Отладчик Visual Studio в окне «Потоки» может каким-то образом отличить основной поток от остальных. И я хочу знать во время загрузки статически связанных DLL (до WinMain), как это делает отладчик. В конечном итоге я хочу получить идентификатор этой темы.
Обновлено: Как заявил @IInspectable: «Хотя существует поток, который в конечном итоге инициализирует процесс, этот поток не имеет каких-либо отличительных особенностей. Он может завершиться в любой момент, в то время как другие потоки, принадлежащие этому процессу, продолжают выполняться». Я это знаю и полностью с этим согласен. Но я имею дело с гигантским продуктом 30-летней давности, состоящим из нескольких взаимодействующих EXE-файлов и нескольких сотен DLL. И многие разные библиотеки DLL (которые ничего не знают друг о друге и о EXE-файлах) имеют фрагменты кода, которые разрешено запускать только в основном потоке. В противном случае приложение выйдет из строя. Можно много раз повторять, что это очень плохой дизайн, но это то, что у меня есть и я ничего не могу с этим поделать. И это то, что я называю «практикой», в отличие от абстрактно истинных, но бесполезных утверждений.
Следовательно, нашему продукту нужен своего рода сервис, который сообщает, является ли текущий поток основным потоком процесса. Поэтому я выбрал очень простую DLL, которая статически связана со всеми EXE-файлами и почти со всеми другими DLL, и реализовал там такой «сервис» точно так, как описано в ответе @MSalters.
Это работало хорошо, пока недавно я не начал получать жалобы от разработчиков из офиса в другой стране о том, что выбранная DLL загружается в рабочий поток (что приводит к сбою приложения). Мы не можем воспроизвести это явление в нашем офисе, поэтому все, что у меня есть, — это снимки окна потока отладчика. Ниже приведен один из них; если это кому-то что-то скажет, я был бы очень рад узнать.
Дубликат: stackoverflow.com/questions/4891303/…
@user207421 user207421 Я знаю, что все потоки имеют одинаковую внутреннюю структуру и одинаково обрабатываются ОС. Так что с теоретической точки зрения они неотличимы. Но когда дело доходит до использования потоков, основной поток часто бывает особенным. В нашем приложении, которое содержит, вероятно, 15 миллионов строк кода C++ и C#, некоторые части могут выполняться только в основном потоке. Не спрашивайте меня, почему – это факт. Я также хотел бы знать, почему мой вопрос был отклонен.
Правильный ответ: «Основной темы нет». Хотя существует поток, который в конечном итоге инициализирует процесс, этот поток не имеет каких-либо отличительных особенностей. Он может завершиться в любой момент, пока другие потоки, принадлежащие этому процессу, продолжают выполняться. Более актуальный вопрос здесь: зачем вам это знать? Что вы планируете делать с этой информацией?
Это неверно.. - Ваше утверждение неверно. что тебе действительно нужно сделать?
@Илия, ты не ответил на мой вопрос.
@IInspectable Пожалуйста, прочитайте мой предыдущий комментарий, чтобы понять, зачем мне нужен идентификатор основного потока. И мне это нужно внутри очень глубокого слоя кода, который ничего не знает, скажем, о главном окне, из которого я мог бы получить такую информацию. Внутри DLL, которая используется в нескольких разных EXE-файлах. Поэтому я хотел инкапсулировать эту информацию в эту DLL. И не говорите мне о плохом дизайне — невозможно перепроектировать приложение, имеющее 15 миллионов строк кода и созданное более 30 лет назад.
@RbMm Пожалуйста, посмотрите мой ответ на IInspectable.
@user207421 user207421 Думаю, да. Я спрашиваю не из теоретического интереса. Мне нужно решить практическую задачу, и я ограничен в решениях, которые могу использовать.
@Ilia, вы можете вызвать NtGetNextThread, чтобы получить дескриптор текущего первого потока в вашем процессе. или используйте NtQuerySystemInformation с SystemExtendedProcessInformation или SystemProcessInformation и получите список идентификаторов потоков в вашем процессе. в вашем приложении — первый поток и будет «основным». несмотря на то, что это действительно неправильный дизайн. dll — это пассивный код. он экспортирует функции и интерфейсы. и экспортированный код, вызываемый другим кодом, в потоках, которые выбираете не вы. dll обычно не заботится о том, в каком потоке выполняется код, и не выбирает это.
Мне нужно решить практическую задачу - лучше вы спросите об этой проблеме, а не в "основной" теме.
«Как мне определить конкретную нить?» это вопрос, отличный от вопроса «Как найти основной поток процесса?». Вы просили последнее, но, видимо, вам нужно первое.
Судя по вашему снимку экрана, некоторый код в вашем процессе вызывает LoadLibraryW из других потоков. Не системный код, а «ваш» код. Или, может быть, некоторые компоненты в системе (например, av, edr) внедряют библиотеки DLL в ваш код через CreateRemoteThread + LoadLibraryW.
Я отредактировал вопрос по мере необходимости, поэтому теперь он сосредоточен на моей реальной проблеме, и добавил все имеющиеся у меня данные. Поэтому прошу возобновить этот вопрос.
Я почти уверен, что DLL не заботится ни на йоту о «основном» потоке - в этом нет ничего особенного. Однако их, вероятно, волнует поток графического интерфейса, который является особенным. Или, что еще хуже, потоки графического интерфейса, учитывая кажущийся беспорядок.
Вы не ответили на мой вопрос, имеете ли вы в виду «неверно» или «очень теоретически». Вы не можете поддерживать оба этих убеждения одновременно в логическом уме. Они взаимно противоречивы.
@user207421 user207421 Это правда, что я не могу предпочесть одну тему другой, потому что они неотличимы. Но верно также и то, что существует поток, известный как основной, который создается процессом первым. Те, кто говорит, что главного потока нет, на самом деле хотят навязать мне свою веру в то, что в правильно спроектированной программе код не должен зависеть от того, в каком потоке он выполняется. И они правы. Но это не помогает решить мою проблему. Реальная жизнь сложнее двоичной логики.
@Илья, ты вообще смотришь на свой скриншот? темы (2) со стартовым адресом LoadLibraryWStub - не дает вам идей? это какой-то внешний вызов процесса CreateRemoteThread с LoadLibraryW в качестве точки входа. классическая инъекция dll.
@MSalters Вы правы, существует много потоков графического интерфейса. Часть из них использует MFC, часть — WTL, третья часть — WPF. Как я уже сказал, продукт старый и огромный, поэтому такой микс разных технологий. Теоретически я мог бы получить основной поток из главного окна приложения, но это часто необходимо в библиотеках DLL, не имеющих доступа к главному окну. Мне нужно было подтверждение того, что моя реализация, которая фактически соответствует вашему ответу, верна.
@Ilia: Что заставляет вас думать, что «основной поток MFC» совпадает с «основным потоком WTL», либо совпадает с «основным потоком WPF», либо что любой из трех является первым потоком в процессе ? Кроме того, то же ошибочное предположение: в Win32 нет понятия главного окна приложения. И первое окно часто представляет собой заставку, поскольку основное приложение все еще загружается.
@MSalters Не интересно, что может быть, интересно только то, что у меня есть. У меня есть главное окно приложения, созданное в WinWain. Не важно, что главное окно не первое (из-за заставки). Имеет значение только то, что главное окно принадлежит первому потоку процесса, который обычно называют «основным» потоком. Это характерно для большинства приложений Windows, по крайней мере, для достаточно старых, и лучше это учитывать при ответе.
@Ilia: Потоки Win32 — это вещь Kernel32. Kernel32 не зависит от User32, где находится CreateWindow. Поэтому вы не можете вызвать какую-либо функцию Kernel32, чтобы получить статус потока, и это зависит от User32. И User32.dll не такой уж особенный, он тоже загружается, как и другие DLL (в отличие от Kernel32, который является особенным и гарантированно загружается раньше вашего DllMain).
@MSalters «Основной поток MFC»… «Основной поток WTL»… «Основной поток WPF» — таких понятий не существует. Существует понятие основного потока приложения — первого, созданного в процессе и выполняющего точку входа WinMain. Этот поток вполне может быть без графического интерфейса. Я тоже не понял, что вы имели в виду в своем последнем комментарии. Конечно, я не вызываю CreateWindow или другие функции, специфичные для User32, изнутри DllMain.





Отладчик может показать, какой поток (если таковой имеется) возник из точки входа EXE. Это не WinMain, потому что вашему компилятору нужна точка входа для настройки среды выполнения. Эта среда выполнения вызывает ваш WinMain.
Во время загрузки DLL (т. е. DllMain) ваш код выполняется под блокировкой загрузчика, и в это время вы мало что можете сделать. Но вызов DLL_PROCESS_ATTACH происходит именно в этом первом потоке. Так что вам не нужно ничего делать, чтобы узнать эту ветку. Вы можете смело использовать TlsAlloc, чтобы отметить эту тему для дальнейшего использования. Вы также можете смело вызывать GetCurrentThreadId, поскольку это функция kernel32, которая уже загружена до DllMain.
Но вызов DLL_PROCESS_ATTACH происходит именно в этом первом потоке. - DLL_PROCESS_ATTACH конкретно происходит в потоке, который вызывает LoadLibrary[Ex] (LdrLoadDll) - и это может быть любой поток. в случае, если это связано с зависимостями exe dll - да, «первый» поток
@MSalters О, слава богу, я получил один содержательный ответ, а не просто отрицательные голоса. Ваш совет был именно тем, что я изначально реализовал, и он работал хорошо до недавнего времени, когда некоторые разработчики (как ни странно, а не конечные пользователи) начали жаловаться, что на их машинах (статически связанная) DLL загружается в рабочий поток. Просматривая Интернет, я обнаружил, что поведение загрузчика было изменено в Windows 10, так что библиотеки DLL теперь можно загружать в потоки из пула потоков. Это основная причина моего вопроса.
@Ilia в Windows 10 ничего не меняется. Точка входа в Dll всегда вызывается из потока, который начинает загрузку dll.
@RbMm Я был удивлен, услышав это от вас, недавно увидев ваш ответ на вопрос «Почему при запуске консольного приложения Win32 появляются три неожиданных рабочих потока?», а также прочитав этот блог.
@RbMm Я знаю, что ты эксперт; Я просто запутался, потому что прочитал два поста о «параллельной загрузке» в Windows 10, и один из этих постов — ваш собственный. Почему ты обижаешься вместо того, чтобы объяснить, чего мне не хватает?
@Ilia DLL_PROCESS_ATTACH всегда вызывается из потока, который начинает загрузку dll (прямую или косвенную). и параллельный загрузчик здесь ничего не меняет. так что в win 10 и здесь ничего не изменилось. ответ неверен. Но вызов DLL_PROCESS_ATTACH происходит именно в этом первом потоке. - об этом я говорю в первом комментарии.
@RbMm «всегда вызывается из потока, который начинает загрузку dll» - но является ли этот поток основным (первым) потоком процесса? И если это так, то что такое параллельный загрузчик? Я говорю здесь о DLL, которая загружается из таблицы импорта процесса, никакого вызова LoadLibrary нет.
@RbMm: Обратите внимание, что в вопросе упоминаются DLL, которые загружаются при запуске программы, то есть не через LoadLibrary (или отложенную загрузку). В документации DllMain конкретно указано, что «Этот вызов [т. е. DllMain] выполняется в контексте потока, который вызвал изменение адресного пространства процесса, например, основного потока процесса или потока, вызвавшего LoadLibrary.». И последний вариант не применим.
@Ilia - как я говорю - DLL_PROCESS_ATTACH всегда вызывается из потока, который начинает загрузку dll (прямую или косвенную). dll можно начать загружать в любом потоке. параллельный загрузчик - выполняет некоторую часть задания загрузки dll в рабочих потоках (загружает дополнительные dl, одновременно разрешая импорт), но DLL_PROCESS_ATTACH всегда вызывается в начальном потоке. DLL, которая загружается из таблицы импорта процесса - для этой библиотеки DLL_PROCESS_ATTACH всегда вызывается поток, который затем вызывает точку входа exe ( WinMain или main, если хотите)
@Ilia DLL, которые загружаются при запуске программы - для этих DLL DLL_PROCESS_ATTACH всегда вызывается в вашем «основном» потоке. и параллельный загрузчик здесь в любом случае не реализован
@Ilia - в вопросе упоминаются библиотеки DLL, которые загружаются при запуске программы - против И я хочу знать, что время загрузки DLL (до WinMain) не самое лучшее, как вы это говорите (до WinMain). в этом случае вы действительно можете просто сохранить идентификатор текущего потока. но очень интересно для чего? вы позже планируете использовать это из dll
@RbMm Я объяснил причину и описал реальную проблему при редактировании моего вопроса. Буду признателен, если посмотрите. Я также попросил возобновить вопрос.
@Илья, я не вижу, где ты описываешь реальную проблему.
Ну, что это? Неправильно? или «очень теоретический»? Вы не можете иметь и то, и другое.