2009-03-09 22 views
6

para permitir el acceso a la API Win32 desde un lenguaje de script (escrito en C), me gustaría escribir una función como la siguiente:¿Cómo puedo escribir una función C genérica para llamar a una función Win32?

void Call(LPCSTR DllName, LPCSTR FunctionName, 
    LPSTR ReturnValue, USHORT ArgumentCount, LPSTR Arguments[]) 

que llamar, genéricamente, cualquier función de API Win32.

(los parámetros LPSTR se están utilizando esencialmente como matrices de bytes: supongamos que se han dimensionado correctamente para tomar el tipo de datos correcto externo a la función. También creo que se requiere cierta complejidad adicional para distinguir entre puntero y no argumentos del puntero pero estoy ignorando eso a los fines de esta pregunta).

El problema que tengo es pasar los argumentos a las funciones de API de Win32. Debido a que estos son stdcall no puedo usar varargs así que la implementación de 'Call' debe conocer el número de argumentos por adelantado y por lo tanto no puede ser genérico ...

Creo que puedo hacer esto con el código de ensamblado (por haciendo un bucle sobre los argumentos, empujando cada uno hacia la pila) pero ¿es esto posible en C pura?

Actualización: He marcado la respuesta 'No, no es posible' como se acepta por ahora. Por supuesto, cambiaré esto si sale a la luz una solución basada en C.

Actualización:ruby/dl parece que se puede implementar utilizando un mecanismo adecuado. Cualquier detalle sobre esto sería apreciado.

Respuesta

5

No, no creo que sea posible sin escribir algún ensamblaje. La razón es que necesitas un control preciso sobre lo que está en la pila antes de llamar a la función objetivo, y no hay una manera real de hacerlo en C. puro. Sin embargo, es simple de hacer en Assembly.

Además, está utilizando PCSTR para todos estos argumentos, que en realidad es solo const char *. Pero como todos estos argumentos no son cadenas, lo que realmente quiere usar para el valor de retorno y para Argumentos [] es void * o LPVOID. Este es el tipo que debe usar cuando no conoce el tipo verdadero de los argumentos, en lugar de enviarlos al char *.

+0

Estoy usando char * aquí como una matriz de bytes en lugar de cadenas. La suposición es que pongo los metadatos a disposición de la función 'Llamada' que describe los tamaños de los tipos reales (es decir, el número de bytes) que se requieren. En ese sentido, los tipos * son * conocidos: son un número conocido de bytes. –

+0

En general, necesita más que el número de bytes para hacer una llamada, ya que la convención de aprobación puede ser diferente para diferentes tipos de datos (particularmente para argumentos de coma flotante). Pero puede que tengas razón en que win32 __stdcall simplemente pone todo en la pila, no lo conozco en detalle. – puetzk

8

Lo primero es lo primero: no se puede pasar un tipo como un parámetro en C. La única opción que le queda son macros.

Este esquema funciona con una pequeña modificación (matriz de vacío * para los argumentos), siempre que esté haciendo un LoadLibrary/GetProcAddress para llamar a las funciones de Win32. Tener una cadena de nombre de función de lo contrario no será de utilidad. En C, la única forma de llamar a una función es a través de su nombre (un identificador) que en la mayoría de los casos se desintegra a un puntero a la función. También debes encargarte de emitir el valor de retorno.

Mi mejor apuesta:

// 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; 
} 
+0

Entonces, ¿está diciendo que * es * posible hacer esto en C, el problema es la llamada real a la función Win32 (que obviamente no tengo control) en lugar del prototipo de la función 'Llamar' en mi ejemplo? –

+0

¿No supone el uso de __VA_ARGS__ que la llamada es cdecl en lugar de stdcall (y por lo tanto, los argumentos se insertarán en la pila en el orden incorrecto)? –

+0

__VA_ARGS__ es una macro variadica. Esto deja de existir después del paso de preproceso. Esto no es lo mismo que va_args para funciones variadas. Tenga en cuenta también que estoy pasando el tipo de la función como un parámetro macro. – dirkgently

