2010-09-29 30 views
18

He definido un registro que tiene muchos campos con diferentes tipos (entero, real, cadena, ... más arreglos dinámicos en términos de "matriz de ..."). Quiero guardarlo como un todo en un archivo y luego poder cargarlo nuevamente en mi programa. No quiero pasar guardando el valor de cada campo individualmente. El tipo de archivo (binario o ascii o ...) no es importante, siempre que Delphi pueda volver a leerlo en un registro.Delphi 2010: ¿Cómo guardar un registro completo en un archivo?

¿Tiene alguna sugerencia?

+1

http://delphi.about.com/od/fileio/a/fileof_delphi.htm También asegúrese de usar un 'registro lleno' porque la alineación de la memoria de los registros regulares está sujeta a cambios entre las versiones –

+0

Una nueva [OpenSource unidad y clases] (http://blog.synopse.info/post/2011/03/12/TDynArray-and-Record-compare/load/save-using- fast -RTTI) que vale la pena considerar para serializar registros o matrices dinámicas (con muchas más funciones que la serialización), trabajando para Delphi 5 hasta XE2. Usar cadenas cortas no es una opción en mi humilde opinión desde Delphi 2009, ya que esas cadenas son cadenas Ansi, y perderás mucho espacio de archivos. –

+0

¿Por qué no quieres guardar cada campo individualmente? no es tan difícil de todos modos. Agregue un método a su procedimiento de registro MyRec.WriteToStream (DiskStream: TStream). En ese método, guarde cada campo de registro en la secuencia. En lugar de utilizar una biblioteca de terceros, su código no es autónomo. Y gastó solo unas pocas líneas adicionales de código (aproximadamente una para cada campo). No es tan malo! Algunas bibliotecas recomendadas aquí ya se han ido (superobjeto). – Ampere

Respuesta

18

Puede cargar y guardar la memoria de un registro directamente desde y hacia una secuencia, siempre que no utilice matrices dinámicas. Así que si usa cadenas, es necesario hacerlos fijos:

type TTestRecord = record 
    FMyString : string[20]; 
end; 

var 
    rTestRecord: TTestRecord; 
    strm : TMemoryStream; 

strm.Write(rTestRecord, Sizeof(TTestRecord)); 

Incluso puede cargar o guardar una tabla de registros a la vez!

type TRecordArray = array of TTestRecord; 

var ra : TRecordArray; 

strm.Write(ra[0], SizeOf(TTestRecord) * Length(ra)); 

En caso de que quiera escribir contenido dinámico:

iCount := Length(aArray); 
strm.Write(iCount, Sizeof(iCount));  //first write our length 
strm.Write(aArray[0], SizeOf * iCount); //then write content 

Después de eso, se puede leer de nuevo:

strm.Read(iCount, Sizeof(iCount));  //first read the length 
SetLength(aArray, iCount);    //then alloc mem 
strm.Read(aArray[0], SizeOf * iCount); //then read content 
+1

+1 por mencionar que no se usan matrices/cadenas dinámicas –

+0

Buena solución, pero lamentablemente también tengo matrices dinámicas. ¿Que puedo hacer? ¿No hay forma de que pueda guardar el estado "actual" del registro en la transmisión? – Mahm00d

+3

Puede escribir manualmente el estado de la secuencia, ver mi versión editada ... –

0

Códigos de delphibasics:

type 
    TCustomer = Record 
    name : string[20]; 
    age : Integer; 
    male : Boolean; 
    end; 

var 
    myFile : File of TCustomer; // A file of customer records 
    customer : TCustomer;   // A customer record variable 

begin 
    // Try to open the Test.cus binary file for writing to 
    AssignFile(myFile, 'Test.cus'); 
    ReWrite(myFile); 

    // Write a couple of customer records to the file 
    customer.name := 'Fred Bloggs'; 
    customer.age := 21; 
    customer.male := true; 
    Write(myFile, customer); 

    customer.name := 'Jane Turner'; 
    customer.age := 45; 
    customer.male := false; 
    Write(myFile, customer); 

    // Close the file 
    CloseFile(myFile); 

    // Reopen the file in read only mode 
    FileMode := fmOpenRead; 
    Reset(myFile); 

    // Display the file contents 
    while not Eof(myFile) do 
    begin 
    Read(myFile, customer); 
    if customer.male 
    then ShowMessage('Man with name '+customer.name+ 
         ' is '+IntToStr(customer.age)) 
    else ShowMessage('Lady with name '+customer.name+ 
         ' is '+IntToStr(customer.age)); 
    end; 

    // Close the file for the last time 
    CloseFile(myFile); 
end; 
+0

Como mencioné anteriormente: Tengo matrices dinámicas en mi registro. Eso no funciona con "archivo de". ¿Alguna otra solución? – Mahm00d

2

También podría definir un o bject en lugar de un registro, por lo que puede usar RTTI para guardar su objeto en XML o lo que sea. Si tiene D2010 o XE, puede utilizar DEHL a serializarlo: Delphi 2010 DeHL Serialization XML and custom attribute : how it work?

Pero si "Google" se pueden encontrar otras librerías con RTTI y la serialización (con D2007, etc)

0

El problema con el ahorro de una registro que contiene una matriz dinámica o cadenas reales (u otros tipos "administrados" para el caso) es que no es una gran cantidad de memoria que contiene todo, es más como un árbol. Alguien o algo necesita revisar todo y guardarlo en almacenamiento, de alguna manera. Otros lenguajes (Python, por ejemplo) incluyen todo tipo de recursos para transformar la mayoría de los objetos en texto (serializarlo), guardarlo en el disco y volver a cargarlo (deserializarlo).

Aunque no existe una solución provista por Embarcadero para Delphi, se puede implementar utilizando el RTTI extendido disponible en Delphi 2010. Existe una implementación lista para usar en la biblioteca DeHL (here's a blog post about it) - pero no puedo decir mucho sobre la implementación, nunca usé DeHL.

Otra opción es la que desea evitar: serializar manualmente el registro en una TStream; En realidad no es medio difícil. Aquí está el tipo de código que suelen utilizar para leer/escribir objetos a una secuencia de archivo:

procedure SaveToFile(FileName:string); 
var F:TFileStream; 
    W:TWriter; 
    i:Integer; 
begin 
    F := TFileStream.Create(FileName, fmCreate); 
    try 
    W := TWriter.Create(F, 128); 
    try 
     // For every field that needs saving: 
     W.WriteString(SomeStr); 
     W.WriteInteger(TheNumber); 
     // Dynamic arrays? Save the length first, then save 
     // every item. The length is needed when reading. 
     W.WriteInteger(Length(DArray));    
     for i:=0 to High(DArray) do 
     W.WriteString(DArray[i]); 
    finally W.Free; 
    end; 
    finally F.Free; 
    end; 
end; 

procedure ReadFromFile(FileName:string); 
var F:TFileStream; 
    R:TReader; 
    i,n:Integer; 
begin 
    F := TFileStream.Create(FileName, fmOpenRead); 
    try 
    R := TReader.Create(F, 128); 
    try 
     SomeStr := R.ReadString; 
     TheNumber := R.ReadInteger; 
     // Reading the dynamic-array. We first get the length: 
     n := R.ReadInteger; 
     SetLength(DArray, n); 
     // And item-by-item 
     for i:=0 to n-1 do 
     DArray[i] := R.ReadString; 
    finally R.Free; 
    end;  
    finally F.Free; 
    end; 
end; 
+1

"... la solución no existe para Delphi ..." pero puedes hacerte uno, y no solo para D2010, sino para cualquier versión Delphi (win-native) (no probé .NET). Lee mis comentarios de este 'tema' – kibab

+1

@kibab, parece que estás tratando de vender algo, pero olvidaste proporcionar el enlace a tu 1k-wander; Y cuando me cites, ¿te importaría citar toda la frase, no solo las 5 palabras que componen tu propósito? Toda la frase es: "Incluso aquellos que no cuentan con una solución provista por Embarcadero para Delphi, se puede implementar usando el RTTI extendido disponible en Delphi 2010". ¿Y notaron que publiqué un enlace a DeHL, verdad? ¿Y realmente sabes que no todo se vuelve RTTI, ni siquiera con Delphi 2010? Ejemplo: No hay RTTI para los elementos en un tipo de enumeración. –

2

Si tiene cuerdas dinámicas o matriz que no se puede escribir el registro "en su conjunto".En lugar de utilizar viejos estilo-25 caracteres cadenas max, me gustaría añadir métodos para el registro para ser capaz de "corriente" en sí a un arroyo, o mejor usando un descendiente TFiler:

TMyRec = record 
    A: string; 
    B: Integer; 
    procedure Read(AReader: TReader); 
    procedure Writer(AWriter: TWriter); 
end; 

procedure TMyrec.Read(AReader: TReader); 
begin 
    A := AReader.ReadString; 
    B := AReader.ReadInteger; 
end; 
+0

No mientas;) PUEDES escribir TODO el registro dinámico, pero necesitas escribir/encontrar una función que haga esto para CUALQUIER registro/dyn.array/string (no solo uno específico como el anterior). Delphi RTL no viene con uno, no sé por qué ... Construí uno analizando cómo funciona System.pas: _Finalize. Con aproximadamente 1k de líneas de código, he creado funciones que funcionan para CUALQUIER matriz dinámica/cadena/registro como esta: SizeOfDynamic (const ADynamicType; ATypeInfo: PTypeInfo), WriteDynamicToStream, ReadDynamicFromStream, CompareDynamic. El uso es simplemente WriteDynamicToStream/ReadDynamicFromStream (lFileStream, lMyRec, TypeInfo (TMyRec)). – kibab

+1

Puede escribir datos dinámicos completos, pero no puede escribir un registro cuyos campos sean dinámicos simplemente escribiendo la imagen de la memoria de registro. De todos modos, debe leerlos por separado y almacenarlos por separado. Qué camino tomar depens en tus necesidades. Puede usar una solución ad hoc, o una genérica, que podría tomar más tiempo para desarrollarse. Se documenta cómo se almacenan RTTI, matrices dinámicas y cadenas, no habría necesitado tanta lectura de system.pas. ¿Solo líneas de código "1k"? Hay aplicaciones enteras escritas con menos líneas. De todos modos, muestre su respuesta, en lugar de solo criticar a los demás ... –

+0

+1, porque incluso aquellos que solicitaron específicamente un método que no "guarda el valor de cada campo individualmente", este método vale la pena considerar: usted obtiene espacio y la eficiencia del tiempo sobre cualquier método basado en RTTI, y se obtiene una gran flexibilidad para hacer frente a las variaciones futuras en la estructura del registro. –

3

Además de las respuestas que indican cómo se hace esto, por favor, también tenga en cuenta de estos:

  1. debe ser consciente de que la escritura de registros en un archivo será Delphi versión específica (por lo general: específico a una serie de versiones de Delphi que comparten el mismo diseño de memoria para los tipos de datos subyacentes).

  2. Solo puede hacerlo si su registro no contiene campos de un tipo gestionado. Lo que significa que los campos no pueden ser de estos tipos administrados: strings, matrices dinámicas, variants, y los tipos de referencia (como pointers, procedural types, method references, interfaces o classes) y tipos de archivo o tipos que contienen los tipos gestiona. Lo que básicamente límites a que estos tipos no administrados:

    • A: Simple types (incluyendo bytes, enteros, flotadores, enumeraciones, caracteres y tal)
    • B: Short strings
    • C: Establece
    • D: Estático matrices de A, B, C, D y e
    • e: Registros de A, B, C, D y e

En lugar de escribir registros en/desde un archivo, podría ser mejor ir con las instancias de clase y convertirlas a/desde JSON, y escribir la cadena JSON equivalente a un archivo y leerla nuevamente.

Puede usar esta unidad para hacer la conversión JSON por usted (debería funcionar con Delphi 2010 y posteriores; funciona con seguridad con Delphi XE y superior) desde this location.

unit BaseObject; 

interface 

uses DBXJSON, DBXJSONReflect; 

type 
    TBaseObject = class 
    public 
    { public declarations } 
    class function ObjectToJSON<T : class>(myObject: T): TJSONValue; 
    class function JSONToObject<T : class>(json: TJSONValue): T; 
    end; 

implementation 

{ TBaseObject } 

class function TBaseObject.JSONToObject<T>(json: TJSONValue): T; 
var 
    unm: TJSONUnMarshal; 
begin 
    if json is TJSONNull then 
    exit(nil); 
    unm := TJSONUnMarshal.Create; 
    try 
    exit(T(unm.Unmarshal(json))) 
    finally 
    unm.Free; 
    end; 

end; 

class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue; 
var 
    m: TJSONMarshal; 
begin 

    if Assigned(myObject) then 
    begin 
    m := TJSONMarshal.Create(TJSONConverter.Create); 
    try 
     exit(m.Marshal(myObject)); 
    finally 
     m.Free; 
    end; 
    end 
    else 
    exit(TJSONNull.Create); 

end; 

end. 

Espero que esto te ayude a obtener una visión general de las cosas.

--jeroen

4

Otra opción que funciona muy bien para los registros (Delphi 2010+) es el uso de la biblioteca SuperObject. Por ejemplo:

type 
    TData = record 
    str: string; 
    int: Integer; 
    bool: Boolean; 
    flt: Double; 
    end; 
var 
    ctx: TSuperRttiContext; 
    data: TData; 
    obj: ISuperObject; 
    sValue : string; 
begin 
    ctx := TSuperRttiContext.Create; 
    try 
    sValue := '{str: "foo", int: 123, bool: true, flt: 1.23}'; 
    data := ctx.AsType<TData>(SO(sValue)); 
    obj := ctx.AsJson<TData>(data); 
    sValue := Obj.AsJson; 
    finally 
    ctx.Free; 
    end; 
end; 

I también probado esto brevemente con una simple matriz dinámica TArray<Integer> y que no tenía un problema el almacenamiento y la carga de los elementos de la matriz.

+0

+1 por lo que parece una buena solución (aunque como novato, ¡es un poco difícil de entender!). Pero tengo 3 preguntas: 1) ¿Cómo se hace que el sValue de un gran registro existente lo pase a "SO()"? 2) ¿Cuál es el objeto aquí para ser almacenado en el archivo? El sValue o el obj? 3) ¿Cómo lo cargas al registro? Gracias de nuevo por su respuesta. – Mahm00d

