Как я могу написать универсальную функцию C для вызова функции Win32? - PullRequest
6 голосов
/ 09 марта 2009

Чтобы разрешить доступ к 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 ]

8 голосов
/ 09 марта 2009

Перво-наперво: вы не можете передать тип в качестве параметра в 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;
}
5 голосов
/ 09 марта 2009

Нет, я не думаю, что это возможно сделать без написания какой-либо сборки. Причина в том, что вам нужен точный контроль над тем, что находится в стеке, прежде чем вызывать целевую функцию, и нет никакого реального способа сделать это в чистом C. Хотя, конечно, это легко сделать в Assembly.

Кроме того, вы используете PCSTR для всех этих аргументов, что на самом деле просто const char *. Но так как все эти аргументы не являются строками, то, что вы на самом деле хотите использовать для возвращаемого значения и для аргументов [], это void * или LPVOID. Этот тип следует использовать, когда вы не знаете истинный тип аргументов, вместо того, чтобы приводить их к char *.

2 голосов
/ 08 апреля 2009

Другие посты правы относительно почти определенной потребности в сборке или других нестандартных приемах для фактического выполнения вызова, не говоря уже о всех деталях фактических соглашений о вызовах.

Windows DLL используют как минимум два различных соглашения о вызовах для функций: stdcall и cdecl. Вам нужно будет справиться с обоими, и даже может потребоваться выяснить, какой из них использовать.

Один из способов справиться с этим - использовать существующую библиотеку для инкапсуляции многих деталей. Удивительно, но есть одно: libffi . Примером его использования в среде сценариев является реализация Lua Alien , модуля Lua, который позволяет создавать интерфейсы для произвольных библиотек DLL на чистом Lua, помимо самого Alien.

1 голос
/ 01 ноября 2009

Вы можете попробовать что-то вроде этого - это хорошо работает для функций 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;
}

Параметры в аргументе «стек» должны быть в обратном порядке (поскольку это порядок, в который они помещаются в стек).

1 голос
/ 12 марта 2009

Многие 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.

0 голосов
/ 09 марта 2009

Во-первых, вы должны добавить размер каждого аргумента в качестве дополнительного параметра. В противном случае вам нужно определить размер каждого параметра для каждой функции, которую нужно поместить в стек, что возможно для функций WinXX, поскольку они должны быть совместимы с параметрами, которые они документируют, но утомительно.

Во-вторых, не существует способа «чистого C» вызова функции без знания аргументов, за исключением функции varargs, и нет ограничения на соглашение о вызовах, используемое функцией в .DLL.

На самом деле вторая часть важнее первой.

Теоретически, вы можете настроить структуру макроса / # препроцессора для генерации всех комбинаций типов параметров, скажем, до 11 параметров, но это означает, что вы заранее знаете, какие типы будут передаваться через вашу функцию Call. Что сумасшедшего, если вы спросите меня.

Хотя, если вы действительно хотите сделать это небезопасно, вы можете передать искаженное имя C ++ и использовать UnDecorateSymbolName для извлечения типов параметров. Однако это не сработает для функций, экспортируемых с помощью связи C.

0 голосов
/ 09 марта 2009

Я не уверен, будет ли он вам интересен, но можно было бы раскошелиться на RunDll32.exe и заставить его выполнить вызов функции для вас. RunDll32 имеет некоторые ограничения, и я не верю, что вы можете получить доступ к возвращаемому значению вообще, но если вы правильно сформируете аргументы командной строки, это вызовет функцию.

Вот ссылка

0 голосов
/ 09 марта 2009

Наличие такой функции звучит как плохая идея, но вы можете попробовать это:

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 к соответствующим типам.

...