Программный вызов функции "main" в Windows
У меня есть стороннее консольное приложение. Мне нужно запустить его из моего приложения, но я не могу запустить его как отдельный процесс (потому что мне нужно работать со своими зависимостями: заполнить таблицы импорта вручную, настроить крючки и т.д.). Поэтому, вероятно, я должен вызвать функцию main
этого исполняемого файла вручную. Вот как я пытаюсь это сделать:
- Загрузите этот EXE с помощью
auto hMod = LoadLibrary("console_app.exe")
- Заполнить таблицу импорта этого exe вручную
- Получить точку входа этого EXE и назвать ее
И я застрял с последним шагом.
Вот как я пытаюсь вызвать точку входа:
void runMain(HINSTANCE hInst)
{
typedef BOOL(WINAPI *PfnMain)(int, char*[]);
auto imageNtHeaders = ImageNtHeader(hInst);
auto pfnMain = (PfnMain)(DWORD_PTR)(imageNtHeaders->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)hInst);
char* args[] = { R"(<console_app_path>)", R"(arg1)", R"(arg2)" };
pfnMain(3, args);
}
Это работает. Но он работает так, как будто нет аргументов.
Где я ошибаюсь? Как запустить исполняемый файл внутри моего процесса с помощью аргументов? Спасибо.
UPDATE:
Я исследовал, как мой сторонний exe получает аргументы cmd и обнаружил, что:
- Он не импортирует
GetCommandLine
вообще и не называет его
- После
call _initterm
вызов argc
и argv
аргументы доступны через cs:argc
и cs:argv
(см. рисунки ниже)
![введите описание изображения здесь]()
- Аргументы CMD, которые я передаю в основное приложение консоли, переносятся на
дочерний EXE тоже.
Можете ли вы объяснить, пожалуйста, что _initterm
действительно и где аргументы CMD на самом деле хранятся?
Ответы
Ответ 1
Вы вызываете точку входа приложения, а не int main(int, char**)
. Теперь вы, возможно, читали, что точка входа в программу на С++ int main(int, char**)
, но это просто перспектива С++.
Учетная точка Win32 отличается; точкой входа является int (*)(void);
. Компонент Visual Studio ищет int mainCRTStartup(void);
и использует это, если вы не укажете другую точку входа с /ENTRY
. Реализация по умолчанию mainCRTStartup
вызывает GetCommandLine()
для заполнения argv[]
перед вызовом main(argc,argv)
. В mainCRTStartup
есть и другие вещи, которые могут возникнуть: запустить глобальные ctors, инициализировать состояние CRT,...
Конечно, предполагая, что другая программа была скомпилирована с Visual С++, но на любом языке, на котором она была написана, она должна вызывать GetCommandLine
.
Теперь, для вашей проблемы, здесь интересное наблюдение: GetCommandLine()
возвращает указатель writeable. Вы можете перезаписать существующую командную строку. Конечно, если вы управляете таблицами импорта, вы решаете, что означает GetCommandLine
. (Помните, как обычно есть варианты A и W).
Одно предупреждение: MSVCRT не предназначен для инициализации дважды, ни статической версии, ни DLL. Практически говоря, вы не можете его использовать, и это будет больно.
[править]
Ваше обновление показывает вызов _initterm
. Это функция MSVCRT, как я уже намекал. В частности,
/***
*crtexe.c - Initialization for console EXE using CRT DLL
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
...
/*
* routine in DLL to do initialization (in this case, C++ constructors)
*/
extern int __cdecl _initterm_e(_PIFV *, _PIFV *);
extern void __cdecl _initterm(_PVFV *, _PVFV *);
DLL MSVCRT вызывает GetCommandLine()
от имени EXE.
Ответ 2
точка входа исполняемого файла (EP
) не имеет аргументов - поэтому вы и не можете направить вызов с помощью аргументов.
обычное приложение получило аргументы путем разбора командной строки. [w]mainCRTStartup
сделайте это - если у вас есть консольное приложение, связанное с временем выполнения c/С++ - это реально EP
.
так что если вы Fill Import table of this exe manually
- установите исключение для GetCommandLineA
и GetCommandLineW
- перенаправить его на самостоятельную реализацию и вернуть пользовательскую командную строку.
но если приложение использует не статический связанный CRT
, он может импортировать __getmainargs
или __wgetmainargs
или даже _acmdln
или _wcmdln
от msvcrt.dll
- поэтому уже задача становится сложной.
и вы предполагаете, что релокс завершает работу в EXE
, вы не обрабатываете TLS
, если он существует, вы не обрабатываете манифест приложения, возможные перенаправления dl и т.д.
но я не могу запустить его как отдельный процесс
это неверно. вы можете и должны запускать его как отдельный процесс - это лучшее решение.
выполните ваше приложение CreateProcess
с флагом CREATE_SUSPENDED
. здесь вы можете легко установить любую CommandLine, которая вам нужна. вам не нужно вручную и не полностью корректировать нагрузку EXE
, но система выполняет эту задачу для вас.
После создания процесса вам нужно ввести self DLL
в него, используя QueueUserAPC
(но не CreateRemoteThread
!!) и, наконец, позвоните ResumeThread
в результате ваш DLL
будет загружен и выполнен в первом потоке EXE
непосредственно перед приложением EP
- и здесь вы можете выполнить все необходимые задачи