FTP | CCD | Buscar | Trucos | Trabajo | Foros |
|
Registrarse | FAQ | Miembros | Calendario | Guía de estilo | Temas de Hoy |
|
Herramientas | Buscar en Tema | Desplegado |
#1
|
|||
|
|||
Ayuda con paquetes por favor!!
Hola a todos.
Estoy vuelto un 8 con los paquetes ... Voy a intentar explicar de la forma más clara, y lo mejor posible lo que quiero hacer... Espero que me explique bien... Estoy haciendo una aplicación muy grande... y quiero partirla en paquetes. Tengo un DataModule (DMDatosZeos) donde residen todas las tablas, algunos querys y los datasources. Tengo otro DataModule (DMGlobal) donde residen algunos componentes no visuales que uso de forma global al programa, por ejemplo para el aspecto visual de los formularios (efectos en botones y cosas así…). Los mantenimientos de las tablas (listados con filtros, modificaciones, eliminaciones y algunas inserciones) los he programado en un formulario "general" y todos los mantenimientos heredan de éste. Cada formulario de mantenimiento tiene un formulario de edición asociado, y éste también hereda de un formulario de edición "general". Básicamente para “personalizar” un mantenimiento, tengo que decirle qué DataSource es el asociado, y qué campos tiene y el resto es más o menos automático. En el programa tengo un objeto global, llamado _BD, de tipo TBD, que encapsula al DataModule (DMDatosZeos) y algunos métodos para tratado de datos. (No encapsula a DMGlobal… eso es aparte) Hasta aquí todo bien... Espero que todo se entienda. Ahora, lo que quiero hacer es... Hacer un paquete para la base de datos (BD.bpl). Dicho paquete contendría la clase TBD, y el DataModule (DMDatosZeos) que es un campo de la clase. Adicionalmente en la misma unit de TBD, tengo también algunas rutinas para trabajar con datos. Recordemos en este punto que en el programa tengo un objeto “global” _BD de tipo TBD. La pregunta es… Cómo hago para desde el ejecutable “importar” el objeto _BD? En el paquete BD.bpl creo el objeto _BD al cargarlo, osea… hago algo tal que así… Código:
initialization RegisterClass(TBD); _BD := TBD.Create; Y tengo una función BD que me devuelve el objeto en cuestión: Código:
function BD: TBD; begin Result := _BD; end; Esta función está en UBD (donde está definido TBD, y se encuentra en la clausula contains de BD.bpl, osea… está en el paquete BD.bpl). Lo que se me ocurrió fue, cargar el paquete y llamar a esta función que me devuelve el objeto desde el programa de la siguiente manera: Código:
procedure CargarPaqueteBD; type TVarBD = function: TBD; var Lib: String; H: HModule; FuncBD: TVarBD; begin Lib := ExtractFilePath(ParamStr(0)) + 'BD.bpl'; if not FileExists(Lib) then ShowMessage('No existe'); H := LoadPackage(PChar(Lib)); if H <> 0 then begin @FuncBD := GetProcAddress(H, 'BD'); if not Assigned(FuncBD) then ShowMessage('FuncBD NO ASIGNADA!!!'); _BD := FuncBD; end else begin ShowMessage('Error cargando'); end; UnloadPackage(H); end; El problema es que no funciona, siempre me dice “FuncBD NO ASIGNADA!!!”, pero sí logra cargar el paquete correctamente. No se si me expliqué lo suficiente, tal vez confundí en lugar de aclarar… En general, necesito desde el principal recibir el objeto _BD de BD.bpl, y luego enviárselo a todos los paquetes que dependan de la base de datos. Igual pasaría con el modulo de datos Global. Por favor, realmente necesito ayuda con esto… Llevo semanas pensando y probando y todavía no logro dar con la solución… Muchas gracias de antemano. Un cordial saludo a todos, y disculpen si el post es muy grande. |
#2
|
||||
|
||||
Solo por curiosidad... ¿Te acordaste de exportar la función "BD"? Es decir, fíjate si tienes la siguiente sentencia después de la implementación de la función:
(Te lo comento porque la función de Windows "GetProcAddress" solo carga funciones que han sido "exportadas", ya sea que éstas se encuentren en una .bpl o .dll). Edito: Como había dado una explicación que se me quedó algo liosa (no se si alguien llegó a leerla) prefiero adjuntarte un ejemplo de como podrías hacer lo que quieres de una forma más sencilla. Para ver el ejemplo haz lo siguiente: - Descomprime el contenido del archivo en alguna carpeta del disco duro (por ejemplo: "C:\EjemploBPL") - Y, por último, abre el archivo de proyecto "pgPrueba.bpg" para ver todas las fuentes y la aplicación de ejemplo. (Si quieres ver la aplicación funcionar compila todos los paquetes, del primero al último. Cuando compiles, los paquetes .bpl se almacenarán en la carpeta por defecto de Delphi, que suele ser: "C:\Archivos de programa\Borland\Delphi7\Projects\Bpl"). Para ver una pequeña descripción del ejemplo lee el archivo "Leeme.txt" que encontrarás en la carpeta donde descomprimiste el archivo. Espero que te sirva! Saludos! Última edición por jmariano fecha: 24-08-2005 a las 05:15:22. |
#3
|
|||
|
|||
Gracias, pero sigo teniendo problemas :(
Muchas gracias por tu ayuda.
Efectivamente, me faltaba exportar la función BD, pero sigo teniendo problemas... Te cuento... El DataModule de datos (DMDatosZeos) lo utilizo en: · Los paquetes (pero al ponerlo en la cláusula requires, no hay problema y lo puedo ver). · El programa principal. Esto es lo que me está volviendo loco . Como el programa principal utiliza _BD, tengo que agregar al proyecto UBD, pero es sólo para "engañar" al compilador y para que logre compilar, pues el objeto como tal reside en el paquete... Pero que pasa, que la referencia de _BD del programa tengo que hacer que apunte a la referencia de _BD del paquete, pero no logro hacerlo... Es raro que los ejecutables no tengan una clausula requires o algo así cuando trabajan con paquetes... Eso me evitaría todos estos problemas. Para hacer la asignación de _BD del principal a _BD del paquete he probado con :=, con Assign, incluso haciendome un metodo de asignación que asigne también las otras propiedades como el DataModule, y la conexión, y nada de nada... Depuro mostrando mensajes con las direcciones de los punteros Format('@_BD=%p', [@_BD]) y cosas así, y hay referencias que no me salen iguales en el principal y en el paquete... Extrapolando mi problema a tu código fuente, sería algo así: en mi caso tengo que agregar CPrueba.pas al proyecto prueba.exe pues en ese se utiliza la base de datos, con lo cual tengo 2 objetos _Prueba, uno en el paquete y otro en el programa, el del paquete es el que se crea en realidad, pero el del programa debe apuntar (incluidos todos los campos métodos y todo) al del paquete (es decir debe ser como una copia exacta, pero sin ser copia, pero sin ser copia, pues la base de datos es común). Cómo puedo hacer esto? Tal vez deba distinguir si el código de UBD es el del ejecutable o el del paquete mediante alguna directiva IFDEF (o algo así) pues dicha unidad es compartida a los paquetes y al ejecutable algo así como: Código:
{$IFDEF PROJECT=BD.BPL} ShowMessage('Estoy en el paquete'); {$ELSEIF PROJECT=PRINCIPAL.EXE} ShowMessage('Estoy en el ejecutable'); {$ELSE} ShowMessage('Estoy PERDIDO!!!'); {$IFEND} Muchas gracias por tu ayuda, te lo agradezco de verdad. Y gracias a todos los que se han molestado en leer todo este rollo... Un cordial saludo a todos. |
#4
|
||||
|
||||
Saludos otra vez!
No entiendo el porqué duplicas ambos objetos (es decir, porqué has de tener el objeto _BD en el principal y en el paquete) si desde el principal puedes usar el objeto _BD del paquete (sin tener que pasar la referencia de un objeto a otro). Seguramente tu problema sea que quieres usar el objeto _BD del paquete desde el mismo archivo de proyecto .dpr. Para hacer esto lo único que tienes que hacer es especificar el unit "UBD" (donde se encuentra declarado el objeto _BD) en la cláusula "uses" del .dpr pero sin añadir "UBD" al proyecto (porque si lo añades, entonces, no usará el del paquete). Siguiendo con el ejemplo que te mandé, el .dpr quedaría así para usar _Prueba desde el principal:
(El ejemplo mostraría el mensaje "Hola" antes de mostrar el formulario principal). Como te comenté antes, no añadas "CPrueba" al proyecto Prueba.exe para que utilice el objeto _Prueba del paquete "BDPrueba". Y acuerdate también que has de añadir, en las opciones del proyecto Prueba.exe (pestaña "Packages", sección "Runtime Packages"), el paquete "BDPrueba" (más el del formulario "FormPrueba", y en realidad cualquier paquete que necesitemos usar). Edito: Me faltó aclarar (por si aún no has entendido bien como va todo este tema de paquetes) que para que un paquete use las clases u objetos definidos en otro paquete tendremos que añadir a su cláusula "Requires" aquellos paquetes donde se encuentren dichas clases u objetos (después, para hacer uso de la clase u objeto, sólo es necerio especificar, en las cláusulas "uses" correspondientes, aquellas units que contengan el objeto). Además, será necesario especificar en las opciones del archivo de proyecto .dpr (pestaña "Packages", sección "Runtime Packages") aquellos paquetes utilizados de forma dinámica. Y para utilizar una clase u objeto desde el mismo .dpr sólo es necesario (al igual que antes) especificar en la cláusula "uses" las units necesarias. Cualquier duda vuelveme a preguntar! Chao! Última edición por jmariano fecha: 24-08-2005 a las 20:21:27. |
#5
|
||||
|
||||
Hola, voy a hablar un poco en el aire porque no he revisado esto con detenimiento.
A mi me parece que hay una confusión de conceptos. Cita:
Al hacer esto (carga estática de paquetes), sigues teniendo la ventaja de la modularidad que proporcionan los paquetes: puedes hacer cambios en el paquete sin tener que recompilar ni redistribuir toda la aplicación porque el paquete como tal no está en el ejecutable, únicamente el enlace a él. Así que, antes de proseguir te conviene preguntarte si no te es suficiente esto. Posiblemente lo sea y ya no has de darle más vueltas al asunto. El uso de paquetes dinámicos- aquellos cargados dinámicamente con LoadPackage -tiene otra finalidad, una ventaja extra sobre los paquetes estáticos. Veamos un ejemplo. Tienes una aplicación que exporta datos a una base. Para ello tienes una rutina en tu aplicación principal que recibe un DataSet:
Supón ahora que la aplicación debe poder exportar los datos bien sea a MySql, Firebird o cualquier otro motor que el cliente desee en un futuro. Para ello debes proporcionar a la rutina de exportación el DataSet adecuado al motor (TZTable, TIBTable, etc...). Claro que lo más fácil sería:
pero esto condena tu aplicación a usar sólo esos dos motores. Para cualquier otro tendrás que agregar la condición correspondiente, recompilar y redistribuir toda la aplicación. Claro que puedes obtener el DataSet de un paquete; pero si el enlace es estático el cliente tendría que cambiar uno por otro cada vez y reiniciar la aplicación. Aquí es donde la carga dinámica adquiere sentido. El cliente proporciona, desde la aplicación, el nombre del paquete, lo cargas con LoadPackage y obtienes el DataSet que proporcione ese paquete. Si una necesidad similar es la que realmente requieres entonces sí tendrás que estudiar como obtener el objeto BD del cuál hablas. Regresando al principio; incluir la unidad en tu aplicación no sirve porque fuerzas un enlace estático. La solución Cita:
Para usar paquetes dinámicos- evitando el enlace estático, tu aplicación no puede contener ninguna referencia al paquete, ni en la lista de run time packages ni en las cláusulas uses. Si no hay ninguna referencia entonces ¿cómo puedes usar los objetos de los paquetes? Una posible solución es mediante el uso de clases abstractas. Defines una clase base que declare los métodos que requiera tu aplicación pero que no los implemente:
Esta clase la colocas o bien directamente en tu aplicación o bien en un paquete enlazado estáticamente. Con esto tu aplicación compila sin problemas y al ejecutable sólo le estás agregando, por así decirlo, la definición de los métodos. La implementación real de la clase (una clase descendiente) la colocarías en un paquete que, éste sí, cargarás dinámicamente:
Como ves, el paquete también enlaza estáticamente la clase base. Pero esto no es una carga ya ésta clase sólo define pero no implementa. Nota que la clase descendiente TDBZeos ni siquiera tiene que estar en la sección interface. En tu aplicación principal cargas el paquete con LoadPackage tal como lo has hecho y obtienes la dirección de la función CrearObjetoDB que exporta el paquete:
Como la aplicación sí usa la unidad DB_Base que define la clase base, el compilador no protesta ni en la declaración de la variable BD ni en la llamada al método HazAlgo. Pero el polimorfismo hace que el objeto real sea de tipo TDBZeos. En cualquier momento puedes programar otro paquete para otro motor de datos y tu aplicación estará lista para usarlo tan sólo especificando el nombre del paquete. Nota además, que ni siquiera se requiere el uso de RegisterClass ni GetClass con lo cual no estás restringido a usar descendientes de TPersistent. Espero no estar equivocado en los conceptos y que esto te aclare las cosas. // Saludos |
#6
|
||||
|
||||
Cita:
Tened en cuenta que cuando se compila un paquete se genera, a parte del .bpl, un archivo de extensión .dcp (que equivaldría a los .dcu de las unit). Este archivo es el que especificamos en la cláusula "Requires" de los paquetes y en la lista "Runtime packages" del archivo de proyecto, y realizar esto sirve precisamente para indicar a la aplicación que paquetes han de ser cargargados dinámicamente (y no enlazarlos estáticamente), evitándonos así todo el proceso que representa la carga manual de paquetes. (Para verlo más fácilmente, imaginemos que cada vez que especificamos un archivo .dcp se está añadiendo a una "lista", dentro del ejecutable o .bpl, aquellos paquetes que han de ser cargados dinámicamente). Por último, aclarar nuevamente que es necesario especificar en la lista "Runtime packages" del archivo de proyecto aquellos paquetes que se desean usar de forma dinámica (porque sino, entonces, sí que se enlazaran estáticamente). (Esta es una de las razones del porqué los ejecutables son tan grandes en Delphi, y cuando hacemos uso de los paquetes de ejecución se "encogen"). Última edición por jmariano fecha: 25-08-2005 a las 14:51:13. |
#7
|
||||
|
||||
Cita:
El que un paquete esté dentro de esta lista no significa que el paquete sea dinámico. El colocar un paquete en esta lista significa simplemente que el paquete residirá fuera del ejecutable (razón por la cual el tamaño del ejecutable decrece). Sin embargo, cualquier referencia al paquete dentro de la aplicación (no me estoy metiendo aquí con otros paquetes y su lista de paquetes requeridos), hará que el enlace sea estático, aún estando fuera del ejecutable. Si se quita el archivo bpl la aplicación marcará un error al iniciar ya que, al ser un enlace estático, el ejecutable busca este bpl desde el inicio. Es similar a las bibliotecas dll (los bpl son a fin de cuentas un tipo especial de dlls). Si existe una sóla referencia a la biblioteca dentro de la aplicación, el ejecutable la buscará desde el inicio (enlace estático) por lo que fallará si no está presente el dll. Si se desea un enlace dinámico a la biblioteca entonces debe omitirse cualquier referencia a ella y cargarla con LoadLibrary. La conclusión entonces es: si el paquete está en la lista de run time packages y por otro lado se usa LoadPackage para cargarlo, entonces se habrá cargado dos veces el paquete; una vez al inicio de la aplicación y otra al usar LoadPackage. En resumen, la calidad de estático o dinámico se refiere a la forma en que se enlaza el paquete, no a si éste reside o no dentro del ejecutable. Puede leerse el conocido artículo Dynamic packages in Delphi de Vino Rodrigues donde específicamente señala los siguientes puntos para enlazar dinámicamente un paquete: Cita:
Última edición por roman fecha: 25-08-2005 a las 14:40:48. |
#8
|
||||
|
||||
Edito: (Edité porque te leí mal y la explicación que di carece, por tanto, de sentido)
Tienes razón (y me equivoqué yo, o más bien me lié con todo este tema de paquetes, je!) en que los paquetes cargados dinámicamente con "LoadPackage" no hay que especificarlos en la lista "Runtimes packages" (como es lógico, sólo es necesario especificar aquí los que queremos que cargue la aplicación de forma automática). (A parte, el significado que yo lo estaba dando a la palabra "estático" era a la "integración" dentro del ejecutable o .bpl). De todas formas, si miráis el ejemplo que subí, sólo el paquete "BDPrueba.bpl" es cargado por la aplicación al inicio (ya que se usa un objeto en el principal), pero el formulario mostrado si es cargado dinámicamente a través de la función "LoadPackage". Mi intención con el ejemplo era mostrar una forma más sencilla de hacer uso de un objeto (implementado en otro paquete) que es utilizado tanto desde el mismo programa principal como desde los demás paquetes relacionados con el programa (los cuales si se cargarían dinámicamente con "LoadPackage"), ya que, por el uso que tiene, resulta innecesario complicarse con la carga manual del objeto. Saludos! Última edición por jmariano fecha: 25-08-2005 a las 16:21:14. |
#9
|
||||
|
||||
Releyendo el mesaje original y para concretar ideas, lo que yo le recomendaría a adlfv es olvidarse de los paquetes dinámicos.
En la unidad UBD colocaría una función así:
Es decir, en lugar de crear el objeto en la inicialización, se crea hasta la primera vez que se use. Cualquier otro paquete que requiera de éste lo agregará (valga la redundancia) en su lista de paquetes requeridos. La aplicación principal incluye UBD (cláusula uses) con lo cual puede usar libremente ObtenerBD (y por tanto _BD) sin necesidad de cargar explícitamente el paquete ni importar la función. Y claro, usar la opción "Build with RunTime Packages" y agregar el paquete BD en la lista. Con esto, la aplicación queda modularizada: se pueden hacer cambios a BD, recompilarlo y, sin necesidad de recompilar la aplicación, la nueva funcionalidad estará lista. // Saludos |
#10
|
|||
|
|||
Gracias
Muchas gracias a los dos por responder.
Sus respuestas me han encaminado respecto a el tema de los paquetes para el caso de la BD, pero ahora tengo otra duda. He estado viendo una web (bastante antigua) que habla de paquetes y conseguí algo que me pareció bastante interesante que plantea el siguiente código: Código:
var ThisInterface: TAddinModuleInterface; initialization ThisInterface := InitAddinModuleInterface(HInstance); with ThisInterface do begin Caption := 'Module &1'; AddMaintenanceForm('&Customers', 'TfrmCustMaint'); AddMaintenanceForm('&Employees', 'TfrmEmpMaint'); AddEnquiryForm('&Sales Orders', 'TfrmOrderEnquiry'); AddReportForm('&Phone List', 'TfrmCustomerPhoneList'); MergeUserInterface; end; La versión del ejemplo es antigua, utiliza D4 y QuickReports, mientras que yo no dispongo de QuickReports y utilizo D2005. Estoy intentando migrar dicho código, para obtener una funcionalidad similar pero desgraciadamente no comprendo aún bien el código. Por ejemplo, el autor de esto lo hace mediante el uso del registro, y no logro comprender el por qué lo hace así. Si alguien conoce un código similar, o me puede orientar sobre cómo hacerlo le estaría muy agradecido. La idea es en cada paquete "registrar" el formulario en la seccion de inicialización y que salga automáticamente la opción para acceder a dicho formulario en el menú del programa. Muchas gracias de antemano. Ahh, se me olvidaba... Estoy intentando hacer la aplicación con arquitectura de plugins, osea que habran varios módulos que el usuario final podrá decidir si quiere o no. Para estos módulos sí debería hacerlo con paquetes dinámicos, no? Pues si no existen, el programa debe funcionar igual (sin fallos) pero sin la funcionalidad encapsulada en dicho paquete... Muchas gracias de nuevo. Un cordial saludo. |
|
|
|