sirve - Crear un sistema de módulos(carga dinámica) en C
que se puede hacer con c++ (9)
Hay un enfoque de bricolaje Si bien el método (y la posibilidad) de hacer esto varía de un sistema a otro, la idea general es abrir un archivo, leer el contenido del archivo en la memoria, hacer que dicha memoria sea ejecutable, inicializar un puntero a una posición válida dentro de esta memoria y ahí estás.
Por supuesto, esto es asumiendo que es solo código ejecutable, bastante improbable. El código probablemente también requiera que los datos se carguen en la memoria RAM, y puede requerir espacio para variables globales / estáticas. Puede cargarlo todo usted mismo, pero necesitará ir al código ejecutable y ajustar todas las referencias de memoria en él.
La mayoría de los sistemas operativos permiten la vinculación dinámica, que hace todo esto por ti.
¿Cómo se podría cargar el código compilado C en tiempo de ejecución y luego llamar funciones dentro de él? No es como llamar simplemente a exec ().
EDITAR: El programa que carga el módulo está en C.
Los lenguajes dinámicos como Perl hacen esto todo el tiempo. El intérprete de Perl está escrito en C, y muchos módulos de Perl están parcialmente escritos en C. Cuando se requieren esos módulos, los componentes C compilados se cargan dinámicamente sobre la marcha. Como se señala en otra respuesta, el mecanismo para almacenar esos módulos es DLL en Windows y bibliotecas compartidas (archivos .so) en UNIX. Creo que la solicitud para cargar una biblioteca compartida en UNIX es dlopen (). Probablemente pueda encontrar indicadores sobre cómo lograr esto en UNIX comenzando con la documentación de esa llamada. Para Windows, necesitaría buscar DLL y aprender a cargarlos dinámicamente en tiempo de ejecución. [O posiblemente pase por la capa de emulación Cygwin UNIX, lo que probablemente le permita usar las mismas llamadas en Windows que haría en UNIX, pero no lo recomendaría a menos que ya esté usando y compilando contra Cygwin.]
Tenga en cuenta que esto es diferente de simplemente vincular con una biblioteca compartida. Si sabe de antemano exactamente a qué código llamará, puede compilar contra una biblioteca compartida y la compilación estará "vinculada dinámicamente" a esa biblioteca; sin su manejo especial, las rutinas de la biblioteca se cargarán en la memoria solo cuando su programa realmente las llame. Pero no puede hacer eso si planea escribir algo capaz de cargar cualquier código de objeto arbitrario , código que no puede identificar ahora, en tiempo de compilación, pero que en cambio espera ser seleccionado de alguna manera en tiempo de ejecución. Para eso, tendrás que usar dlopen () y sus primos de Windows.
Puede ver la forma en que Perl u otros lenguajes dinámicos hacen esto para ver algunos ejemplos reales. La biblioteca de Perl responsable de este tipo de carga dinámica es DynaLoader; tiene tanto un componente Perl como un componente C, creo. Estoy seguro de que otros lenguajes dinámicos como Python tienen algo similar que podrías mirar; y Parrot, la máquina virtual para el Perl 6 inédito, seguramente tiene un mecanismo para hacer esto también (o lo hará en el futuro).
Para el caso, Java logra esto a través de su interfaz JNI (Java Native Interface), por lo que probablemente pueda ver el código fuente de OpenJDK para ver cómo lo logra Java tanto en UNIX como en Windows.
En Linux / UNIX puede usar las dlopen
POSIX dlopen
/ dlsym
/ dlerror
/ dlclose
para abrir dinámicamente las bibliotecas compartidas y acceder a los símbolos (incluidas las funciones) que proporcionan, consulte la página de manual para obtener más información.
También puedes mirar cpluff . Es una biblioteca de administración de complementos en c pura.
En Windows, así es como lo hago:
- Generar código (en C porque es fácil encontrar compiladores y los requisitos de la biblioteca son mínimos)
- engendrar un trabajo para compilar / vincularlo a un archivo DLL
- cargarlo con LoadLibrary
- obtener punteros de función con GetProcAddress
Los pasos generar / compilar / vincular generalmente toman menos de un segundo.
Ver que se ha respondido esta pregunta, pero que otros interesados en este tema pueden apreciar un ejemplo de plataforma cruzada de una aplicación antigua basada en un complemento. El ejemplo funciona en win32 o linux, y establece y llama a una función llamada ''constructor'' en el archivo .so o .dll cargado dinámicamente especificado en el argumento del archivo. El ejemplo está en c ++ pero los procedimientos deberían ser los mismos para c.
//firstly the includes
#if !defined WIN32
#include <dlfcn.h>
#include <sys/types.h>
#else
#include <windows.h>
#endif
//define the plugin''s constructor function type named PConst
typedef tcnplugin* (*PConst)(tcnplugin*,tcnplugin*,HANDLE);
//loads a single specified tcnplugin,allmychildren[0] = null plugin
int tcnplugin::loadplugin(char *file) {
tcnplugin *hpi;
#if defined WIN32 //Load library windows style
HINSTANCE hplugin=LoadLibrary(file);
if (hplugin != NULL) {
PConst pinconstruct = (PConst)GetProcAddress(hplugin,"construct");
#else //Load it nix style
void * hplugin=dlopen(file,RTLD_NOW);
if (hplugin != NULL) {
PConst pinconstruct = (PConst)dlsym(hplugin,"construct");
#endif
if (pinconstruct != NULL) { //Try to call constructor function in dynamically loaded file, which returns a pointer to an instance of the plugin''s class
hpi = pinconstruct(this, this, hstdout);
} else {
piprintf("Cannot find constructor export in plugin!/n");
return 0;
}
} else {
piprintf("Cannot open plugin!/n");
#if !defined WIN32
perror(dlerror());
#endif
return 0;
}
return addchild(hpi); //add pointer to plugin''s class to our list of plugins
}
También se puede mencionar que si el módulo cuyas funciones desea llamar está escrito en c ++, debe declarar la función con extern "C" como:
extern "C" pcparport * construct(tcnplugin *tcnptr,tcnplugin *parent) {
return new pcparport(tcnptr,parent,"PCPARPORT",0,1);
}
Si está dispuesto a considerar el marco, Qt proporciona QPluginLoader: Qt 5 documentos (o para los antiguos documentos de Qt 4.8 ver aquí )
Si necesita / desea un control de grano más fino, Qt también proporciona un medio para cargar bibliotecas sobre la marcha con QLibrary: Qt 5 documentos (o para documentos antiguos de Qt 4.8 ver aquí )
Aún mejor, estos son portátiles en todas las plataformas.
dlopen es el camino a seguir. Aquí están algunos ejemplos:
Cargando un complemento con dlopen:
#include <dlfcn.h>
...
int
main (const int argc, const char *argv[])
{
char *plugin_name;
char file_name[80];
void *plugin;
...
plugin = dlopen(file_name, RTLD_NOW);
if (!plugin)
{
fatal("Cannot load %s: %s", plugin_name, dlerror ());
}
Compilando lo anterior:
% cc -ldl -o program program.o
Luego, asumiendo esta API para los complementos:
/* The functions we will find in the plugin */
typedef void (*init_f) ();
init_f init;
typedef int (*query_f) ();
query_f query;
Encontrar la dirección de init () en el complemento:
init = dlsym(plugin, "init");
result = dlerror();
if (result)
{
fatal("Cannot find init in %s: %s", plugin_name, result);
}
init();
Con la otra función, query (), que devuelve un valor:
query = dlsym (plugin, "query");
result = dlerror();
if (result)
{
fatal("Cannot find query in %s: %s", plugin_name, result);
}
printf("Result of plugin %s is %d/n", plugin_name, query ());
Puede recuperar el ejemplo completo en línea .
para usuarios de GNU / Linux
La biblioteca de carga dinámica es un mecanismo que, con la ayuda de eso, podemos ejecutar nuestro programa y, en tiempo de ejecución, decidir qué función queremos usar. Creo que en algunos casos static
variable static
es posible.
Primero, comience viendo a man 3 dlopen
o vea en línea
El archivo de encabezado que se requiere es: dlfcn
y, como esto no forma parte del estándar, debería tenerlo como su archivo de objeto con esta biblioteca: libdl.(so/a)
y, por lo tanto, necesita algo como:
gcc yours.c -ldl
entonces tiene un nombre de archivo a.out
y puede ejecutarlo PERO no funciona correctamente y le explicaré por qué.
Un ejemplo completo:
primeros archivos de func1.c
2 func1.c
y func2.c
respectivamente. Queremos llamar a estas funciones en tiempo de ejecución.
func.c
int func1(){
return 1;
}
func2.c
const char* func2(){
return "upgrading to version 2";
}
Ahora tenemos 2 funciones, hagamos nuestros módulos:
ALP ❱ gcc -c -fPIC func1.c
ALP ❱ gcc -c -fPIC func2.c
ALP ❱ gcc -o libfunc.so -shared -fPIC func1.o func2.o
para preguntar acerca de -fPIC
=> PIC
Ahora tiene dynamic library
nombres de una dynamic library
: libfunc.so
temp.c
programa principal (= temp.c
) que quiere usar esas funciones.
archivos de encabezado
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
y el programa principal
int main()
{
// pointer function to func1 and func2
int ( *f1ptr )();
const char* ( *f2ptr )();
// for pointing to the library
void* handle = NULL;
// for saving the error messages
const char* error_message = NULL;
// on error dlopen returns NULL
handle = dlopen( "libfunc.so", RTLD_LAZY );
// check for error, if it is NULL
if( !handle )
{
fprintf( stderr, "dlopen() %s/n", dlerror() );
exit( 1 );
}
/*
according to the header file:
When any of the above functions fails, call this function
to return a string describing the error. Each call resets
the error string so that a following call returns null.
extern char *dlerror (void) __THROW;
*/
// So, reset the error string, of course we no need to do it just for sure
dlerror();
// point to func1
f1ptr = (int (*)()) dlsym( handle, "func1" );
// store the error message to error_message
// because it is reseted if we use it directly
error_message = dlerror();
if( error_message ) // it means if it is not null
{
fprintf( stderr, "dlsym() for func1 %s/n", error_message );
dlclose( handle );
exit( 1 );
}
// point the func2
f2ptr = (const char* (*)()) dlsym( handle, "func2" );
// store the error message to error_message
// because it is reseted if we use it directly
error_message = dlerror();
if( error_message ) // it means if it is not null
{
fprintf( stderr, "dlsym() for func2 %s/n", error_message );
dlclose( handle );
exit( 1 );
}
printf( "func1: %d/n", ( *f1ptr )() );
printf( "func2: %s/n", ( *f2ptr )() );
// unload the library
dlclose( handle );
// the main return value
return 0;
}
Ahora solo tenemos que compilar este código (= temp.c
), por lo tanto, intente:
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or directory
¡No funciona! POR QUÉ fácil; porque nuestro programa a.out
no sabe dónde encontrar la biblioteca relacionada: libfunc.so
y por lo tanto nos dice que cannot not open ...
¿Cómo decirle al programa (= a.out
) para encontrar su biblioteca?
- usando
ld
linker - utilizando la variable de entorno
LD_LIBRARY_PATH
- usando la ruta estándar
primera manera, con ayuda de ld
usa -Wl,-rpath,
y pwd
y pon la ruta como argumento para ello
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or directory
ALP ❱ pwd
/home/shu/codeblock/ALP
ALP ❱ gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
segunda manera
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or direc
ALP ❱ export LD_LIBRARY_PATH=$PWD
ALP ❱ echo $LD_LIBRARY_PATH
/home/shu/codeblock/ALP
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
ALP ❱ export LD_LIBRARY_PATH=
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or
y tercera forma
tiene libfunc.so
en su ruta actual, por lo tanto puede copiarlo en una ruta estándar para bibliotecas.
ALP $ sudo cp libfunc.so /usr/lib
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
puede eliminarlo de /usr/lib
y usarlo. Es tu decision.
NOTA
¿Cómo saber que nuestro a.out
sabe acerca de su camino?
fácil:
ALP ❱ gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP
ALP ❱ strings a.out | grep //
/lib/ld-linux.so.2
/home/shu/codeblock/ALP
cómo podemos usarlo en c ++ ?
Mientras sepa que no puede porque g++
altera los nombres de las funciones mientras que gcc
no lo hace, debería usar: extern "C" int func1();
por ejemplo.
Para más detalles, consulte las páginas de manual y los libros de programación de Linux.