+0

El sValue es una representación del registro como un paquete JSON. Puede guardar fácilmente esta única cadena en un archivo de texto. En el ejemplo anterior, las dos líneas "Obj: = ctx.AsJson (data)" y "sValue: = Obj.AsJson" son las que realizan la traducción mágica de registro a cadena. Almacene el sValue. La línea anterior "data: = ctx.AsType (entonces (sValue));" es lo que analiza esto y rellena los datos del registro con los valores correctos. – skamradt

11

Según lo prometido aquí está: http://kblib.googlecode.com

Cuando haya definido, por ejemplo, registro como:

TTestRecord = record 
    I: Integer; 
    D: Double; 
    U: UnicodeString; 
    W: WideString; 
    A: AnsiString; 
    Options: TKBDynamicOptions; 

    IA: array[0..2] of Integer; 

    AI: TIntegerDynArray; 
    AD: TDoubleDynArray; 
    AU: array of UnicodeString; 
    AW: TWideStringDynArray; 
    AA: array of AnsiString; 

    R: array of TTestRecord; // record contain dynamic array of itself (D2009+) 
end; 

Puede guardar el registro dinámico conjunto para transmitir (como datos binarios) por:

TKBDynamic.WriteTo(lStream, lTestRecord, TypeInfo(TTestRecord)); 

