Как я могу написать универсальную функцию 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 к соответствующим типам.

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