Inicio > OOP > DLL’s, BPL’s, Carga dinámica/Estática y “Packages en Runtime”

DLL’s, BPL’s, Carga dinámica/Estática y “Packages en Runtime”

Share Button

Cuando uno empieza a conocer Delphi (al menos a mi me pasó) y dependiendo de si “viene” de otros lenguajes, se hace un lío con las diferentes formas en que podemos generar un programa; Con la forma de trabajar con packages o sin packages, con los ficheros a distribuir,… Y le empiezan a surgir diferentes preguntas:

¿Porqué mi ejecutabe ocupa tanto si sólo he puesto un botón?
¿Qué son los packages de liberías? ¿Los debo copiar con mi aplicación?
¿Porque si marco “Build with runtime packages” mi programa ocupa tampoco?
¿Porque si marco “Build with runtime packages” mi programa no funciona en otras máquinas?
¿Como trabajar con DLL’s? ¿Y con BPL’s? ¿Cual es mejor? ¿Es lo mismo?

Y muchas otras relacionadas con los elementos que aparecen en el título de esta entrada.

Mi idea en este caso es, intentar aclarar algunos de estos términos y las diferentes formas de generar un programa desde Delphi (utilizando DLL’s y BPL’s -packages-). Otra cosa, es que lo consiga…  ;-D

Antes de comenzar, debemos aclarar algunos términos o elementos que debemos tener claros antes de continuar.

——————————————————————————————————————————————-

BWRP = “Build with runtime packages”

Esta opción se encuentra en las “Opciones de proyecto/Packages” y con ella conseguimos:

  • ACTIVADA: El programa no incluye las librerías (normalmente la VCL y componentes de terceros) en el EXE; El ejecutable es más pequeño, pero necesita de los packages (BPL’s) de la VCL; Al distribuir la aplicación se deben copiar también esos ficheros (los necesarios -NOTA1-) y los packages que hayamos creado nosotros.
  • DESACTIVADA: El programa incluye en el EXE los packages de librerías y componentes de terceros. En un único fichero va todo incluído. El EXE tiene mayor tamaño (lógico porque todo va incluído).

NOTA1: Se puede saber qué librerías (DLL’s o BPL’s) necesita un ejecutable utilizando herramientas como “Dependency Walker”, GExperts o CnWizards.

DLL = “Dinamic Link Libray / Librería de enlace dinámico”

Son ficheros con código ejecutable, que se utilizan en sistemas Windows, y que forma parte de otras aplicaciones o del propieo sistema operativo. Permiten hacer que los programas sean más pequeños y que el código que contienen pueda ser usado por diferentes aplicaciones.

BPL = “Borland Package Library”

En pocas palabras podemos decir que una BPL es una DLL sólo para Delphi. Las BPL’s son más potentes que las DLL’s. Permiten almacenar más información fFunciones, procedimientos, Units, pero también Clases, Objetos y Componentes). La mayor potencia de estos ficheros se “paga” con la exclusividad de poderlos utilizar únicamente en Delphi.

Carga estática de librerías

Tanto para DLL’s como para BPL’s, cuando hablamos de carga estática, nos referimos a que al ejecutarse el programa, se carga automáticamente esa librería. Aunque en ese momento no se vaya a utilizar el código que hay dentro de la librerías, esta se carga al iniciar. Si la librerías no existe en disco el programa falla durante la carga y no es posible ejecutarlo.

Carga dinámica de librerías

La carga dinámica de packages, tiene como contrapartida que es más compleja (programáticamente) que la carga estática. La carga se debe hacer de forma expresa por el programador cuando se dese. Esto significa que sólo se cargará esa librería cuando se necesite. El programa puede funcionar aunque no esté (el programador deberá comprobarlo antes de cargarla). Este sistema hace que este tipo de carga sea ideal para sistemas con plugins/Addins.

——————————————————————————————————————————————-

Una vez revisado esto, voy a intentar explicar las diferencias al crear un proyecto, segun si utilizamos DLL’s o BPL’s y carga estática/dinámica.

EXE + DLL con Carga estática(El EXE puede ser con o sin BWRP)

Tal y como hemos dicho, la DLL debe existir en disco cuando se ejecuta la aplicación, de otra forma esta no funcionaría y fallaría al arrancar.
Hay que definir dónde se encuentra la DLL (en el que la carga -el EXE-)

