2012-01-02 19 views
54

Estoy teniendo un pequeño vistazo a los archivos .dll, entiendo su uso y estoy tratando de entender cómo usarlos.Carga dinámicamente una función desde una DLL

He creado un archivo .dll que contiene una función que devuelve una funci nombre entero()

usar este código, (creo) he importado el archivo .dll en el proyecto (no hay quejas):

#include <windows.h> 
#include <iostream> 

int main() { 
    HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop \\fgfdg\\dgdg\\test.dll"); 

    if (hGetProcIDDLL == NULL) { 
    std::cout << "cannot locate the .dll file" << std::endl; 
    } else { 
    std::cout << "it has been called" << std::endl; 
    return -1; 
    } 

    int a = funci(); 

    return a; 
} 

# funci function 

int funci() { 
    return 40; 
} 

Sin embargo, cuando intento compilar este archivo .cpp que creo que ha importado el .dll tengo el siguiente error:

C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':| 
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|16|error: 'funci' was not  declared in this scope| 
||=== Build finished: 1 errors, 0 warnings ===| 

conozco a un .dll es diferente de un archivo de encabezado, así que sé que no puedo importar una función como esta, pero es lo mejor que pude hacer para demostrar que lo he intentado.

Mi pregunta es, ¿cómo puedo utilizar el puntero "hGetProcIDDLL" para acceder a la función dentro del .dll.

Espero que esta pregunta tenga sentido y no estoy ladrando un árbol equivocado una vez más.

+0

búsqueda enlace estático/dinámico. –

+0

Gracias, voy a mirar esto –

+0

Yo doblo mi código pero cuando lo introduzco aquí el formato se arruina así que termino hendiéndolo todo por 4 líneas –

Respuesta

96

LoadLibrary no hace lo que usted piensa que hace. Carga la DLL en la memoria del proceso actual, pero no ¡importa mágicamente las funciones definidas en ella! Esto no sería posible, ya que las llamadas a funciones las resuelve el vinculador en tiempo de compilación mientras se llama a LoadLibrary en tiempo de ejecución (recuerde que C++ es un lenguaje statically typed).

Necesita una función WinAPI separada para obtener la dirección de las funciones cargadas dinámicamente: GetProcAddress.

Ejemplo

#include <windows.h> 
#include <iostream> 

/* Define a function pointer for our imported 
* function. 
* This reads as "introduce the new type f_funci as the type: 
*    pointer to a function returning an int and 
*    taking no arguments. 
* 
* Make sure to use matching calling convention (__cdecl, __stdcall, ...) 
* with the exported function. __stdcall is the convention used by the WinAPI 
*/ 
typedef int (__stdcall *f_funci)(); 

int main() 
{ 
    HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop\\test.dll"); 

    if (!hGetProcIDDLL) { 
    std::cout << "could not load the dynamic library" << std::endl; 
    return EXIT_FAILURE; 
    } 

    // resolve function address here 
    f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci"); 
    if (!funci) { 
    std::cout << "could not locate the function" << std::endl; 
    return EXIT_FAILURE; 
    } 

    std::cout << "funci() returned " << funci() << std::endl; 

    return EXIT_SUCCESS; 
} 

También, usted debe export su función desde el DLL correctamente. Esto se puede hacer de esta manera:

int __declspec(dllexport) __stdcall funci() { 
    // ... 
} 

Como notas Lundin, es una buena práctica free the handle to the library si no los necesita más tiempo. Esto hará que se descargue si ningún otro proceso todavía tiene un identificador para la misma DLL.

+0

Puede sonar como una pregunta estúpida pero cuál es/debería ser el tipo de f_funci? –

+6

Aparte de eso, la respuesta es excelente y fácilmente comprensible –

+3

Tenga en cuenta que 'f_funci' de hecho _es un tipo_ (en lugar de _hasta_ un tipo). El tipo 'f_funci' se lee como" puntero a una función que devuelve un 'int' y no toma argumentos". Se puede encontrar más información acerca de los indicadores de función en C en http://www.newty.de/fpt/index.html. –

16

Además de la respuesta ya publicada, pensé que debería compartir un truco útil que uso para cargar todas las funciones de DLL en el programa a través de indicadores de función, sin escribir una llamada GetProcAddress por separado para cada función. También me gusta llamar a las funciones directamente como se intentó en el OP.

