-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 1/33
Delphi Bsico
Dar una buena imagen!
Parte 2
Salvador marzo 25, 2013 Advertencia,
Artculos, Cdigo, DataSnap, Delphi, Entrada Diario, FireMonkey,
LiveBindings, Uncategorized, XE3
Si os parece bien, recapitulamos: Comentaba al
finalizar la entrada anterior, Dar una buena imagen!
Parte 1, que estbamos en condiciones de proseguir,
toda vez que habamos generado una base de datos y
almacenado algunas imgenes en ella, que era uno de
los requisitos que poda demandar el hecho de
habernos planteado devolver una imagen a travs de
nuestro servidor de DataSnap. No es que realmente
nos hiciera falta explcitamente contar con una base
de datos, pero recordando lo compartido en aquellas
lineas, me pareca un poco mas didctico a efectos
Al da con HTML5 Builder
y F.Charte
Buscar
Search this website
CategorasElegir categora
A cerca de
m
Salvador Jover Proyecto
DelpHispano
SELECCION DE CONTENIDO (2003-2013) THE EMBARCADERO MVP PROGRAM
DELPHISPANO FB DELPHI SOLIDARIO MAPA LEGAL
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 2/33
del blog y de las reflexiones que motivara su
contenido. Por otro lado, hacer el cambio, y sustituir
este origen por el de un fichero grfico, era sencillo y
bastaba cambiar un par de lineas. As pues, y
resumiendo, tomamos la decisin de guardar la
imagen en la base de datos en un campo cuyo tipo de
dato es adecuado a su contenido, habitualmente
binario, en lugar de conservar la ruta donde ste est
ubicado, y creamos una pequea utilidad para
alimentar y gestionar la tabla de imgenes.
Menciones a mtodos en DataSnap
Sera bueno que nos detuviramos por un momento,
antes de seguir adelante, y dedicramos unas lineas a
hablar, o reflexionar, sobre los mtodos en DataSnap,
que no siempre existieron tal y como ahora los
entendemos, y que fueron una muy buena aportacin
tras la llegada de Embarcadero y aplicar cambios
importantes y profundos en esta tecnologa.
Los mtodos remotos fueron uno de esos cambios
importantes. En la docwiki de Embarcadero existe
bastante informacin, -aunque nunca sea suficiente-,
y de hecho, he buscado, para que pudieran quedar
reflejados en esta segunda parte, algunos enlaces que
nos puedan ayudar, de forma que nos permitan
introducir el tema. Concretamente, el enlace principal
que lo abre, Developing DataSnap Applicatins, que
es uno de esos enlaces que es bueno tener a mano ya
que, adems de los apuntes hacia las distintas
referencias al tema de la docwiki, se enumeran una
buena cantidad de recursos externos, enlazados
desde vdeos, cdigo fuente y artculos tcnicos.
Tambin tengo anotados por su utilidad, el enlace
que abre el tema propiamente de los
mtodos Exposing DataSnap Server Methods, y el que
hace referencia y enumera los distintos tipos de datos
que se soportan dentro del marco de
DBExpress Data.DBXCommon.TDBXDataTypes.
Redes Sociales
PROMOCIN
XE4!
Comunidad
Australiana
Australian
Delphi User
Group
Members
Comunidad
de Brasil y
Portugal
PRXIMO
EVENTO
XE4!
Grupo
Facebook
Delphi
SolidarioS parte del
grupo de
facebook ms
activo de
nuestra
Comunidad
Hispana:
Delphi
Solidario.
Unete a
nuestro
esfuerzo por
hacer de la
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 3/33
Sin embargo, si nos referimos en concreto a la
informacin sobre el uso de parmetros de tipo
TStream, Jim Tierney dej escritos varios artculos
imprescindibles, en los que se aborda el tema con
bastante detalle. Uno de estos enlaces es DataSnap
Server Method Stream Parameters, cuya lectura es
necesaria, as como revisar el cdigo fuente que
enlaza
desde http://cc.embarcadero.com/item/26854.
En la parte tercera de esta serie de artculos, al final
de la misma, intentar recopilar todos los enlaces que
me sea posible encontrar y que os puedan ser
necesarios e interesantes. De hecho, durante los dos
aos anteriores y este, se incrementa la
documentacin tcnica con la aportacin de algunos
artculos de compaeros de nuestra comunidad, que
han ido introduciendo en la red las distintas reas de
conocimiento de datasnap, aportando su granito de
arena. Por supuesto, gran parte de esa posible
lista sern recursos de habla inglesa.
Consideraciones previas a tener en cuenta
Todos tenemos una idea intuitiva de qu es
un mtodo remoto expuesto por el servidor de
DataSnap, porque a fin de cuentas, no dejan de ser
nada distinto de los mtodos que habitualmente
expone una clase. DataSnap, nos permite as, desde
una aplicacin cliente, invocar mtodos que se
ejecutan en otra aplicacin que acta como un
servicio y que atiende o queda a la escucha de estas
peticiones, bien a travs del protocolo http o https,
bien a travs de tcp/ip. Bsicamente es eso.
A menudo y de forma corporativa, hemos conocido
esta imagen que resume y ofrece una vista amplia de
DataSnap.
Active Delphi
Fernando
Rizzato: Delphi
for Enterprise
Comunidad
EEUU y
habla
inglesa
Andy's Blog
and Tools
blog.marcocantu.com
marco's tech
world
Delphi Insider
Felix Colibri-
Delphi Source
Code,
Technical
Articles, Delphi
Trainings,
Delphi
Consulting
and
Development
Nick Hodges
Sip from the
Firehose
The Hackers
Corner |
Complex
problems
have simple
easy-to-
understand
wrong
answers
Comunidad
Hispana de
Delphi en
facebook un
sitio de
encuentro y
productividad.
Miembros: 384
Moderadores:
4
Meta
Acceder
RSS de las
entradas
RSS de los
comentarios
WordPress.org
Suscrbete
al blog por
correo
electrnicoIntroduce tu
correo
electrnico
para
suscribirte a
este blog y
recibir
notificaciones
de nuevas
entradas.
nete a 1
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 4/33
Permitid me que deje algunas ideas en el aire, aun a
costa de simplificar.
Dejado aparte todas aquellas clases que colaboran en
la parte digamos propia de la estructura del servicio
(las que crean propiamente el servicio como tal, fijan
y configuran los distintos canales, abriendo o
cerrando comunicaciones, etc.), quizs la primera
consideracin que se me ocurre para ayudaros en esa
primera toma de contacto, es resaltar que solo van a
poder exponer mtodos remotos las clases
descendientes TDataModule y TDSServerModule, as
como cualquier descendiente de TPersistent, pero
esta ultima opcin requerira de vuestra parte un
esfuerzo adicional ya que os obligara a implementar
las capacidades de servicio que ya vienen
introducidas desde TDataModule o
TDSServerModule. No parece lo mas habitual, por lo
que, a efectos prcticos, consideraremos que vuestra
atencin se va a centrar realmente en estas dos
clases, que son las que usareis en vuestro da a da.
La segunda idea es que un TDSServerModule es un
descendiente de la clase TDataModule y por lo tanto,
extiende la clase y la funcionalidad de
sta. Bsicamente la diferencia que os guiar en la
necesidad de hacer uso de una u otra ser, que
vuestro modulo de datos en el servidor deba exportar
datasets hacia el cliente. En ese caso, para que sean
visibles estos datasets y podais conectarlos a travs
del driver de datasnap, el contendor ser la clase
TDSServerModule.
The Road to
Delphi a
Blog about
programming
Comunidad
Hispana
/*Prog*/
Delphi-Neftal
/*finProg*/
Andreano
Lanusse
Making Things
Happen
Aprendiendo
Firemonkey
Blog de Carlos
G
Daniel Luyo
Daniel Magin
Delphi en
Movimiento
Delphi Live
Delphi Magic
Delphi y
FastReport
El blog de
cadetill
El blog de
jachguate
El Blog de
Jhonny Surez
El blog del Poli
Glenn
suscriptor
Correo electrnico
Subscribir
Comentarios
recientes
Jose E Leyva
en Dieciocho
aos de
Delphi y un
largo camino
por andar
Salvador en
Dar una
buena
imagen! Parte
2
GustavoSV en
Dar una
buena
imagen! Parte
2
Al Gonzlez en
Dieciocho
aos de
Delphi y un
largo camino
por andar
Eliseo
Gonzalez
Nieto en
Dieciocho
aos de
Delphi y un
largo camino
por andar
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 5/33
Si damos un vistazo a la unidad DataSnap.DSServer,
veremos su interfaz como sigue:
{$MethodInfo ON}
TDSServerModule = class(TDSServerModuleBase)
end;
{$MethodInfo OFF}
Esto enlaza con algo que nos comentaba desde la
lectura de la ayuda en linea, cuando se nos dice que
es necesario aplicar la directiva {$MethodInfo} para
que los mtodos sean visibles desde la clase
TDataModule. Esta directiva, {$MethodInfo
on/off} trabaja conjuntamente con la que ayuda a
nuestro compilador a generar informacin RTTI (Run
Time Type Library) de clase, {$TYPEINFO ON/OFF},
haciendo necesario que ambas estn activas en el
estado de la clase. De hecho, cuando nos dicen que
los descendientes de TPersistent van a poder
exponer mtodos remotos es porque esta clase -
TPersistent- activa la informacin de tipos y por lo
tanto es una posible candidata en el nivel mas
elevado de esa jerarqua, cuyos descendientes van a
heredar tambin dicho estado. La directiva
{$TYPEINFO} es equivalente a {$M+}.
Podemos entender el papel de la RTTI, si
consideramos que precisamente, la generacin de
esta metadata adicional en el servidor, ayudar al
sistema a descubrir los mtodos a travs de una
cadena de texto literal enviada en el mensaje
compartido entre la aplicacion cliente y
la aplicacin servidor, cadena de texto que representa
la clase y el mtodo a invocar por el servidor. Si
repasamos, por ejemplo, las lineas
generadas automticamente en la unidad proxy, que
es una plantilla que se crea a peticin del usuario con
la informacin de los mtodos que descubre el
servidor en el contexto de la aplicacin cliente, para
facilitarnos no tener que mantener esta tarea que
Stephens
La Web de
Seoane
Legin Delphi
Luis Felipe
Gonzlez
Torres
Embarcadero
MVP
Puro Cdigo
Blog de Jos
Castillo
Rescatando a
Delphi
Software: de
Mexico para el
mundo
Waooo
Delphi!
Embarcadero
MVP
Adriano
Santos
Alan Fletcher
Alan Glei
Alexander
Alexeev
Alexander
Bozhko
Alister Christie
Andrey
Terekhov
Bob Swart
COMUNIDAD
Recursos en la
Red
Publicaciones
Truco o
trato
Foros
Stackoverflow
en espaol
Embarcadero
Uso del
componente
TListView en
nuestras
aplicaciones
mviles mayo
16, 2013 Jos
Prez
Le Dev du
Jeudi Episode
11 mayo 16,
2013 tlaborde
Ven a verme
en Ciudad de
Mxico y
Guadalajara
mayo 16, 2013
Anders Ohlsson
RAD Studio
XE4 Whats
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 6/33
puede ser repetitiva y tediosa, la implementacin que
recrea la invocacin del mtodo EchoString se
presenta como sigue:
function TServerMethods1Client.EchoString(Value: string): string;
begin
if FEchoStringCommand = nil then
begin
FEchoStringCommand := FDBXConnection.CreateCommand;
FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod;
FEchoStringCommand.Text := 'TServerMethods1.EchoString';
FEchoStringCommand.Prepare;
end;
FEchoStringCommand.Parameters[0].Value.SetWideString(Value);
FEchoStringCommand.ExecuteUpdate;
Result := FEchoStringCommand.Parameters[1].Value.GetWideString;
end;
He incluido en negrita la cadena que identifica
el mtodo a invocar. Dicha cadena representa la clase
que contiene el mtodo y su nombre. De no existir
esa informacin adicional extra, asociada a la clase,
no seriamos capaz de encontrar ni los actores ni las
acciones. En agosto del 2007 compartimos una
entrada en el blog que os puede ayudar a entender
este punto: La Granja y en la que precisamente
vemos como invocar un mtodo en la clase a travs
de su nombre. Creo recordar
que hacamos trotar graciosamente a la clase
TCaballo, adems de otras tropelias como alimentarle
o permitir que relinche. La RTTI se nos presenta a
varios niveles: desde un nivel mnimo que hace que
una instancia sea capaz de reconocerse en una clase,
que podramos llamar autoconciencia , hasta un
nivel ms extendido que permite a la clase describirse
y reconocerse en toda la funcionalidad que el
desarrollador de la misma autorice a conocer y
describir. Las zonas publicadas (published) de la clase,
forman parte de esas zonas que generan informacin
extendida y metadata adicional que la RTTI ya recoga
con la directiva {$M+}. Al activar la directiva
Brian Long
Bruno Fierens
Caique
Rodrigues
Carlos
Henrique
Agnes
Cary Jensen
Daniel Wildt
Daniel Wolf
Danny Wind
Didier Cabale
Eliseo
Gonzalez
Felix Colibri
Filip Lagrou
Francois Piette
Germn
Estvez Ruiz
Guinther Pauli
Jim McKeeth
Jose Castillo
Juan Antonio
Castillo
Kelyn Pena
Lachlan
Gemmell
Luciano
Pimenta
Luis Alfonso
Rey
new in the IDE
(X) Info.plist
mayo 16, 2013
Jose Leon
Getting
TWebBrowser
on iOS to
zoom mayo
15, 2013
Anders Ohlsson
Report do
Delphi Tour
Multi-Device:
Porto Alegre
mayo 15, 2013
Fernando
Rizzato
Delphi for iOS
in Zagreb,
Croatia mayo
15, 2013 Pawel
Glowacki
iOS app dev
with Delphi
XE4 and
FireMonkey 3
is a quantum
leap beyond
XE2 mayo
14, 2013 David
Intersimone
Report do
Delphi Tour
Multi-Device:
Florianpolis
mayo 14, 2013
Fernando
Rizzato
RAD Studio
XE4 Whats
new in the IDE
(IX) Misc
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 7/33
{$MethodInfo} ayudamos a que la zona publica de la
clase genere tambin esa informacin extra de tipos
que de otra forma no se generara.
La tercera idea, en la que me gustara incidir es que
una gran parte de los mtodos, se nos
presentan ntimamente ligados a la informacin de
tipos. Es decir, podemos encontrar la necesidad de
ejecutar un procedimiento remoto que no
necesite parmetros pero lo habitual es que
existan parmetros de entrada y un tipo de retorno,
en el caso de las funciones. As que en este punto nos
podemos hacer la pregunta que este compaero me
hacia entre lineas: Que tipo de dato necesito declarar
como retorno de mi funcin, para exportar la
imagen? Y si necesito una fecha? Puedo retornar un
tipo propio de la logica de mi aplicacin, como por
ejemplo una instancia de la clase TCliente?
La respuesta a estas preguntas, la encontramos en los
enlaces que compartamos en las lineas superiores de
la entrada. En el primero de ellos, encontramos lo
que buscamos: No todos lo tipos son soportados
como parmetros.
Luis Felipe
Gonzlez T.
Magnus Flysjo
Marcelo
Nogueira
Marcelo
Varela
Marco Cantu
Marco Santn
Marcos
Antonio
Moreira
Marcos P.
Gomes
Martin Strohal
Mike Sutton
Newton
Gaucho
Nick Hodges |
A man's got to
know his
limitations
Nirav Kaku
Olaf Monien
Pedro Bento
Radek
Cervinka
Ray Konopka
Ricardo Boaro
Robert Love
Salvador Jover
Samuel David
Changes mayo
14, 2013 Jose
Leon
EDN
Visual
Video: Using
the iOS
TWebBrowser
Component in
Delphi -
iOS_WebBrowser
Code Snippet
mayo 12, 2013
Video: Using
IBLite in
Delphi iOS
Apps mayo 10,
2013
Video: iOS
SendCancelNotifications
in Delphi
mayo 8, 2013
Video: Using
iOS
AudioRecPlay
in Delphi to
Record and
Play Back
Sounds mayo
6, 2013
Video: Using
iOS Location
Services in
Delphi apps
mayo 4, 2013
Video: Using
the iOS
DatePicker in
Delphi
applications
but not all the parameter types are
supported. The supported types are:Boolean
SmallInt Integer Int64 Single Double
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 8/33
Uwe Raabe
Victory
Fernandes
Vladislav
Bajenov
Xavier
Martinez
Zarko Gajic
Foros
Hispanos
Club Delphi
Comunidad
Delphiaccess
Sitio
oficial y
Partners
Danysoft
Embarcadero
with
TCalendarEdit
mayo 2, 2013
Get Set Up for
Delphi XE4 iOS
Development
abril 30, 2013
Video: Build
Your First
Multi-Device
Delphi App
abril 28, 2013
Building a C++
multi-tier
master/detail
Customer and
Sales
database
application
abril 26, 2013
Using the
Boost C++11
Algorithms
Library with
the C++Builder
64-bit
compiler abril
26, 2013
Programa Certificacin
Delphi
Programa de Certificacion de
Delphi
AnsiString
String TDBXTime TDBXDate TJSONValue and all descendents TDBXWideStringValue TDBXAnsiStringValue TDBXUInt8Value TDBXInt8Value TDBXInt16Value TDBXInt32Value TDBXInt64Value TDBXSingleValue TDBXDoubleValue TDBXBcdValue TDBXTimeValue TDBXDateValue TDBXTimeStampValue TDBXBooleanValue TDBXReaderValue
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 9/33
En la entrada de Jim Tierney
los encontris clasificados y agrupados de una forma
mas clara, incluyendo algunos que no se mencionan
explicitamente en la lista.
Basic Basic DBXValue Collection Connection
AnsiString
Boolean
Currency
TDateTime
TDBXDate
TDBXTime
Double
Int64
Integer
LongInt
OleVariant
Single
SmallInt
WideString
TDBXAnsiStringValue
TDBXAnsiCharsValue
TDBXBcdValue
TDBXBooleanValue
TDBXDateValue
TDBXDoubleValue
TDBXInt16Value
TDBXInt32Value
TDBXInt64Value
TDBXSingleValue
TDBXStringValue
TDBXTimeStampValue
TDBXTimeValue
TDBXWideCharsValue
TDBXWideStringValue
TDBXReader
TDataSet
TParams
TStream
Collection DBXValue
TDBXReaderValue
TDBXStreamValue
TDBXConnection
Connection DBXValue
TDBXConnectionValue
Por lo tanto, ahora s estamos en posicin de
responder a las preguntas que nos formulbamos.
No existe un parmetro que identifique directamente
a una instancia de TImage o de la clase TBitmap. Sin
embargo, genricamente nos podemos apoyar en el
uso de una clase como TStream que permite
manipular esta informacin como una secuencia de
bytes, optando por cualquiera de los descendientes
Niveles:
1. Delphi Certified Developer
2. Delphi Certified Master
Developer
Estadisticas
Inicio: 08/03/2012
Estadsticas anteriores
Delphi Bsico . 2003-2013
La fe no es la simple aceptacin
de unas verdades abstractas,
sino una relacin ntima con
Cristo que nos lleva a abrir
nuestro corazn a este misterio
de amor y a vivir como personas
que se saben amadas por Dios.
Benedicto XVI
TDBXStreamValue
Both procedure and functions aresupported, as well as out and varparameters.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 10/33
que veamos apropiado para el contenido real.
Tambin ahora estamos en condiciones de entender
porque en el ejemplo del Restaurante, Luis Alfonso
Rey se puede apoyar en la clase TDataSet como
retorno de una de las funciones que su servidor
exportaba. Recordad que el cdigo fuente de esta
demo que sirvi en la exposicin durante las ultimas
presentaciones del partner espaol se encuentra a
vuestra disposicin en el link de la entrada
inmediatamente anterior a esta.
Primer paso: Nuestro servidor de datasnap
Aunque sea repetitivo, he incluido las capturas de los
primeros pasos, para que aquellos que se inician no
tengan que tirar de imaginacin o memoria.
Lo primero de todo, vamos a crear la aplicacin que
va a dar el servicio, nuestro servidor de datasnap. Va
a ser un servidor bsico, basado en tcp/ip y no
expondr sus servicios a travs de http.
Accedemos al men FILE -> NEW -> OTHER ->
DataSnapServer. Seleccionamos la opcin DataSnap
Server entre las tres disponibles.
Una vez en ese punto, accederemos a un wizard que
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 11/33
nos va a permitir configurar el servidor. He
seleccionado la opcin VCL Forms Application ya que
no va a ser un servicio integrado en el sistema ni un
proyecto de tipo consola sino que ser una aplicacin
monda y lironda.
La siguiente parada ser para determinar como va a
ser el servidor. Nuestro wizard necesita conocer
como va a ser ese servidor, que capa de transporte de
datos va a usar, si existirn filtros, si es una
comunicacin segura y si adems, deseamos que
incluya los mtodos de ejemplo que se aaden de
ayuda. En el caso del servidor bsico se incluyen
los mtodos EchoString y ReverseString, por defecto
pero para servidores de tipo Rest, el sistema incluye
adicionalmente un test en formato html capaz de
evaluar los mtodos que exporta el servidor,
incluyendo los administrativos.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 12/33
En el siguiente paso, vamos a poder hacer un test al
puerto 211, que es el que por defecto asigna
DataSnap.
Y finalmente, seleccionamos de que clase heredar la
creada por nosotros para albergar los
distintos mtodos que exportar el servidor. Yo he
elegido TDSServerModule ya que, en este caso
concreto, s voy a incluir al menos un dataset. Existir
un componente TSQLQuery que he llamado
Imagenes_Interbase con el fin de que se pueda
recorrer el contenido de la tabla.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 13/33
Al finalizar el wizard, se habr creado el proyecto en
el que existirn varias unidades, que representan
respectivamente el formulario de la aplicacin, el
mdulo que contiene los componentes que generan y
configuran la estructura del servicio y un modulo
adicional que el sistema por defecto denomina
ServerMethodsUnit1, conteniendo la clase que va a
exponer los servicios. En dicho modulo estn
publicados los mtodos de ejemplo
que mencionbamos.
Dejadme que escriba unas lineas de cdigo.
Dad le un vistazo al formulario, tal y como queda tras
aadir los componentes y comentamos.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 14/33
Nuestro TDSserverModule, es muy sencillo y contiene
un query para recorrer la tabla y que sea el usuario
desde la aplicacin cliente quien pueda seleccionar
cual de las imgenes quiere que sea devuelta por
el mtodo remoto, y un segundo query que
efectivamente la devolver mediante el mtodo
adecuado, apoyndose en el indice de esa imagen
como parmetro de entrada. Para ello,
parametrizamos la consulta de
GetImageBanner_Intebase, que recibe en un integer la
clave primaria que identifica la imagen que
deseamos:
Select Banner
from Banners
where IDBanner = :IDBanner
Veamos el cdigo que hemos escrito a tal efecto:
unit ServerMethodsUnit1;
interface
uses System.SysUtils, System.Classes, Datasnap.DSServer, Datasnap.DSAuth,
Data.DBXMSSQL, Data.FMTBcd, Data.DB, Data.SqlExpr, Datasnap.Provider, Data.DBXJSON,
Data.DBXInterBase, Vcl.Dialogs;
type
TServerMethods1 = class(TDSServerModule)
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 15/33
demos_interbase: TSQLConnection;
Imagenes_Interbase: TSQLQuery;
dspImagenes_Interbase: TDataSetProvider;
Imagenes_InterbaseIDBANNER: TIntegerField;
Imagenes_InterbaseBANNER: TBlobField;
GetImageBanner_Interbase: TSQLQuery;
dspGetImageBanner_Interbase: TDataSetProvider;
GetImageBanner_InterbaseBANNER: TBlobField;
procedure demos_interbaseBeforeConnect(Sender: TObject);
private
{ Private declarations }
function GetImagenBannerAsStream(ASQLQuery: TSQLQuery; AIDBanner: Integer): TStream; overload;
public
{ Public declarations }
function EchoString(Value: string): string;
function ReverseString(Value: string): string;
function GetImagenBannerAsJSON(AIDBanner: Integer): TJSONArray;
function GetImagenBannerAsStream(AIDBanner: Integer): TStream; overload;
end;
implementation
{$R *.dfm}
uses System.StrUtils, UMainServer, Data.DBXJSONCommon;
function TServerMethods1.GetImagenBannerAsStream(ASQLQuery: TSQLQuery; AIDBanner: Integer): TStream;
var
stream: TMemoryStream;
begin
if (ASQLQuery.Active) then ASQLQuery.Close();
ASQLQuery.ParamByName('IDBanner').Value := AIDBanner;
ASQLQuery.Open();
stream := TMemoryStream.Create;
stream.WriteData(ASQLQuery.FieldByName('Banner').AsBytes,
Length(ASQLQuery.FieldByName('Banner').AsBytes));
//stream.SaveToFile('test_ahora.jpg');
stream.Seek(0, System.Classes.TSeekOrigin.soBeginning);
ASQLQuery.Close();
Result:= stream;
end;
function TServerMethods1.GetImagenBannerAsJSON(AIDBanner: Integer): TJSONArray;
var
stream: TStream;
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 16/33
begin
stream:= GetImagenBannerAsStream(AIDBanner);
try
Result:= TDBXJSONTools.StreamToJSON(stream, 0, stream.Size);
finally
stream.Free;
end;
end;
function TServerMethods1.GetImagenBannerAsStream(AIDBanner: Integer): TStream;
begin
Result:= GetImagenBannerAsStream(GetImageBanner_Interbase, AIDBanner);
end;
procedure TServerMethods1.demos_interbaseBeforeConnect(Sender: TObject);
begin
demos_interbase.Params.Values['Database'] := MainServer.GetPath;
end;
function TServerMethods1.EchoString(Value: string): string;
begin
Result := Value;
end;
function TServerMethods1.ReverseString(Value: string): string;
begin
Result := System.StrUtils.ReverseString(Value);
end;
end.
La pregunta mas interesante que yo me hara en
vuestro caso, a la vista de la inspeccin ocular del
cdigo, podra ser:
Cuando escribimos una asignacin al valor de retorno
de la funcin, como el que podemos ver en la linea
inmediatamente inferior, qu sucede con la reserva
de memoria que se ha hecho para gestionar el
contenido del stream a devolver?
Result:= GetImagenBannerAsStream(GetImageBanner_Interbase, AIDBanner);
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 17/33
Fijmonos que la funcin
privada, GetImagenBannerAsStream, declara una
variable local de tipo TMemoryStream que va a ser
instanciada cuando devolvemos un TStream, sin que
por ningn lado se gestione que sta sea liberada. Sin
embargo, al escribir la funcin que retornar esa
misma imagen en el interior de una estructura del
tipo TJSONArray, si que puedo liberar la memoria, ya
que esa memoria asociada a la instancia del stream
no forma parte del retorno sino que el mtodo de
clase
TDBXJSONTools.StreamToJSON( )
hace su propia reserva para la estructura TJSONArray
y copia en ella el stream. Una de las cosas que
aprend, al dar mis primeros pasos en el entorno, es
que cada palo aguantaba su propia vela y que si
en algn momento he gestionado la reserva de
memoria por el motivo que sea, debo
responsabilizarme de su liberacin. Eso sucede
cuando instanciamos objetos, seres inferiores sin
padre que les cobije. Tener un propietario, es
una garanta que se responsabilizara de lo que suceda
con esa criatura. Los componentes tienen esos
privilegios as como sus descendientes. Gente con
suerte!
Sin embargo para un TStream o cualquiera de sus
multiples caras, el sistema encuentra una solucin
pactada. El parmetro InstanceOwner, de tipo
boolean, media en el dilema abierto. Os leo
un prrafo de la entrada de Jim Terney.
Accessing Values
The GetStream and SetStream methods
have an InstanceOwner parameter.
Passing True indicates that DBX owns the
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 18/33
Si a continuacin observamos con detenimiento
los mtodos generados por el proxy para devolver el
stream
function TServerMethods1Client.GetImagenBannerAsStream(AIDBanner: Integer): TStream;
begin
if FGetImagenBannerAsStreamCommand = nil then
begin
FGetImagenBannerAsStreamCommand := FDBXConnection.CreateCommand;
FGetImagenBannerAsStreamCommand.CommandType := TDBXCommandTypes.DSServerMethod;
FGetImagenBannerAsStreamCommand.Text := 'TServerMethods1.GetImagenBannerAsStream';
FGetImagenBannerAsStreamCommand.Prepare;
end;
FGetImagenBannerAsStreamCommand.Parameters[0].Value.SetInt32(AIDBanner);
FGetImagenBannerAsStreamCommand.ExecuteUpdate;
Result := FGetImagenBannerAsStreamCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;
La definicin del parmetro FInstanceOwner en
GetStream( ) es la que establece como se va a liberar
la memoria reservada previamente, de forma que no
existan problemas derivados de su no liberacin.
FInstanceOwner se estableca con un valor en el
mismo constructor de la clase que va a exponer
los mtodos
constructor TServerMethods1Client.Create(ADBXConnection: TDBXConnection; AInstanceOwner
begin
inherited Create(ADBXConnection, AInstanceOwner);
end;
En este caso, tomaba el valor True (lo veris en el
interior del evento onCreate del formulario cliente,
stream and will free it. Passing False
indicates that the caller owns the stream.
To control how the generated proxy
classes call SetStream and GetStream,
there is an AInstanceOwner parameter
on the proxy class constructor:
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 19/33
que usar el proxy). Es lgico que sea el cliente el que
decida qu hacer con esa memoria y al asignar True
lo que implica es que sea el propio sistema el que se
encargue de liberar esa memoria y por lo tanto, no
tendremos que hacer una llamada explicita para ello.
Cuando no sea necesaria esa informacin, en una
llamada posterior, el sistema podr decidir si libera la
instancia antes de obtener un nueva invocacin.
Segundo paso: Nuestra aplicacin cliente.
Para hacer la aplicacin cliente, no vamos a crear
mas que un formulario, aunque esto no sea
demasiado ortodoxo.
Os muestro una imagen del formulario
que tambin es muy bsico y sencillo. El usario
pulsar el boton Abrir DataSet y podr elegir una de
las imagenes en el componente TImage inferior.
Luego, al pulsar uno de los dos botones de la parte
derecha, se ejecutara uno de los dos mtodos a los
que alude.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 20/33
Este es el cdigo que he escrito a tal efecto.
unit UMainClient;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
Data.DBXDataSnap, IPPeerClient, Data.DBXCommon, Data.FMTBcd, Data.DB,
Data.SqlExpr, FMX.Objects, Data.Bind.EngExt, Fmx.Bind.DBEngExt,
System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.Components,
Data.Bind.DBScope, Datasnap.DBClient, Datasnap.DSConnect, FMX.Layouts,
Fmx.Bind.Navigator, UProxy, FMX.ListBox;
type
TForm1 = class(TForm)
Image1: TImage;
DataSnapCONNECTION: TSQLConnection;
Banners: TClientDataSet;
DSProviderConnection1: TDSProviderConnection;
BannersBanner: TBlobField;
BindSourceDB1: TBindSourceDB;
BindingsList1: TBindingsList;
LinkPropertyToField1: TLinkPropertyToField;
BindNavigator1: TBindNavigator;
btnDataset: TButton;
btnMethodJSON: TButton;
Image2: TImage;
Label1: TLabel;
BannersIDBanner: TIntegerField;
LinkPropertyToField2: TLinkPropertyToField;
cbxSelector: TComboBox;
Interbase: TListBoxItem;
btnMethodStream: TButton;
Panel1: TPanel;
labBaseDeDatos: TLabel;
procedure btnDatasetClick(Sender: TObject);
procedure btnMethodJSONClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure cbxSelectorChange(Sender: TObject);
procedure btnMethodStreamClick(Sender: TObject);
private
{ Private declarations }
FInstanceOwner : Boolean;
FServerMethods1Client: TServerMethods1Client;
function GetServerMethods1Client: TServerMethods1Client;
public
{ Public declarations }
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 21/33
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses Data.DBXJSONCommon;
procedure TForm1.btnDatasetClick(Sender: TObject);
begin
Banners.Open;
end;
procedure TForm1.btnMethodJSONClick(Sender: TObject);
var
fStream: TMemoryStream;
i: Integer;
begin
if Banners.IsEmpty then
raise Exception.Create('Debes abrir primero el dataset');
try
i:= BannersIDBanner.AsInteger;
fStream:= TMemoryStream.Create();
case cbxSelector.ItemIndex of
0: begin
fStream.LoadFromStream(TDBXJSONTools.JSONToStream(GetServerMethods1Client.GetImagenBannerAsJSON(i)));
//fStream.SaveToFile('..\..\..\test_interbase_jason_fmx.jpg');
end;
end;
Image2.Bitmap.LoadFromStream(fStream);
finally
fStream.Free;
end;
end;
procedure TForm1.btnMethodStreamClick(Sender: TObject);
var
fStream: TMemoryStream;
i: Integer;
begin
if Banners.IsEmpty then
raise Exception.Create('Debes abrir primero el dataset');
try
i:= BannersIDBanner.AsInteger;
fStream:= TMemoryStream.Create();
case cbxSelector.ItemIndex of
0: begin
fStream.LoadFromStream(GetServerMethods1Client.GetImagenBannerAsStream(i));
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 22/33
//fStream.SaveToFile('..\..\..\test_interbase_stream_fmx.jpg');
end;
end;
Image2.Bitmap.LoadFromStream(fStream);
finally
fStream.Free;
end;
end;
procedure TForm1.cbxSelectorChange(Sender: TObject);
begin
Banners.Close;
case cbxSelector.ItemIndex of
0: Banners.ProviderName:= 'dspImagenes_Interbase';
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FInstanceOwner := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FServerMethods1Client.Free;
end;
function TForm1.GetServerMethods1Client: TServerMethods1Client;
begin
if FServerMethods1Client = nil then
FServerMethods1Client:= TServerMethods1Client.Create(DataSnapCONNECTION.DBXConnection, FInstanceOwner);
Result := FServerMethods1Client;
end;
end.
Y aqu tenis algunas capturas de la aplicacin en
ejecucin, con una animacin mostrando el
resultado.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 23/33
En el video se puede ver la aplicacin
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 24/33
ejecutandose
Conclusin y comentarios adicionales
A tenor de lo visto, ambos mtodos funcionan
correctamente y conseguimos cargar sin problemas
tanto desde un valor de retorno con un tipo TStream,
como considerar un valor de tipo TJSONArray.
En imgenes pequeas ambos trabajan
correctamente.
Ahora bien, mientras montaba el ejemplo para
compartirlo con este compaero en un correo
privado, me encontr que generaba un error si la
imagen era de un tamao mediano o grande desde
GetServerMethods1Client.GetImagenBannerAsStream(i)
donde emerge el error que mostraba la animacin:
Out of memory while expanding memory stream.
TJSON array funcionaba correctamente en cualquier
caso.
As que os podis imaginar que parte de tiempo de
demora de todas estas lineas ha estado en
asegurarme que realmente era as e intentar
descartar que no fuera un problema generado por mi
propio cdigo.
Yo creo que nosotros, como MVPs, o como personas
comprometidas con la Comunidad, no podemos
ocultar que exista un error puesto que no sera moral
ni actuaria en conciencia. Mas al contrario, requiere
de nosotros la responsabilidad de que perdamos un
poco de nuestro tiempo intentando averiguar que lo
produce, si es que realmente al final es producido por
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 25/33
alguna linea de la librera y no de nuestro desarrollo.
Creo que es un buen signo de salud de la Comunidad
y por ello, he aadido unas lineas para intentar
capturar la naturaleza del problema y con
mi psimo ingls, intentar subirlo a QC para que se
pueda solucionar. Quizs ya est documentado.
Quizs sea una regresin fruto de cambios
posteriores. Ni idea. Es algo que ver en
los prximos das.
Podis descargar el cdigo fuente demo_parte2 y
si deseis seguir la lectura, voy a incluir algo de
cdigo para analizarlo.
Podemos aislar el problema?
Creo que podemos olvidarnos por un momento de
que el contenido de un TStream sea grfico o que
tenga otra naturaleza. Por eso, se me ocurre invocar
una funcin que devuelva un tamao en relacin de
un parmetro de entrada, entero, que lo fije.
La funcin GetStreamBySize( ) devolver un TStream
de un tamao de ASizeBytes, de tipo Integer. Su
implementacin es sencilla. Fijamos el tamao de una
matriz de Bytes en funcin ASizeBytes y
sobrescribimos con un numero cualquiera cada byte.
Yo he usado un 1. Y finalmente devolvemos la
instancia de la clase TBytesStream que es creada en
base al tamao de la matriz de bytes.
Este es el cdigo del servidor:
unit ServerMethodsUnit1;
interface
uses System.SysUtils, System.Classes, Datasnap.DSServer, Datasnap.DSAuth;
type
TServerMethods1 = class(TDSServerModule)
private
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 26/33
{ Private declarations }
public
{ Public declarations }
function EchoString(Value: string): string;
function ReverseString(Value: string): string;
function GetStreamBySize(ASizeBytes: Integer): TStream; //By Bytes
end;
implementation
{$R *.dfm}
uses System.StrUtils;
function TServerMethods1.EchoString(Value: string): string;
begin
Result := Value;
end;
function TServerMethods1.GetStreamBySize(ASizeBytes: Integer): TStream;
var
fStream: TBytesStream;
fBytes: TBytes;
i: Integer;
begin
SetLength(fBytes, ASizeBytes);
for i := 0 to Length(fBytes) - 1 do fBytes[i] := 1;
fStream:= TBytesStream.Create(fBytes);
Result:= fStream;
end;
function TServerMethods1.ReverseString(Value: string): string;
begin
Result := System.StrUtils.ReverseString(Value);
end;
end.
El formulario cliente consta de un par de botones y
una casilla de texto que nos permite probar con
distintos tamaos. Tambin generamos un log del
resultado, devuelto en las lneas del componente
TMemo.
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 27/33
Este es el cdigo del cliente.
unit UMainClient_fmx;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
Data.DBXDataSnap, IPPeerClient, Data.DBXCommon, FMX.Objects, Data.DB,
Data.SqlExpr, UProxy, FMX.Layouts, FMX.Memo, FMX.Edit;
type
TMainClienteFMX = class(TForm)
btnImg1: TButton;
btnImg2: TButton;
DataSnapCONNECTION: TSQLConnection;
Image1: TImage;
btnTest: TButton;
Memo1: TMemo;
btnCancel: TButton;
numbox: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnTestClick(Sender: TObject);
procedure btnCancelClick(Sender: TObject);
private
{ Private declarations }
FFin: Boolean;
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 28/33
FInstanceOwner : Boolean;
FServerMethods1Client: TServerMethods1Client;
function GetServerMethods1Client: TServerMethods1Client;
public
{ Public declarations }
function Fin: Boolean;
end;
var
MainClienteFMX: TMainClienteFMX;
implementation
{$R *.fmx}
procedure TMainClienteFMX.btnCancelClick(Sender: TObject);
begin
FFin:= True;
end;
procedure TMainClienteFMX.btnTestClick(Sender: TObject);
var
fBytesStream: TBytesStream;
i,j: Integer;
sLog: String;
begin
FFin:= False;
memo1.Lines.Clear;
fBytesStream:= TBytesStream.Create;
try
i:= StrToInt(numbox.Text);
while not Fin do
begin
fBytesStream.LoadFromStream(GetServerMethods1Client.GetStreamBySize(i));
sLog:= 'Size: '+IntToStr(fBytesStream.Size)+ ' -> ';
for j := 0 to Length(fBytesStream.Bytes)-1 do
sLog:= sLog + IntToStr(fBytesStream.Bytes[j]);
memo1.Lines.Add(sLog);
Inc(i);
numbox.Text:= IntToStr(i);
Application.ProcessMessages;
end;
finally
memo1.Lines.SaveToFile('Test.txt');
fBytesStream.Free;
end;
end;
function TMainClienteFMX.Fin: Boolean;
begin
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 29/33
Result:= FFin;
end;
procedure TMainClienteFMX.FormCreate(Sender: TObject);
begin
FInstanceOwner := True;
end;
procedure TMainClienteFMX.FormDestroy(Sender: TObject);
begin
FServerMethods1Client.Free;
FServerMethods1Client:= Nil;
end;
function TMainClienteFMX.GetServerMethods1Client: TServerMethods1Client;
begin
if FServerMethods1Client = nil then
FServerMethods1Client:= TServerMethods1Client.Create(DataSnapCONNECTION.DBXConnection, FInstanceOwner);
Result := FServerMethods1Client;
end;
end.
Y finalmente, podis ver una imagen de la aplicacin
en ejecucin, as como una animacin
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 30/33
Podis ver el test ejecutndose
Descargar cdigo fuente Test Stream
Conclusiones del Test
Creo entender que la funcin rompe o genera el error
cuando el tamao a devolver es superior o igual a
30717 bytes, aunque sinceramente no he podido
averiguar la razn. Hasta ese tamao funciona
correctamente.
Haciendo algunos puntos de parada intuyo que
la gestin de memoria no es capaz de alojar el buffer
que contiene el stream pero no estoy seguro por lo
que intentar en los prximos das comentarlo en QC
y buscar si existe o se documentacin algn error
similar.
Sin embargo, tambin quiero decir y dejar constancia
que el ejemplo que nos muestra Jim Tierney aplicado
sobre XE3 funciona correctamente, y precisamente
testea tambin la devolucin de los mtodos remotos
que retorna un valor de tipo TStream, aunque no
exactamente igual. En el cdigo que l comparte,
retorna una instancia TFileStream que contiene el log
de resultados del test. Su ejemplo es muy bueno y
complejo y me ha llevado algo de tiempo entenderlo,
y quizs por eso no he acabado de ver qu le
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 31/33
Comprtelo:
TwittearTwittear 1 0
diferencia de este que ahora yo he
expuesto. Quizs sea simplemente un error mio y me
alegrara que fuera as.
Espero que haya sido didctica esta segunda parte y
que os pueda servir de algo todos estos comentarios.
Hasta esa tercera parte que en teora cerrara la serie.
Un saludo a todos.
TwittearTwittear 1
Did you like this article? Share it with your friends!
2 Responses to "Dar una buena imagen!
Parte 2"
GustavoSV dice:
marzo 26, 2013 a las 7:46 pm
Salvador muy didctica tu explicacin, tienes
vena de maestro !
Es un ejemplo muy prctico e interesante, hay algo que no
0
Like 4 Send
Facebook 4 Twitter 1 Google +1
LinkedIn 1 Correo electrnico
Imprimir Ms
datasnap Delphi qc XE3
Like 4
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 32/33
entiendo y es el por qu la redefinicin del mtodo
GetImagenBannerAsStream tanto en la seccin private como
public ?
Son esas cosas de la OOP que no he podido masticar bien
Responder
Salvador dice:
marzo 26, 2013 a las 9:07 pm
Hola Gustavo,
me alegra que te haya gustado. +1
Respecto al metodo, queda en la parte privada porque no
quera que el servidor lo expusiera a la clase cliente. El
compilador es capaz de distinguir por los parmetros que
recibe cuando debe invocar a uno u otro. Pero ciertamente, si
hubieras cambiado la firma del mtodo privado no hubiera
cambiado el resultado.As queda mas misterioso jajaja
Inicialmente, exista en el ejemplo 2 bases de datos (sqlserver e
internase) y con el selector podas cambiar de una base a otra
ejecutando el mtodo. Luego, a medida que avanzaba en la
escritura de la entrada, fui cambiando el cdigo para
simplificarlo y que no quedara muy lioso, limpindolo, pero
algunos detalles quedaron y ya no los rectifique. Un ejemplo
de eso es el combobox, que no tiene demasiado sentido pero
realmente tampoco afecta, ni para mal ni para bien.
Un saludo,
Salvador
Responder
Deja un comentario
Tu direccin de correo electrnico no ser publicada. Los campos
necesarios estn marcados *
-
16/05/13 Dar una buena imagen! Parte 2 | Delphi Bsico
delphibasico.com/delphi/?p=3918 33/33
2013 Delphi Bsico Powered by Pinboard Theme by One Designs and
WordPress
cinco + 1 =
Puedes usar las siguientes etiquetas y atributos HTML:
Publicar comentario
Spam Protection by WP-SpamFree
Recibir un email con los siguientes comentarios a esta
entrada.
Recibir un email con cada nuevo post.
Nombre *
Correo
electrnico
*
Web
Comentario