function Sumar(x,y:integer):Integer; stdcall; external 'sumas.dll';

Y con eso ya se puede hacer la llamada:

Res := Sumar(4,5);

EXE + DLL con Carga dinámica(El EXE puede ser con o sin BWRP)

En este caso el programa puede funcionar aunque la DLL no exista; En el momento de intentar cargarla se debe comprobar si está o no en disco. Se puede cargar cuando se necesita y descargarla cuando ya no es útil. La carga es un poco más compleja que el caso anterior.

Se utiliza LoadLibrary para cargar la DLL en el momento en que se necesita:

H := LoadLibrary('SUMAS.DLL');

Para acceder a las funciones de la DLL se usa GetProcAddress:

@sum := GetProcAddress(H, 'Sumar2');

En el programa que hace la llamada (el EXE) debe estar predefinido el tipo de la función a la que vamos a realizar la llamada:

Sum: function(x,y:Integer):integer; stdcall;

EXE + BPL con Carga estática(El EXE puede ser con o sin BWRP)

En este caso no hace falta definir nada referente a la función que se encuentra en el package, simplemente hacer referencia a ella en diseño (añadiendo la unit al USES).

Si el programa principal (EXE) se compila con “BRWP Desactivado” todo va dentro del EXE (componentes de terceros y código de la VCL); Si se compila con “BWRP activado” la VCL y los packages de terceros van aparte; Estos packages se deberán distribuir junto al EXE.

Utilizando carga estática es obligatorio que exista el package en disco y que esté accesible al arrancar la aplicación; Si la BPL no existe el programa no puede funcionar.

EXE + BPL con Carga dinámica + RTTI(El EXE debe ser con BWRP)

Es el caso más potente que tenemos. Cargar el package (hemos dicho que las BPL’s son más potentes que las DLL’s) de forma dinámica. Para ello hacemos uso de RTTI (Runtime Type Information) que es lo que le da la potencia extra a las BPL’s. Es obligatorio en este caso que “BWRP esté activado”; Es decir, obligatoriamente debemos distribuir nuestra aplicación con packages.

Los packages se cargan de forma dinámica con LoadPackage (de forma similar a cómo se hace con LoadLibrary):

H := LoadPackage('RESTAS.BPL');

Podemos acceder a las funciones de la misma forma que lo hacemos con las DLL’s, utilizando GetProcAddress:

@resta := GetProcAddress(H, 'Resta');

Podemos utilizar RTTI para acceder a classes que previamente se han registrado (con RegisterClass) utilizando funciones de RTTI como GetClass:

AClass := GetClass('TFormMain');

Y podemos acceder a métodos de las clases utilizando también RTTI, utilizando MethodAddress:

// Acceder al método
Routine.Data := Pointer((F as FormClass));
// Ejecutar al código
Routine.Code := (F as FormClass).MethodAddress('ExecForm');

No es necesario definir nada en el programa principal, puesto que podemos obtener todo la información de las clases definidas en el package utilizando RTTI.

Quedaría una última combinación, posible, pero un poco extraña, que sería EXE sin “runtime packages” y con carga dinámica. Y digo que es un poco rara, porque es un poco contracorriente. Utilizar BPL’s tiene la ventaja que le confiere utilizar RTTI, sin ella los BPL’s pasan a ser DLL’s normales. Esta opción haría justo eso, utilizar BPL’s, pero sin RTTI.

EXE + BPL con Carga dinámica sin RTTI(El EXE sin BWRP)

Esta opción (un poco rara) sería la de utilizar una aplicación con “BRWP Desactivado” (es decir TODO en un único EXE) y cargar en él package de forma dinámica. Esto es posible, pero en este caso perdemos la opción de utilizar RTTI; El EXE (fichero único) podría funcionar sin que exista la BPL que vamos a cargar. Al no poder utilizar RTTI, las “mejoras” que tienen las BPL’s se pierden y pasan a usarse como DLL’s.

Se pueden cargar las BPL’s de forma dinámica con LoadPackage:

H := LoadPackage('RESTAS.BPL');

Y para acceder a las funciones usamos GetProcAddress (como en las DLL’s):

@resta := GetProcAddress(H, 'Resta');

Por supuesto si intentamos acceder a información de clases (GetClass) el programa falla.
Más o menos hasta aquí las diferentes opciones de compilar el proyecto y algunas referencias a cómo hacer las llamadas y las definiciones.

