PDA

Ver la Versión Completa : Optener un Stream como "Result" en una funcion


sitrico
24-11-2004, 17:39:24
Buenas:

Estoy mejorando una aplicación y se me ocurrio usar Streams en lugar de archivos temporales pero no he podido "pasar" el resultado de una funcion como un stream.

El caso es el siguiente:

Tengo una aplicacion que usa campos memo para almacenar archivos html (comprimidos) (En el registro 1 guardo los encabezados, y en los siguientes los diferentes contenidos, lo trabajé así para ahorrar espacio ya que como los html son generados desde winword los encabezados de los archivos eran identicos y muy, MUY grandes) entonces para poder usar el archivo html completo uso:


FiltrarTextos(db,Encabezados,Codigo,IdLibro);
// Toma el Registro 1 + el que me interesa
Html := TMemoryStream.Create;
InputStream := TMemoryStream.Create;
OutputStream := TMemoryStream.Create;
Temp := TMemoryStream.Create;
db.First;
While Not(db.Eof) do
Begin
InputStream.Clear;
OutputStream.Clear;
Temp.Clear;
TBlobField(db.FieldByName('Data')).SaveToStream(Temp);
InputStream.LoadFromStream(temp);
If InputStream.Size <> 0 Then
Begin
ZDecompressStream(InputStream,OutputStream); // estan comprimidos
Html.CopyFrom(OutputStream,0);
End;
db.Next;
End;
html.SaveToFile(Destino);
Temp.Free;
OutputStream.Free;
InputStream.Free;
html.Free;


Como pueden ver eso me deja con un archivo temporal (destino)
Que cargo en un componente WebBrowser: TEmbeddedWB que permite usar:

WebBrowser.LoadFromFile(Destino);

Pero revizandolo un poco más tambien permite cargarlo desde:

WebBrowser.LoadFromStream(Destino);

Lo que quisiera hacer es convertir el procedimiento anterior en una funcion donde al final poga algo como:


Function CargarStreamHTML:TMemoryStream;

Begin
// Mismo codigo de antes para leer y descomprimir
// pero en lugar de:
// html.SaveToFile(Destino);
// Usar
Result := html;
End;


He hecho algunas pruebas y he tenido problemas con los create's y Free's ya que al final no sé si debo liberar el objeto asignado al result o si tengo que crear un objeto para asignarle el resultado.

En fin lo que necesito es un ejemplo de una funcion en la que el resultado pueda ir directo a un método "LoadFromStream".

Gracias

Uso delphi 7 + el TEmbeddedWB (http://www.euromind.com/iedelphi/embeddedwb.htm)

Toda la aplicación corre localmente y se usa para consultar unos informes históricos

roman
24-11-2004, 18:35:55
Puedes regresar directamente como lo haces


Result := html;


pero no puedes liberarlo dentro de la misma función sino posteriormente. Por tanto, si usas simplemente:



LoadFromStream(CargarStream);


tendrás una fuga de memoria ya que no tienes ninguna referencia al Stream devuelto para poder liberarlo, así que tendrías que hacer algo como:



var
Stream: TStream;

begin
Stream := CargarStream;
LoadFromStream(Stream);
Stream.Free;
end;


de manera que no ahorras nada.

Si de plano te da flojera escribir tanto :D podrías englobar el stream dentro de una interfaz:


type
ILazyStream = interface
function GetStream: TMemoryStream;
property Stream: TMemoryStream read GetStream;
end;

TLazyStream = class(TInterfacedObject, ILazyStream)
private
FStream: TMemoryStream;

public
constructor Create;
destructor Destroy; override;

{ ILazyStream }
function GetStream: TMemoryStream;
end;

...

constructor TLazyStream.Create(AStream: TMemoryStream);
begin
inherited;
FStream := TMemoryStream.Create;
end;

destructor TLazyStream.Destroy;
begin
FStream.Free;
inherited;
end;

function TLazyStream.GetStream: TMemoryStream;
begin
Result := FStream;
end;


En tu función CargarStream creas una instancia de TLazyStream y esa es la que devuelves usando LazyStream.Stream:


function CargarStream: ILazyStream;
begin
Result := TLazyStream.Create;

...
end;

...

LoadFromStream(CargarStream.Stream);


Así, cuando se pierda el contexto de LoadFromStream (es decir termine el procedimiento desde donde lo llamas) CargarStream, siendo una interfaz, automáticamente llamará al destructor de TLazyStream.

// Saludos

sitrico
24-11-2004, 19:14:15
Bueno Gracias (aunque la parte del interface escapa de mis posibilidades actuales :( ) creo que has dado en el clavo.

Por suerte, el trabajo de crear los html lo realizo dentro de un objeto:


Type
TGenHTML = class(TObject)
Private
Public
Procedure FiltrarTextos(db:TDataSet;Encabezados:Integer;Codigo: String;IdLibro:Integer);
Function GetURL(db:TDataSet;Encabezados:Integer;Codigo,Destino:String;IdLibro:Integer): String;
Function GetText(db:TDataSet;Codigo:String): String;
Procedure ResaltarBusqueda(f,ElTextoBuscado: String);
Procedure ComprimirHtml(db:TDataSet;Origen:String);
End;


Ahora se me ocurre que puedo cambiar el objeto padre de TObject a TMemoryStream (que cuenta con los Métodos LoadFromStream y LoadFromFile (y los save...) entonces lo unico que tendría que hacer es modificar las rutinas para que en lugar de terminar creando un archivo temporal cargen el stream en el propio objeto derivado de TMemoryStream y no tendría que usar una función para obtener el resultado sino que mi objeto TGenHTML seria un "TStreamGenHTML"

Voy a probar a ver que sale. Gracias nuevamente.

sitrico
24-11-2004, 22:06:38
Bueno, quedo muy bien, al convertir el objeto a un tMemoryStream lo único que tuve que hacer fue asignar el resultado a la variable "Self" y listo

Puedo usar:


Var
html : THTMLdbStream;
Begin
html := THTMLdbStream.Create;
Try
html.LoadFromDB(Table1,0,'A0100102',1);
EmbeddedWB1.LoadFromStream(html);
Finally
html.free;
End;
end;


Pero y siempre hay un pero. El componente tEmbeddedWB recibe el archivo html y lo muestra como texto (el código html) aparece:

<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns........

En lugar del archivo formateado.

Pienso que el problema puede estar en el largo de la línea (algunas de mucho más de 256 carácteres) o bien en el primer caracter del stream (distinto a "<") como puedo "Ver" y editar el contenido de un MemoryStream?

Ya he probado casí todo y por lo visto el problema es del tEmbeddedWB voy a buscar otras opciones y si acaso luego pondré otro post.