Bagikan melalui


TN002: Format Data Objek Persisten

Catatan ini menjelaskan rutinitas MFC yang mendukung objek C++ persisten dan format data objek saat disimpan dalam file. Ini hanya berlaku untuk kelas dengan makro DECLARE_SERIAL dan IMPLEMENT_SERIAL .

Masalah

Implementasi MFC untuk data persisten menyimpan data untuk banyak objek dalam satu bagian file yang bersebelahan. Metode objek Serialize menerjemahkan data objek ke dalam format biner yang ringkas.

Implementasi menjamin bahwa semua data disimpan dalam format yang sama dengan menggunakan Kelas CArchive. Ini menggunakan CArchive objek sebagai penerjemah. Objek ini bertahan sejak dibuat hingga Anda memanggil CArchive::Close. Metode ini dapat dipanggil baik secara eksplisit oleh programmer atau secara implisit oleh destruktor ketika program keluar dari cakupan yang berisi CArchive.

Catatan ini menjelaskan implementasi CArchive anggota CArchive::ReadObject dan CArchive::WriteObject. Anda akan menemukan kode untuk fungsi-fungsi ini di Arcobj.cpp, dan implementasi utama untuk CArchive di Arccore.cpp. Kode pengguna tidak memanggil ReadObject dan WriteObject secara langsung. Sebagai gantinya, objek-objek ini digunakan oleh operator penyisipan dan ekstraksi yang aman untuk tipe khusus kelas, yang dihasilkan secara otomatis oleh makro DECLARE_SERIAL dan IMPLEMENT_SERIAL. Kode berikut menunjukkan bagaimana WriteObject dan ReadObject secara implisit disebut:

class CMyObject : public CObject
{
    DECLARE_SERIAL(CMyObject)
};

IMPLEMENT_SERIAL(CMyObj, CObject, 1)

// example usage (ar is a CArchive&)
CMyObject* pObj;
CArchive& ar;
ar <<pObj;        // calls ar.WriteObject(pObj)
ar>> pObj;        // calls ar.ReadObject(RUNTIME_CLASS(CObj))

Menyimpan Objek ke Penyimpanan (CArchive::WriteObject)

Metode CArchive::WriteObject menulis data header yang digunakan untuk membangun ulang objek. Data ini terdiri dari dua bagian: jenis objek dan status objek. Metode ini juga bertanggung jawab untuk mempertahankan identitas objek yang ditulis, sehingga hanya satu salinan yang disimpan, terlepas dari jumlah pointer ke objek tersebut (termasuk penunjuk melingkar).

Menyimpan (menyisipkan) dan memulihkan (mengekstrak) objek bergantung pada beberapa "konstanta manifes." Ini adalah nilai yang disimpan dalam biner dan memberikan informasi penting ke arsip (perhatikan awalan "w" menunjukkan jumlah 16-bit):

Etiket Deskripsi
wNullTag Digunakan untuk penunjuk objek NULL (0).
wNewClassTag Menunjukkan deskripsi kelas baru yang mengikuti untuk konteks arsip ini (-1).
wOldClassTag Menunjukkan kelas objek yang dibaca telah terlihat dalam konteks ini (0x8000).

Saat menyimpan objek, arsip mempertahankan CMapPtrToPtr ( m_pStoreMap) yang merupakan pemetaan dari objek tersimpan ke pengidentifikasi persisten (PID) 32-bit. PID ditetapkan ke setiap objek unik dan setiap nama kelas unik yang disimpan dalam konteks arsip. PID ini dibagikan secara berurutan mulai dari 1. PID ini tidak memiliki signifikansi di luar lingkup arsip dan, khususnya, tidak akan bingung dengan nomor rekaman atau item identitas lainnya.

CArchive Di kelas , PID adalah 32-bit, tetapi ditulis sebagai 16-bit kecuali mereka lebih besar dari 0x7FFE. PID besar ditulis sebagai 0x7FFF diikuti oleh PID 32-bit. Ini mempertahankan kompatibilitas dengan proyek yang dibuat di versi sebelumnya.

