Как я могу написать универсальную функцию C для вызова функции Win32?
Чтобы разрешить доступ к Win32 API из скриптового языка (написанного на C), я хотел бы написать такую функцию:
void Call(LPCSTR DllName, LPCSTR FunctionName,
LPSTR ReturnValue, USHORT ArgumentCount, LPSTR Arguments[])
который будет вызывать, как правило, любую функцию Win32 API.
(Параметры LPSTR по существу используются в качестве байтовых массивов - предположим, что они были правильно откалиброваны, чтобы принять правильный тип данных, внешний по отношению к функции. Также я полагаю, что требуется некоторая дополнительная сложность, чтобы различать аргументы-указатели и не-указатели, но я игнорирую это для целей этого вопроса).
У меня проблема с передачей аргументов в функции Win32 API. Поскольку это stdcall, я не могу использовать varargs, поэтому реализация Call должна заранее знать количество аргументов и, следовательно, не может быть универсальной...
Я думаю, что могу сделать это с помощью ассемблерного кода (перебирая аргументы, помещая каждый из них в стек), но возможно ли это в чистом C?
Обновление: я отметил ответ "Нет, это невозможно" как принятый на данный момент. Я, конечно, изменит это, если появится решение на основе C.
Обновление: ruby / dl выглядит так, как будто это может быть реализовано с использованием подходящего механизма. Любые детали по этому поводу будут оценены.
8 ответов
Нет, я не думаю, что это возможно сделать без написания некоторой сборки. Причина в том, что вам нужен точный контроль над тем, что находится в стеке, прежде чем вызывать целевую функцию, и нет никакого реального способа сделать это в чистом C. Хотя, конечно, это легко сделать в Assembly.
Кроме того, вы используете PCSTR для всех этих аргументов, что на самом деле просто const char *
, Но так как все эти аргументы не являются строками, то, что вы на самом деле хотите использовать для возвращаемого значения и для аргументов [], это void *
или же LPVOID
, Этот тип следует использовать, когда вы не знаете истинный тип аргументов, а не приводите их к char *
,
Перво-наперво: вы не можете передать тип в качестве параметра в C. Единственная опция, с которой вы остаетесь - это макросы.
Эта схема работает с небольшой модификацией (массив void * для аргументов), если вы делаете LoadLibrary/GetProcAddress
для вызова функций Win32. Наличие строки имени функции в противном случае будет бесполезным. В C единственный способ вызывать функцию - через ее имя (идентификатор), которое в большинстве случаев превращается в указатель на функцию. Вы также должны позаботиться о приведении возвращаемого значения.
Моя лучшая ставка:
// define a function type to be passed on to the next macro
#define Declare(ret, cc, fn_t, ...) typedef ret (cc *fn_t)(__VA_ARGS__)
// for the time being doesn't work with UNICODE turned on
#define Call(dll, fn, fn_t, ...) do {\
HMODULE lib = LoadLibraryA(dll); \
if (lib) { \
fn_t pfn = (fn_t)GetProcAddress(lib, fn); \
if (pfn) { \
(pfn)(__VA_ARGS__); \
} \
FreeLibrary(lib); \
} \
} while(0)
int main() {
Declare(int, __stdcall, MessageBoxProc, HWND, LPCSTR, LPCSTR, UINT);
Call("user32.dll", "MessageBoxA", MessageBoxProc,
NULL, ((LPCSTR)"?"), ((LPCSTR)"Details"),
(MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2));
return 0;
}
Другие посты правы относительно почти определенной потребности в сборке или других нестандартных приемах для фактического выполнения вызова, не говоря уже о всех деталях фактических соглашений о вызовах.
Windows DLL используют как минимум два различных соглашения о вызовах для функций: stdcall
а также cdecl
, Вам нужно будет справиться с обоими, и, возможно, даже нужно выяснить, какой использовать.
Один из способов справиться с этим - использовать существующую библиотеку для инкапсуляции многих деталей. Удивительно, но есть один: libffi. Примером его использования в среде сценариев является реализация Lua Alien, модуля Lua, который позволяет создавать интерфейсы для произвольных библиотек DLL на чистом Lua, помимо самого Alien.
Многие Win32 API используют указатели на структуры с конкретными макетами. Из них большое подмножество следует общему шаблону, где первый DWORD должен быть инициализирован, чтобы иметь размер структуры перед его вызовом. Иногда им требуется передать блок памяти, в который они будут записывать структуру, а блок памяти должен иметь размер, который определяется первым вызовом того же API с указателем NULL и чтением возвращаемого значения для обнаружения правильного размер. Некоторые API выделяют структуру и возвращают указатель на нее, так что указатель должен быть освобожден при втором вызове.
Я не удивлюсь, если набор API, которые можно с пользой вызывать за один раз, с отдельными аргументами, конвертируемыми из простого строкового представления, достаточно мал.
Чтобы сделать эту идею в целом применимой, мы должны пойти на крайнюю крайность:
typedef void DynamicFunction(size_t argumentCount, const wchar_t *arguments[],
size_t maxReturnValueSize, wchar_t *returnValue);
DynamicFunction *GenerateDynamicFunction(const wchar_t *code);
Вы должны передать простой фрагмент кода в GenerateDynamicFunction, и он обернет этот код в некоторый стандартный шаблон, а затем вызовет компилятор / компоновщик C, чтобы создать из него DLL (доступно довольно много свободных опций), содержащую функцию. Было бы тогда LoadLibrary
что DLL и использовать GetProcAddress
найти функцию, а затем вернуть ее. Это будет дорого, но вы сделаете это один раз и кэшируете полученный DynamicFunctionPtr для повторного использования. Вы можете сделать это динамически, сохраняя указатели в хеш-таблице, основанной на самих фрагментах кода.
Образец может быть:
#include <windows.h>
// and anything else that might be handy
void DynamicFunctionWrapper(size_t argumentCount, const wchar_t *arguments[],
size_t maxReturnValueSize, wchar_t *returnValue)
{
// --- insert code snipped here
}
Таким образом, пример использования этой системы будет:
DynamicFunction *getUserName = GenerateDynamicFunction(
"GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))");
wchar_t userName[100];
getUserName(0, NULL, sizeof(userName) / sizeof(wchar_t), userName);
Вы можете улучшить это, сделав GenerateDynamicFunction
принять количество аргументов, чтобы он мог генерировать проверку в начале оболочки, что передано правильное количество аргументов. И если вы поместите туда хеш-таблицу, чтобы кэшировать функции для каждого обнаруженного кода, вы можете приблизиться к исходному примеру. Функция Call взяла бы фрагмент кода вместо просто имени API, но в противном случае была бы такой же. Он будет искать фрагмент кода в хеш-таблице, а если его нет, он вызовет GenerateDynamicFunction и сохранит результат в хеш-таблице в следующий раз. Затем он будет выполнять вызов функции. Пример использования:
wchar_t userName[100];
Call("GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))",
0, NULL, sizeof(userName) / sizeof(wchar_t), userName);
Конечно, не было бы никакого смысла делать что-либо из этого, если бы идея не заключалась в том, чтобы открыть какую-то общую дыру в безопасности. например выставить Call
как веб-сервис. Значения безопасности существуют для вашей первоначальной идеи, но менее очевидны просто потому, что предложенный вами оригинальный подход не будет настолько эффективным. Чем более мощным он будет, тем больше будет проблем с безопасностью.
Обновление на основе комментариев:
.NET Framework имеет функцию p/invoke, которая существует именно для решения вашей проблемы. Так что, если вы делаете это как проект для изучения вещей, вы можете посмотреть на p/invoke, чтобы понять, насколько это сложно. Вы могли бы нацелить платформу.NET на свой язык сценариев - вместо интерпретации сценариев в реальном времени или компиляции их в свой собственный байт-код, вы можете скомпилировать их в IL. Или вы можете разместить существующий язык сценариев из множества уже доступных в.NET.
Вы можете попробовать что-то вроде этого - это хорошо работает для функций Win32 API:
int CallFunction(int functionPtr, int* stack, int size)
{
if(!stack && size > 0)
return 0;
for(int i = 0; i < size; i++) {
int v = *stack;
__asm {
push v
}
stack++;
}
int r;
FARPROC fp = (FARPROC) functionPtr;
__asm {
call fp
mov dword ptr[r], eax
}
return r;
}
Параметры в аргументе "стек" должны быть в обратном порядке (так как это порядок, в который они помещаются в стек).
Я не уверен, будет ли он вам интересен, но можно было бы раскошелиться на RunDll32.exe и заставить его выполнить вызов функции для вас. RunDll32 имеет некоторые ограничения, и я не верю, что вы можете получить доступ к возвращаемому значению вообще, но если вы правильно сформируете аргументы командной строки, это вызовет функцию.
Вот ссылка
Во-первых, вы должны добавить размер каждого аргумента в качестве дополнительного параметра. В противном случае вам нужно определить размер каждого параметра для каждой функции, которую нужно поместить в стек, что возможно для функций WinXX, поскольку они должны быть совместимы с параметрами, которые они задокументировали, но утомительно.
Во-вторых, не существует способа "чистого C" вызова функции без знания аргументов, за исключением функции varargs, и нет ограничений на соглашение о вызовах, используемое функцией в.DLL.
На самом деле вторая часть важнее первой.
Теоретически, вы можете настроить структуру препроцессора макроса /#include для генерации всех комбинаций типов параметров, скажем, до 11 параметров, но это означает, что вы заранее знаете, какие типы будут передаваться через вашу функцию Call
, Что довольно безумно, если вы спросите меня.
Хотя, если вы действительно хотите сделать это небезопасно, вы можете передать искаженное имя C++ и использовать UnDecorateSymbolName
извлечь типы параметров. Тем не менее, это не будет работать для функций, экспортируемых с помощью C-linkage.
Наличие такой функции звучит как плохая идея, но вы можете попробовать это:
int Call(LPCSTR DllName, LPCSTR FunctionName,
USHORT ArgumentCount, int args[])
{
void STDCALL (*foobar)()=lookupDLL(...);
switch(ArgumentCount) {
/* Note: If these give some compiler errors, you need to cast
each one to a func ptr type with suitable number of arguments. */
case 0: return foobar();
case 1: return foobar(args[0]);
...
}
}
В 32-разрядной системе почти все значения помещаются в 32-разрядное слово, а более короткие значения помещаются в стек как 32-разрядные слова для аргументов вызова функции, поэтому вы должны иметь возможность вызывать практически все функции Win32 API таким образом, просто приведите аргументы к int и возвращаемое значение от int к соответствующим типам.