de inicio mediante la definición de un tipo genérico de puntero de función:

typedef int (__stdcall* func_ptr_t)(); 

¿Qué tipos que se utilizan no son realmente importantes. Ahora crear una matriz de ese tipo, que corresponde a la cantidad de funciones que tiene en el archivo DLL:

func_ptr_t func_ptr [DLL_FUNCTIONS_N]; 

En esta matriz podemos almacenar los punteros a funciones reales que apuntan hacia el espacio de memoria DLL.

El siguiente problema es que GetProcAddress espera los nombres de las funciones como cadenas.Por lo tanto crear una matriz similares que consiste en los nombres de las funciones de la DLL:

const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = 
{ 
    "dll_add", 
    "dll_subtract", 
    "dll_do_stuff", 
    ... 
}; 

Ahora podemos llamar fácilmente GetProcAddress() en un bucle y almacenar cada función dentro de esa matriz:

for(int i=0; i<DLL_FUNCTIONS_N; i++) 
{ 
    func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]); 

    if(func_ptr[i] == NULL) 
    { 
    // error handling, most likely you have to terminate the program here 
    } 
} 

Si el bucle fue exitoso, el único problema que tenemos ahora es llamar a las funciones. El puntero de función typedef de antes no es útil, porque cada función tendrá su propia firma. Esto se puede resolver mediante la creación de una estructura con todos los tipos de funciones:

typedef struct 
{ 
    int (__stdcall* dll_add_ptr)(int, int); 
    int (__stdcall* dll_subtract_ptr)(int, int); 
    void (__stdcall* dll_do_stuff_ptr)(something); 
    ... 
} functions_struct; 

Y, por último, para conectar estos a la matriz de antes, crear una unión:

typedef union 
{ 
    functions_struct by_type; 
    func_ptr_t  func_ptr [DLL_FUNCTIONS_N]; 
} functions_union; 

Ahora puede cargar toda la funciones de la DLL con el bucle conveniente, pero llámelos a través del miembro de la unión by_type.

Pero, por supuesto, que es un poco onerosa que escribir algo así como

functions.by_type.dll_add_ptr(1, 1); cada vez que desee llamar a una función.

Resulta que esta es la razón por la que agregué el postfix "ptr" a los nombres: quería mantenerlos diferentes de los nombres de las funciones reales. Ahora podemos suavizar la sintaxis estructura desordenada y obtener los nombres deseados, mediante el uso de algunas macros:

#define dll_add (functions.by_type.dll_add_ptr) 
#define dll_subtract (functions.by_type.dll_subtract_ptr) 
#define dll_do_stuff (functions.by_type.dll_do_stuff_ptr) 

y listo, ahora puede usar los nombres de función, con el tipo y los parámetros correctos, como si fueran estáticamente vinculado a su proyecto:

int result = dll_add(1, 1); 

de responsabilidad: en sentido estricto, las conversiones entre diferentes punteros de función no están definidas por la norma C y no es seguro. Entonces formalmente, lo que estoy haciendo aquí es un comportamiento indefinido. Sin embargo, en el mundo de Windows, los punteros de función siempre son del mismo tamaño sin importar su tipo y las conversiones entre ellos son predecibles en cualquier versión de Windows que haya utilizado.

Además, en teoría podría haber relleno insertado en la unión/estructura, lo que haría que todo falle. Sin embargo, los punteros son del mismo tamaño que el requisito de alineación en Windows. A static_assert para asegurarse de que la estructura/unión no tenga relleno todavía podría estar en orden.

+0

Este enfoque de estilo C funcionaría. Pero, ¿no sería apropiado usar una construcción C++ para evitar el '# define's? – harper

+0

@harper Bueno en C++ 11 podrías usar 'auto dll_add = ...', pero en C++ 03 no hay ninguna construcción que se me ocurra que simplifique la tarea (tampoco veo ningún problema particular con' # define's aquí) –

+0

Dado que esto es todo específico de WinAPI, no necesita tipear su propio 'func_ptr_t'. En su lugar, puede usar 'FARPROC', que es el tipo de devolución de' GetProcAddress'. Esto podría permitirle compilar con un nivel de advertencia más alto sin agregar un molde a la llamada 'GetProcAddress'. –

Cuestiones relacionadas