Ketika permintaan dibuat untuk menyimpan objek ke arsip (biasanya dengan menggunakan operator penyisipan global), pemeriksaan dilakukan untuk pointer NULL CObject . Jika pointer adalah NULL, wNullTag dimasukkan ke dalam aliran arsip.

Jika penunjuk bukan NULL dan dapat diserialisasikan (kelas adalah DECLARE_SERIAL kelas), kode memeriksa m_pStoreMap untuk melihat apakah objek telah disimpan. Jika sudah, kode menyisipkan PID 32-bit yang terkait dengan objek tersebut ke dalam aliran arsip.

Jika objek belum pernah disimpan sebelumnya, ada dua kemungkinan untuk dipertimbangkan: baik objek maupun jenis yang tepat (yaitu, kelas) objek tersebut baru untuk konteks arsip ini, atau objek memiliki jenis yang tepat yang sudah terlihat. Untuk menentukan apakah tipe telah terlihat, kode memeriksa m_pStoreMap untuk objek CRuntimeClass yang sesuai dengan objek yang terkait dengan objek yang disimpan. Jika ada kecocokan, WriteObject menyisipkan tag yang merupakan hasil operasi bit-wise dari OR dan indeks ini. CRuntimeClass Jika CRuntimeClass baru di dalam konteks arsip ini, maka menetapkan PID baru ke kelas tersebut dan menyisipkannya ke dalam arsip, yang didahului oleh nilai wNewClassTag.

Deskriptor untuk kelas ini kemudian dimasukkan ke dalam arsip menggunakan CRuntimeClass::Store metode . CRuntimeClass::Store menyisipkan nomor skema kelas (lihat di bawah) dan nama teks ASCII kelas. Perhatikan bahwa penggunaan nama teks ASCII tidak menjamin keunikan arsip di seluruh aplikasi. Oleh karena itu, Anda harus menandai file data Anda untuk mencegah kerusakan. Setelah penyisipan informasi kelas, arsip menempatkan objek ke dalam m_pStoreMap lalu memanggil Serialize metode untuk menyisipkan data khusus kelas. Menempatkan objek ke dalam m_pStoreMap sebelum memanggil Serialize mencegah beberapa salinan objek disimpan ke penyimpanan.

Saat kembali ke pemanggil awal (biasanya akar jaringan objek), Anda harus memanggil CArchive::Close. Jika Anda berencana untuk melakukan operasi CFile lainnya, Anda harus memanggil CArchive metode Flush untuk mencegah kerusakan arsip.

Nota

Implementasi ini memberlakukan batas keras indeks 0x3FFFFFFE per konteks arsip. Angka ini menunjukkan jumlah maksimum objek dan kelas unik yang dapat disimpan dalam satu arsip, tetapi satu file disk dapat memiliki sejumlah konteks arsip yang tidak terbatas.

Memuat Objek dari Penyimpanan (CArchive::ReadObject)

Memuat (mengekstrak) objek menggunakan CArchive::ReadObject metode dan merupakan kebalikan dari WriteObject. Seperti halnya WriteObject, ReadObject tidak dipanggil langsung oleh kode pengguna; kode pengguna harus memanggil operator ekstraksi jenis aman yang memanggil ReadObject dengan CRuntimeClass yang diharapkan. Ini memastikan integritas jenis operasi ekstrak.

WriteObject Karena implementasi yang menetapkan PID bertambah, memulai dari 1 (0 telah ditentukan sebagai objek NULL), ReadObject implementasi dapat menggunakan array untuk menjaga status konteks arsip. Ketika PID dibaca dari penyimpanan, jika PID lebih besar daripada batas atas m_pLoadArray yang ada saat ini, ReadObject mengetahui bahwa objek baru (atau deskripsi kelas) akan mengikuti.

Nomor Skema