Para cargarlo:

TKBDynamic.ReadFrom(lStream, lTestRecord, TypeInfo(TTestRecord)); 

No tiene por qué ser un registro, puede hacerlo mismo para cualquier tipo dinámico como:

TKBDynamic.WriteTo(lStream, lStr, TypeInfo(UnicodeString)); 
TKBDynamic.WriteTo(lStream, lInts, TypeInfo(TIntegerDynArray)); 
TKBDynamic.WriteTo(lStream, lArrayOfTestRecord, TypeInfo(TArrayOfTestRecord)); // TArrayOfTestRecord = array of TTestRecord; 

probado en Delphi 2006/2009/XE. Licencia: MPL 1.1/GPL 2.0/LGPL 3.0 Consulte el archivo léame para obtener información.

+2

+1 Esto funciona muy bien.El código podría hacerse compatible con el próximo compilador de 64 bits, usando NativeUInt en lugar de cardinal para la aritmética del puntero. –

+1

@Arnaud Bouchez - se agregó compatibilidad con el compilador x64 (consulte el archivo léame para obtener más información sobre la compatibilidad de datos binarios entre x86 y x64) – kibab

+0

¡Agradable! Gracias también por el mantenimiento del proyecto. – TLama

3

Otra solución, que funciona desde Delphi 5 hasta XE, está disponible as an OpenSource unit.

