Manejar el Object Store en Windows Pocket 2003 con eVC++:
El «Object Store» (ObS) en Windows Pocket 2003 cumple en muchos casos la
misma función que el disco duro en un equipo de escritorio. Proporciona
almacenamiento permanente para las aplicaciones y sus datos incluso cuando la
fuente de alimentación se desconecta, siempre y cuando exista una fuente de
reserva de alimentación. Físicamente el ObS lo componen varios chips de
almacenamiento de memoria no volátil.
Conceptualmente, el ObS consiste de tres tipos de almacenamiento persistente
– sistema de ficheros, bases de datos y registro del sistema – que comparten un
mismo heap de memoria.
Una gran ventaja del uso del ObS para el almacenamiento de datos es que el
mecanismo para almacenar datos esta basado en transacciones (utilizando por
ejemplo algoritmos como el Commit en dos fases). Si se
interrumpe la alimentación mientras se están escribiendo datos al ObS, el
sistema operativo se asegura de que el almacenamiento no se corrompa y la base
de datos quede en un estado consistente, bien sea completando la operación
cuando el sistema se reinicia (asegurar el commit) o
volviendo la base de datos a la última configuración estable conocida antes de
la interrupción (rollback). Para el caso del sistema de ficheros y
del registro, esto puede significar la recarga de los valores iniciales de la
ROM en caso de no haber hecho un backup del sistema.
A continuación nos centramos en el estudio de las bases de datos del
Object Store.
Base de datos del Object Store
El modelo de de base de datos que usa el ObS es un sistema de
almacenamiento pequeño con estructura plana y optimizado para un almacenamiento
eficiente. Esta base de datos consiste en un número de registros simples,
cada uno de los cuales contiene un número de propiedades de distinto tipo. Un registro puede tener un número variable de propiedades, pero no puede
contener a otro registro. Por lo tanto el Object Store tendría la
siguiente estructura:
Cada base de datos, cada registro de la misma y cada propiedad de cada registro tiene asociado un
identificador del tipo CEOID. Cada registro no es más que un conjunto de
propiedades (CEPROPVAL) que se identifican unívocamente por un CEOID.
Puesto que Windows Pocket PC esta diseñado para operar en entornos
relativamente volátiles, las actualizaciones de la base de datos no solo se
realiza cuando se abre o se cierra, sino que se actualiza después de cada
escritura con la función
CeWriteRecordProps que explicaremos más adelante.
Estructura de los registros
Como hemos dicho antes, un registro no es más que un conjunto de propiedades
(CEPROPVAL):
|
typedef struct _CEPROPVAL { CEPROPID propid; WORD wLenData; WORD wFlags; CEVALUNION val; } CEPROPVAL; typedef CEPROPVAL *PCEPROPVAL;
|
typedef union _CEVALUNION { short iVal; USHORT uiVal; long lVal; ULONG ulVal; FILETIME filetime; LPWSTR lpwstr; CEBLOB blob; BOOL boolVal double dblVal } CEVALUNION; |
Esta estructura contiene los siguientes campos:
- CEPROPID propid : Identificador del valor de la propiedad. La parte
alta de la palabra consiste en un tipo definido por la aplicación,
definido por el programador, mientras que la parte baja de la palabra es una
constante predefinida que indica el tipo del miembro CEVALUNION val.
CEPROPID puede tomar los siguientes valores:
(Los registros pueden tener propiedades de los siguientes tipos)
Value Description CEVT_BLOB A BLOB structure. CEVT_BOOL A Boolean value. CEVT_FILETIME A FILETIME
structure.CEVT_I2 A 16-bit signed integer. CEVT_I4 A 32-bit signed integer. CEVT_LPWSTR A null-terminated string. CEVT_R8 A 64-bit signed integer. CEVT_UI2 A 16-bit unsigned integer. CEVT_UI4 A 32-bit unsigned integer. A partir de estas propiedades podemos construir tipos personalizados usando
la macro PROP_TAG
- WORD wLenData: Actualmente no usado
- WORD wFlags: Flags especiales de la propiedad, Puede ser uno
de los siguientes valores:
Value | Description |
---|---|
CEDB_PROPNOTFOUND | Valor guardado por la función CeReadRecordProps en caso de que la propiedad no se encuentre. |
CEDB_PROPDELETE | Si se pasa como parámetro a la función CeWriteRecordProps hace que dicha popiedad sea borrada. |
-
CEVALUNION val: En este campo es donde se almacena la información de la
propiedad.Este campo consiste en una enumeración de C++, es decir un único
valor que puede tomar distintos tipos:- iVal El valor es un integer.
- uiVal El valor es un integer sin signo.
- lVal El valor es un long.
-
ulVal El valor es un long sin signo
- filetime Dato con la estructura FILETIME.
- lpwstr Puntero a una cadena de caracteres terminada en
- blob Estructura del tipo CEBLOB.
- boolVal Valor booleano: un entero sin signo de 16-bits.
- dblVal Valor double de 64-bits con signo.
De estos posibles valores deberemos usar el adecuado según el tipo e dato
que deseemos almacenar.
Diseño de nuestra base de datos
Lo complicado para utilizar el sistema de base de datos del Object Store, es
trasladar el diseño conceptual de nuestra base de datos al modelo que usa por el
Object Store.
Si por ejemplo tenemos una base de datos de Clientes donde por cada cliente
almacena los siguientes datos:
|
class Cliente : public CObject{ public: CString dni; CString nombre; CString apellidos; CString mail; CString telefono; Cliente(); }; |
Cada uno de los campos de información del cliente serán
almacenados como cadenas de caracteres, por lo que dentro de el CEPROPVAL, el
valor CEOID será CEVT_LPWSTR y la información se almacenará en CEPROPVAL.val.lpwstr.
Para poder distinguir entre las distintas propiedades del
registro, al ser todas del tipo CEVT_LPWSTR, creamos tipos de dato
personalizados con la macro PROP_TAG:
#define HHPR_DNI PROP_TAG( CEVT_LPWSTR, 0x8200 ) #define HHPR_NOMBRE PROP_TAG( CEVT_LPWSTR, 0x8201 ) #define HHPR_APELLIDOS PROP_TAG( CEVT_LPWSTR, 0x8202 ) #define HHPR_MAIL PROP_TAG( CEVT_LPWSTR, 0x8203 ) #define HHPR_TELEFONO PROP_TAG( CEVT_LPWSTR, 0x8203 )
De esta forma si tenemos un cliente para poder insertarlo en
la base de datos del ObS tendremos que copiar sus campos de la siguiente forma:
CEPROPVAL *rgProps = NULL; //registro = conjunto de propiedades
rgProps = new CEPROPVAL[ 4 ];
memset( rgProps, 0, 4 * sizeof( CEPROPVAL ) );rgProps[0].propid = HHPR_DNI;
rgProps[0].val.lpwstr = LPWSTR(LPCWSTR (cliente->dni));rgProps[1].propid = HHPR_NOMBRE;
rgProps[1].val.lpwstr = LPWSTR(LPCWSTR (cliente->nombre));rgProps[2].propid = HHPR_APELLIDOS;
rgProps[2].val.lpwstr = LPWSTR(LPCWSTR (cliente->apellidos));rgProps[3].propid = HHPR_MAIL;
rgProps[3].val.lpwstr = LPWSTR(LPCWSTR (cliente->mail));rgProps[4].propid = HHPR_TELEFONO;
rgProps[4].val.lpwstr = LPWSTR(LPCWSTR (cliente->telefono));
/*Para una mayor comodidad los números anteriores pueden ser sustituidos por
#defines*/
Finalmente en la variable rgProps tenemos nuestro registro dispuesto para ser
insertado en nuestra base de datos.
Análogamente para transformar un registro leído de la base de datos en un
Cliente realizamos la operación contraria:
lpwstr = rgProps[0].val.lpwstr ;
cliente->dni = LPCWSTR(lpwstr);lpwstr = rgProps[1].val.lpwstr ;
cliente->nombre = LPCWSTR(lpwstr);lpwstr = rgProps[2].val.lpwstr ;
cliente->apellidos = LPCWSTR(lpwstr);lpwstr = rgProps[3].val.lpwstr ;
cliente->mail = LPCWSTR(lpwstr);lpwstr = rgProps[4].val.lpwstr ;
cliente->telefono = LPCWSTR(lpwstr);(Otro método sería buscar entre cada una de las propiedades comparando rgProps[i].propid hasta encontrar la que buscamos)
(Nótese la gran utilidad que nos proporciona el que el campo val de CEPROPVAL sea una union, nos permite que pueda tomar valores de distinto tipo.
En caso de tener algun valor entero como int i = rgProps[5].val.iVal
siendo rgProps[5].propid = CEVT_I4;)
Manejo de la base de datos
Con la base de datos debemos de ser capaz de realizar las siguientes tareas:
crear, abrir, leer, escribir, borrar y cerrar.
En nuestro caso para abrir nuestra base de datos debemos primero encontrar el
identificador de la base de datos en caso de no saberlo por lo que se añade la
operación «buscar» la base de datos dentro de todo el ObS.
Crear una base de datos:
Para crear una nueva base de datos usamos la función CeCreateDatabaseEx. Para crear nuestra base de datos debemos indicarle
el nombre de la base de datos MY_DB_FILE y el tipo DBTYPE_CLIENTES que será valores personalizados.
CEOID m_oidDatabase; CEGUID v_guid; // GUID para el volumen de la base de datos //Estos son valores personales que identifican #define MY_DB_FILE TEXT( «\\CLIENTES_DB.DB» //Obtememos el CREATE_SYSTEMGUID( &v_guid CEDBASEINFO info; memset( &info, 0, sizeof( info ) ); info.dwFlags = CEDB_VALIDNAME | CEDB_VALIDTYPE; lstrcpy( info.szDbaseName, SZ_DB_FILE ); info.dwDbaseType = DBTYPE_CLIENTES; m_oidDatabase = CeCreateDatabaseEx( &v_guid, &info ); |
Abrir base de datos:
Para abrir una base de datos debemos saber su
identificador, o bien buscar la base de datos en todo el object store:
Buscar identificador de una base de datos:
//Obtememos el hFind = CeFindFirstDatabaseEx( &v_guid, DBTYPE_CLIENTES ); if ( hFind != INVALID_HANDLE_VALUE ) { for ( ;; ) { m_oidDatabase = CeFindNextDatabase( hFind ); if ( !m_oidDatabase || CeOidGetInfoEx( &v_guid, m_oidDatabase, &oidInfo ) && oidInfo.wObjType == OBJTYPE_DATABASE && !wcscmp( oidInfo.infDatabase.szDbaseName, MY_DB_FILE ) ) break; } CloseHandle( hFind ); } |
Una vez encontrado nuestra base de datos tenemos su identificador en m_oidDatabase y v_guid como el volumen donde se encuentra montado.
Con el identificador m_oidDatabase abrimos la base de datos con la función CeOpenDatabaseEx:
m_hDatabase = CeOpenDatabaseEx( &v_guid, &m_oidDatabase, 0, 0, CEDB_AUTOINCREMENT, NULL ); if ( !m_hDatabase ) { ShowMsg( TEXT( "Error abriendo base de datos." ), GetLastError() ); return FALSE; } return TRUE; |
Una vez abierta la base de datos podemos operar con sus registros usando el
manejador m_hDatabase .
Escribir Registros
Para escribir en la base de datos usaremos la función: CeWriteRecordProps.
oidWrote = CeWriteRecordProps( m_hDatabase, oid, cProps, rgProps );
Donde HANDLE m_hDatabase; es el manejador de la base de datos una vez
abierta.
Oid es el identificador del registro que deseamos escribir. En caso de
querer insertar un nuevo registro este valor tiene que ser 0;
cProps : Es el número de propiedades de que consta nuestro registro.
rgProps : Es el registro a insertar, con la información almacenada como se
indica mas arriba.
Esta función nos devuelve el oid con que se ha insertado el registro.
Leer registros
Antes de leer un registro de la base de datos y de la misma forma que
hiciéramos con un fichero deberemos indicarle la posición a partir de la cual
leer mediante la función CeSeekDatabase:
En caso de querer leer el primer registro de la base de datos :
CeSeekDatabase( m_hDatabase, CEDB_SEEK_BEGINNING, 0, &dwIndex );
En caso de saber el oid de un registro en concreto y querer los datos:
CeSeekDatabase( m_hDatabase, CEDB_SEEK_CEOID, oid, &dwIndex );
En la documentación de eVC++ se encuentran todas las posibles flags para
determinar la posición.
Una vez posicionados en la base de datos leemos el registro que nos interese
con la función: CeReadRecordProps:
oid = CeReadRecordProps( m_hDatabase, CEDB_ALLOWREALLOC, &cProps, NULL, (LPBYTE *)&rgProps, &cbProps );
//oid indica el identificador del registro leido. En caso de error oid será
0.
//Una vez realizada esta llamada tenemos en rgProps un array de «cbProps»
propiedades (PROPVALS)
/*….. Convertimos rgProps en Cliente…….*/
LocalFree( rgProps ); // liberamos la memoria reservada por la funcion al
haber usado CEDB_ALLOWREALLOC
Cerrar base de datos:
Una vez usada la base de datos la cerramos con la función CloseHandle:
ret = CloseHandle( m_hDatabase );
Para más información:
Ayuda del eVC++.
http://msdn.microsoft.com/library/en-us/wcemain4/html/_wcefiles_Using_a_Windows_CE_Database.asp