Nomor skema, yang ditetapkan ke kelas ketika metode IMPLEMENT_SERIAL dari kelas tersebut ditemui, adalah "versi" dari implementasi kelas. Skema mengacu pada implementasi kelas, bukan berapa kali objek tertentu telah dibuat persisten (biasanya disebut sebagai versi objek).

Jika Anda ingin mempertahankan beberapa implementasi yang berbeda dari kelas yang sama dari waktu ke waktu, meningkatkan skema saat Anda merevisi implementasi metode objek Serialize Anda akan memungkinkan Anda menulis kode yang dapat memuat objek yang disimpan dengan menggunakan versi implementasi yang lebih lama.

Metode ini CArchive::ReadObject akan melempar CArchiveException ketika menemukan nomor skema di penyimpanan persisten yang berbeda dari jumlah skema deskripsi kelas dalam memori. Tidak mudah untuk pulih dari pengecualian ini.

Anda dapat menggunakan VERSIONABLE_SCHEMA yang dikombinasikan dengan (bitwise OR) versi skema Anda untuk mencegah pengecualian ini dilemparkan. Dengan menggunakan VERSIONABLE_SCHEMA, kode Anda dapat mengambil tindakan yang sesuai dalam fungsinya Serialize dengan memeriksa nilai pengembalian dari CArchive::GetObjectSchema.

Memanggil Serialisasi Secara Langsung

Dalam banyak kasus, overhead dari skema arsip objek umum WriteObject dan ReadObject tidak diperlukan. Ini adalah kasus umum untuk menserialisasikan data ke dalam CDocument. Dalam hal ini, metode Serialize dari CDocument dipanggil secara langsung, bukan dengan operator ekstrak atau sisipkan. Konten dokumen pada gilirannya dapat menggunakan skema arsip objek yang lebih umum.

Memanggil Serialize secara langsung memiliki kelebihan dan kekurangan berikut:

  • Tidak ada byte tambahan yang ditambahkan ke arsip sebelum atau sesudah objek diserialisasikan. Ini tidak hanya membuat data yang disimpan lebih kecil, tetapi memungkinkan Anda menerapkan Serialize rutinitas yang dapat menangani format file apa pun.

  • MFC telah dikonfigurasikan agar implementasi WriteObject dan ReadObject serta koleksi terkait tidak akan ditautkan ke dalam aplikasi Anda, kecuali Anda memerlukan skema arsip objek yang lebih umum untuk tujuan lain.

  • Kode Anda tidak perlu memulihkan nomor skema lama. Ini membuat kode serialisasi dokumen Anda bertanggung jawab untuk mengodekan nomor skema, nomor versi format file, atau nomor identifikasi apa pun yang Anda gunakan di awal file data Anda.

  • Objek apa pun yang diserialisasi dengan panggilan langsung ke Serialize tidak boleh menggunakan CArchive::GetObjectSchema atau harus menangani nilai pengembalian (UINT)-1 yang menunjukkan bahwa versi tidak diketahui.

Karena Serialize dipanggil langsung pada dokumen Anda, biasanya tidak mungkin bagi sub-objek dokumen untuk mengarsipkan referensi ke dokumen induk mereka. Objek-objek ini harus diberikan penunjuk ke dokumen kontainer mereka secara eksplisit, atau Anda harus menggunakan fungsi CArchive::MapObject untuk memetakan penunjuk ke PID sebelum penunjuk balik ini diarsipkan.

Seperti disebutkan sebelumnya, Anda harus mengodekan informasi versi dan kelas sendiri ketika Anda memanggil Serialize secara langsung, memungkinkan Anda untuk mengubah format nanti sambil tetap mempertahankan kompatibilitas mundur dengan file yang lebih lama. Fungsi CArchive::SerializeClass ini dapat dipanggil secara eksplisit sebelum secara langsung membuat serial objek atau sebelum memanggil kelas dasar.

Lihat juga

Catatan Teknis berdasarkan Angka
Catatan Teknis menurut kategori