0

Tener una función como la que suena como una mala idea, pero se puede intentar esto:

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]); 
     ... 
    } 
} 

En un sistema de 32 bits, casi todos los valores se ajustan en una palabra de 32 bits y los valores más cortos se insertan en la pila como palabras de 32 bits para los argumentos de llamadas a funciones, por lo que debería poder invocar virtualmente todas las funciones API de Win32 de esta manera, simplemente transfiera los argumentos a int y el valor de retorno de int a los tipos apropiados.

+0

Para una implementación genérica, realmente no quiero tener código de cambio (o si no) para diferentes números de argumentos, aunque estoy de acuerdo en que hay un límite práctico para la cantidad de argumentos que necesitaría atender. –

+0

También necesita llamar a las funciones de cdecl ... – RBerteig

0

No estoy seguro de si le interesará, pero una opción sería pagar a RunDll32.exe y hacer que ejecute la llamada de función por usted. RunDll32 tiene algunas limitaciones y no creo que pueda acceder al valor de retorno en absoluto, pero si forma los argumentos de la línea de comandos correctamente, llamará a la función.

Aquí hay una link

+0

Gracias por el enlace, pero sugiere que RunDll32 explícitamente no permite llamar a ninguna función de API de Win32 ... –

0

En primer lugar, se debe añadir el tamaño de cada argumento como un parámetro adicional. De lo contrario, debe adivinar el tamaño de cada parámetro para que cada función ingrese en la pila, lo cual es posible para las funciones de WinXX ya que deben ser compatibles con los parámetros que están documentados, pero son tediosas.

En segundo lugar, no hay una forma de "C puro" para llamar a una función sin conocer los argumentos, excepto para una función varargs, y no hay restricción en la convención de llamadas utilizada por una función en un .DLL.

En realidad, la segunda parte es más importante que la primera.

En teoría, podría configurar una estructura de macro/# de preprocesador para generar todas las combinaciones de tipos de parámetros hasta, por ejemplo, 11 parámetros, pero eso implica que sabe de antemano qué tipos pasarán por su función Call. Lo cual es un poco loco si me preguntas.

Aunque, si realmente deseaba hacer esto inseguramente, podría pasar el nombre de C++ destrozado y usar UnDecorateSymbolName para extraer los tipos de los parámetros. Sin embargo, eso no funcionará para las funciones exportadas con enlace C.

1

Muchas API de Win32 toman punteros a estructuras con diseños específicos. De estos, un subconjunto grande sigue un patrón común donde el primer DWORD tiene que inicializarse para tener el tamaño de la estructura antes de que se llame. A veces requieren pasar un bloque de memoria, en el que escribirán una estructura, y el bloque de memoria debe tener un tamaño que se determina llamando primero a la misma API con un puntero NULL y leyendo el valor de retorno para descubrir el correcto tamaño. Algunas API asignan una estructura y le devuelven un puntero, de modo que el puntero debe desasignarse con una segunda llamada.

No me sorprendería que el conjunto de API que se pueden llamar de una sola vez, con argumentos individuales convertibles desde una simple representación de cadena, sea bastante pequeño.

Para que esta idea de aplicación general, tendríamos que ir a toda una extrema:

typedef void DynamicFunction(size_t argumentCount, const wchar_t *arguments[], 
          size_t maxReturnValueSize, wchar_t *returnValue); 

DynamicFunction *GenerateDynamicFunction(const wchar_t *code); 

Usted pasaría un fragmento de código simple a GenerateDynamicFunction, y sería envolver ese código de alguna repetitivo estándar y a continuación, invoque un compilador/vinculador de C para crear una DLL (hay bastantes opciones gratuitas disponibles) que contiene la función. Sería LoadLibrary esa DLL y usar GetProcAddress para encontrar la función y luego devolverla. Esto sería costoso, pero lo haría una vez y almacenaría en caché el DynamicFunctionPtr resultante para un uso repetido. Puede hacer esto de forma dinámica manteniendo los punteros en una tabla hash, codificados por los fragmentos de código.