Todo ello está implementado en los ejemplos que ajunto al final de esta entrada. Hay un grupo de proyectos (compilado con Delphi 6) que incluye los siguientes ficheros:

  • sumas.dpr: Proyecto que genera un DLL con una función de suma
  • Call_suma_estat.dpr: Proyecto para llamar a una DLL con carga estática.
  • Call_suma_dinam.dpr: Proyecto para llamar a una DLL con carga dinámica.
  • restas.dpk: Proyecto para generar una BPL con una función de resta y un formulario.
  • Call_resta_estat.dpr: Proyecto para llamar a la BPL con carga estática.
  • Call_suma_dinam.dpr: Proyecto para llamar a la BPL con carga dinámica (uso de RTTI y proyecto compilado con Runtime Packages).
  • Call_resta_dinam_sin_pack.dpr: Proyecto para llamar a la BPL con carga dinámica, pero sin compilación con packages (sin uso de RTTI).

El Grupo de proyectos completo se puede descargar desde aquí.

[Link descarga código fuente]

AÑADIDO: El otro día me dejé sin poner algunas referencias que tengo sobre el tama y que me parecen interesante, así que ahí van:

Categories: OOP Tags: , ,
  1. Hector
    jueves, 12 de julio de 2012 a las 16:43 | #1

    Gracias.
    ahora aprendi bien las diferencias.

  2. Manuel
    jueves, 28 de febrero de 2013 a las 02:24 | #2

    excelente explicación..

  3. José Palumbo
    sábado, 6 de abril de 2013 a las 16:57 | #3

    Muy buena explicación. Saludos

  4. Yacnier
    miércoles, 21 de octubre de 2020 a las 05:25 | #4

    Hola, excelente árticulo.
    He tratado de reproducir: EXE + BPL con Carga dinámica + RTTI(El EXE debe ser con BWRP).
    Me ha sucedido algo bastante extraño con lo que no contaba, no he encontrado bibliografia que me explique el porque y talvez usted pueda ayudarme.

    Mi idea era separar por modulos una App que queria actualizar, pero antes quise experimentar. Hice todo como usted explica en el articulo y funcionó. Ahora mi duda trata es si desde el plugin puedo acceder a objetos de la App principal, porque me llamo tremendamente la atención que si a un dbgrid situado en una forma de la bpl le pasaba como datasource uno ubicado en la App principal a pesar que entre ellas no existe conocimiento acerca una de la otra, funcionan perfectamente un runtime (se muestran datos en al grid), sin embargo no encuentro vía para desde el editor de códigos por acceder al main form de la App principal que no sea de esta forma:

    (DBGrid1.DataSource.Owner as TForm).Caption:=’?!?’;
    De está forma accedo al formulario principal de la app que carga el plugin, pero quisiera saber que hay de tras de esto, y si existe alguna manera de poder acceder directamente a objetos de la App principal.

  5. miércoles, 21 de octubre de 2020 a las 07:27 | #5

    @Yacnier
    Hola Yacnier.
    Efectivamente tal como comentas, hay determinadas cosas que en diseño no puedes hacer. al haber dividido la aplicación en packages no puedes acceder directamente al Mainform, por ejemplo.

    No hay manera de hacerlo directamente.
    Lo que utilizamos es RTTI y la herencia entre objetos.
    Piensa que al final el MainForm hereda de un TForm y la clase TForm si que podemos usarla en cualquier sitio.
    Como te he dicho necesitamos usar herencia, “casteo”, RTTI para salvar esas dificultades.

    Espero haberte aclarado algo.

    Un saludo.

  6. Yacnier
    miércoles, 21 de octubre de 2020 a las 20:15 | #6

    @Germán Estévez
    Sí, tu ayuda me ha sido útil, me has sacado de dudas, estaba dando vueltas buscando en la web sin suerte.
    Quería saber si había algo más oculto, porque me llamo la atención que en modo de diseño podía acceder desde la bpl al main form.

    Gracias nuevamente.

  1. martes, 20 de julio de 2010 a las 11:41 | #1
  2. viernes, 23 de julio de 2010 a las 16:48 | #2
What is 2 + 4 ?
Please leave these two fields as-is:
IMPORTANTE! Para continuar, debes contestar la pregunta anterior (para evitar SPAM) :-)