De hecho, se implementa:

  • algunas funciones de RTTI de bajo nivel para el manejo de los tipos de registro: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
  • un objeto dedicado TDynArray, que es un envoltorio alrededor de cualquier matriz dinámica, capaz de exponer los métodos tipo TList alrededor de cualquier matriz dinámica, incluso con registros, cadenas u otras matrices dinámicas. Puede serializar cualquier matriz dinámica.

La serialización utiliza un formato binario optimizado, y es capaz de guardar y cargar cualquier matriz dinámica o de registro como RawByteString.

Usamos esto en nuestro ORM, para almacenar tipos de alto nivel como propiedades de matriz dinámica en un servidor de base de datos. Primer paso a un DB-Sharding architecture.

+0

+1, parece una buena solución. Definitivamente voy a probarlo. Muchas gracias por mencionarlo. – Mahm00d

+0

¿Es parte de SQLite o se puede agregar como una unidad independiente al proyecto? ¿Puede guardar/cargar en el archivo? – Mahm00d

+0

@Flom Lo usa nuestro ORM que usa SQLite, pero no forma parte de él. Se puede usar para guardar/cargar en un archivo, incluso después de la compresión si es necesario. La unidad SynCommons.pas es autónoma (de hecho, enlaza a Synopse.inc y SynLZ.pas) y no requiere SQLite ni ninguna otra parte de nuestras bibliotecas. Y tiene algunas otras funciones de bajo nivel en esta unidad, como la codificación UTF-8, la serialización JSON y el registro mejorado, con rastreo de pila y rastreo de excepciones. Disfruta el código abierto! –