El repetitivo podría ser:

#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 
} 

lo tanto, un ejemplo de uso de este sistema sería:

DynamicFunction *getUserName = GenerateDynamicFunction(
    "GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))"); 

wchar_t userName[100]; 
getUserName(0, NULL, sizeof(userName)/sizeof(wchar_t), userName); 

Se podría mejorar esto haciendo GenerateDynamicFunction aceptar el número del argumento, por lo que podría generar una comprobar al inicio del envoltorio que se ha pasado la cantidad correcta de argumentos. Y si coloca una tabla hash allí para almacenar en caché las funciones de cada codenippet encontrado, podría acercarse a su ejemplo original. La función Llamar tomaría un fragmento de código en lugar de simplemente un nombre de API, pero de lo contrario sería el mismo. Buscaría el fragmento de código en la tabla hash, y si no estaba presente, llamaría a GenerateDynamicFunction y almacenaría el resultado en la tabla hash para la próxima vez. Entonces realizaría la llamada en la función. Ejemplo de uso:

wchar_t userName[100]; 

Call("GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))", 
    0, NULL, sizeof(userName)/sizeof(wchar_t), userName); 

Por supuesto que no tendría mucho sentido hacer nada de esto a menos que la idea era abrir una especie de agujero de seguridad en general. p.ej. para exponer Call como un servicio web. Las implicaciones de seguridad existen para su idea original, pero son menos evidentes simplemente porque el enfoque original que sugirió no sería tan efectivo. Cuanto más poderosos lo logremos, mayor será un problema de seguridad.

de actualización basado en los comentarios:

El marco .NET tiene una característica llamada p/invocar, que existe precisamente para resolver su problema. Entonces, si estás haciendo esto como un proyecto para aprender sobre cosas, podrías mirar p/invoke para tener una idea de cuán complejo es. Posiblemente pueda orientar el .NET Framework con su lenguaje de scripts: en lugar de interpretar scripts en tiempo real o compilarlos en su propio bytecode, puede compilarlos en IL. O puede alojar un lenguaje de scripts existente de los muchos ya disponibles en .NET.

+0

+1, pero mi intención es agregar la capacidad de llamar arbitrariamente a la API de Win32 desde un lenguaje de scripting implementado en C (¡en lugar de abrir un agujero de seguridad!). Prefiero que la implementación no dependa de un compilador/enlazador de C si es posible. –

+0

Gracias por la actualización (lamentablemente no puedo volver a votarlo de nuevo!). Estoy buscando una solución basada en C, idealmente, pero la opción .NET es algo para pensar. –

+0

La técnica tiene casos de uso que no están destinados a ser agujeros de seguridad. Una es permitir que los autores de guiones adapten el código extraño o las API del sistema a su lenguaje de scripting sin necesidad de poder compilar. – RBerteig

2

Las otras publicaciones son correctas sobre la necesidad casi segura de ensamblaje u otros trucos no estándar para hacer la llamada, sin mencionar todos los detalles de las convenciones de llamadas reales.

Las DLL de Windows usan al menos dos convenciones de llamada distintas para las funciones: stdcall y cdecl. Debería manejar ambos, e incluso podría necesitar averiguar cuál usar.

Una forma de resolver esto es utilizar una biblioteca existente para encapsular muchos de los detalles. Sorprendentemente, hay uno: libffi. Un ejemplo de su uso en un entorno de scripting es la implementación de Lua Alien, un módulo Lua que permite que se creen interfaces con DLL arbitrarios en Lua puro, aparte de Alien.

1

Usted podría intentar algo como esto - que funciona bien para las funciones de la API de Win32:

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; 
} 

Los parámetros en el argumento de la "pila" debe estar en orden inverso (ya que este es el orden en que se inserta en la apilar).

Cuestiones relacionadas