This section provides a roadmap for those who want to learn how to use the OpenDoc storage using the recipes. It is assumed that you have a basic knowledge of OpenDoc and its storage system (including container, document, draft, and storage unit).
Storage recipes contains the following documents that discuss the basic storage concepts and usage:
The following recipe is specific only to the bento container suite:
Every OpenDoc draft contains a network of storage units. These storage units are connected to each other through persistent references. See the "Persistent References" recipe for more information. When an OpenDoc draft is opened, ODPersistentObjects and ODStorageUnit objects are instantiated using the storage units.
Copying an object is not as straight forward as the usual stream operations. For example, when one tries to copy a value, one needs to copy all the storage units that are being referred to by the value also. Otherwise, the data copied is not complete.
To help developers accomplish this task more easily, OpenDoc provides a cloning mechanism to enable deep-copy through a simple function. This function has the following characteristics:
Clients need to use the following ODDraft function calls:
ODDraftKey BeginClone(in ODDraft destDraft, in ODFrame destFrame, in ODCloneKind kind); void EndClone(in ODDraftKey key); void AbortClone(in ODDraftKey key); ODID Clone(in ODDraftKey key, in ODID fromObjectID, in ODID toObjectID, in ODID scope); ODID WeakClone(in ODDraftKey key, in ODID objectID, in ODID toObjectID, in ODID scope); |
BeginClone, EndClone and AbortClone are for setting up and terminating the cloning transaction. Clone and WeakClone are used to notify the draft that the specified object needs to be cloned.
ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy); ODID newA = draft->Clone(ev, key, A->GetID(ev), 0, 0); draft->EndClone(ev, key); |
The result is that A, B, C, D are all copied.
ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy); ODID newB = draft->WeakClone(ev, key, B->GetID(ev), 0, 0); ODID newD = draft->Clone(ev, key, D->GetID(ev), 0, 0); draft->EndClone(ev, key); |
The result is that C and D are copied. B is not copied because it is not strongly referenced by C or any objects transitively strongly referenced by C. The newB returned by WeakClone is an invalid ID. Therefore, when the client tries to get that object using newB, an error results. It is the responsibility of the client to handle this error condition.
Also, the IDs can only be used to make storage unit references during cloning. The client should not try to get the persistent object or storage unit.
The following diagram shows what the copied network of storage units or persistent objects looks like: Figure 86
Figure 86. Copied Network of Storage Units/Persistent Objects
In this case, the code fragment in step 2 copies C, D and B. Then, the returned newB is a valid ID referring to the new copy of B. The following diagram shows what the resulting network looks like:
All persistent objects (including parts) have to override CloneInto in ODPersistentObject:
void CloneInto(in ODDraftKey key, in ODStorageUnit toSU, in ODFrame scope); |
During the clone operation (that is, between BeginClone and EndClone), a persistent object's CloneInto method is called by the draft to do the actual data copying. The recipe is very similar to that for the clients except that the persistent object has to write out its intrinsic content in addition to cloning its referenced storage units/persistent objects.
The following is an example of what a part editor might do in response to CloneInto:
void MyPartCloneInto(MyPart* somSelf, Environment* ev, ODDraftKey key, ODStorageUnit toSU, OID scopeFrameID) { // Since this method may be called multiple times during one cloning // transaction, the part should check to see whether it really needs // to write out any data. if (toSU->Exists(ev, kODPropContents, kMyContentKind, 0) == kODFalse) { // Write out intrinsic content toSU->AddProperty(ev, kODPropContents); toSU->AddValue(ev, kMyContentKind); toSU->SetValue(ev, myContentByteArray); // Write out referenced object ODStorageUnit* mySU= somSelf->GetStorageUnit(ev); ODDraft* myDraft = mySU->GetDraft(ev); ODID newReferencedObjectID = myDraft->Clone(ev, key, myReferencedObject, 0, scopeFrameID); // Write out referenced object id ODStorageUnitRef ref; toSU->GetStrongStorageUnitRef(ev, newReferencedObjectID, ref); toSU->SetOffset(ev, correctOffsetInMyContent); // Set up the byte array ODByteArray refByteArray; refByteArray._length = sizeof(ODStorageUnitRef); refByteArray._maximum = sizeof(ODStorageUnitRef); refByteArray._buffer = &ref; toSU->SetValue(ev, &refByteArray); } } |
If a part has two frames and the user only chooses to copy one frame, the part should be notified about it. Otherwise, the part may copy too much data or in certain cases the wrong data. Let's consider the following situation. Part A contains part B (which has two display frames). In turn, part B contains part C and part D.
If the user selects some intrinsic content with B1 (as shown in the thin box) and selects Copy, part A should do the following:
// Begin cloning transaction ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy); // Write out intrinsic content to storageUnit ... // Get reference to frame B1 using frame B1 as the scope ODID newB1 = draft->Clone(ev, key, B1->GetID(ev), 0, B1->GetID(ev)); // Write out the reference to storageUnit also. ... // Finish cloning draft->EndClone(ev, key); |
When part B's CloneInto method is called, the scope parameter is the ID of frame B1. In this way, part B can choose to clone only the necessary data.
However, if the user selects both B1 and B2 (as shown below), Part A should do the following:
// Begin cloning transaction ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy); // Write out intrinsic content to storageUnit ... // Get reference to frame B1 using frame B1 as the scope ODID newB1 = draft->Clone(ev, key, B1->GetID(ev), 0, B1->GetID(ev)); // Get reference to frame B2 using frame B2 as the scope ODID newB2 = draft->Clone(ev, key, B2->GetID(ev), 0, B2->GetID(ev)); // Write out the reference to storageUnit also ... // Finish cloning draft->EndClone(ev, key); |
As a result, part B's CloneInto method is called twice (once with frame B1 as the scope and another time with frame B2 as the scope). Part B should be ready to handle this and write out the part content accordingly.
For more information on Data Interchange, see the "Data Interchange" recipes (especially the "Clipboard Recipes" recipe).
When the ODCloneKind argument to BeginClone is kODClonePaste or kODCloneDropMove, parts must supply as the ODFrame argument the frame that is performing the paste or receiving the drop. For other values of ODCloneKind, the frame may be kODNULL, as in the examples in this document. This parameter allows OpenDoc to determine if the destination is a valid location for content being moved; an invalid destination frame is one that would cause a display frame of a part to be embedded within another display frame of the same part. In this unusual situation, the subsequent call to EndClone raises an exception. When EndClone returns an error, parts must call AbortClone (which does not return errors).
As discussed above, the cloning mechanism is biased toward runtime objects. Therefore, if there is anything in the storage unit that is not stored by the runtime persistent object itself, it is not going to be cloned. An example of this may be a utility like a spell checker. It may store a list of words in the part's storage unit.
To make sure that this information is not lost, the container suite copies any properties with the following prefix to the destination storage unit:
const ODPropertyName kODPropPreAnnotation= "+//ISO 9070/ANSI::113722::US::CI LABS::OpenDoc:Annotation:"; |
The persistent object being cloned does not even need to know that these annotations exist.
This section discusses the following:
During an OpenDoc session, many objects are created. Because there is a very complex relationship among objects, it is difficult to determine when it is safe to delete an object.
Reference counting is a way to determine when these runtime objects can be deleted so that valuable memory space can be reclaimed.
ODRefCntObject is an object with a reference count. A reference count must be 0 or a positive integer.
Here is the class definition of ODRefCntObject:
interface ODRefCntObject:ODObject { void InitRefCntObject(); void Acquire(); void Release(); ODULong GetRefCount(); }; |
These are the subclasses of ODRefCntObject class:
ODRefCntObject | Factory Object |
ODContainer | ODStorageSystem |
ODDocument | ODContainer |
ODDraft | ODDocument |
ODExtension | ODObject (or subclass) |
ODFrame | ODDraft |
ODLink | ODDraft |
ODLinkSource | ODDraft |
ODPart | ODDraft |
ODShape | ODFrame |
ODStorageUnit | ODDraft |
ODTransform | ODFrame |
ODWindow | ODWindowState |
For example, to get a ODFrame object, one has to call ODDraft's CreateFrame or ODDraft's AcquireFrame. The ODFrame object returned from ODDraft's CreateFrame has a reference count of 1 while the ODFrame object returned from ODDraft's AcquireFrame has a reference count of at least 1.
Let's look at a very simple example:
// frame1's RefCount is 1 ODFrame* frame1 = draft->CreateFrame(...); // frame2 == frame1 and its RefCount is 2 ODFrame* frame2 = draft->AcquireFrame(...); // frame2's RefCount is 1. // Since frame1 == frame2, frame1's RefCount is also 1. frame2->Release(); // The user should not use frame2 after this point! frame1->Acquire(...); // frame1's RefCount is 2 frame1->Release(...); // frame1's RefCount is 1 frame1->Release(...); // frame1's RefCount is 0 // The user should not use frame1 after this point! frame1->Acquire(...); or // ERROR! frame1->Release(...); // ERROR! |
For example, ODFrame's Release() can be implemented like this:
void ODFrameRelease(ODFrame* somSelf, Environment* ev) { parent_Release(somSelf, ev); // which calls ODRefCntObject's Release() if (somSelf->GetRefCount(ev) == 0) fDraft->ReleaseFrame(ev, somSelf); // Call the factory object } |
The factory object can choose to dispose of the object immediately or keep the object around until a purge is called upon itself.
In the OpenDoc implementation, ODDraft uses the latter strategy. Therefore, ODDraft does not delete the object as soon as ODDraft's ReleaseXXX is called. Instead, it puts this released object in a collection so that it can be retrieved and reused again (that is, when ODDraft's AcquireXXX is called for that object). But, if ODDraft's Purge() is called, all the released objects in the collection will be deleted.
The reason why the object is not deleted immediately is that the object may be reused in the near future. One good example is scrolling. Parts may choose to release objects that are scrolled out of view and re-get them when they come into view again. If we delete the objects as soon as ReleaseXXX is called, we will have to re-instantiate these objects again. Creating persistent objects is not a fast process.
For example, since ODFrame is keeping a reference to the containing frame and the part, it has to increment the refCounts on both of these objects.
void ODFrameInitFrame(ODFrame* somSelf, Environment* ev, ODStorageUnit* storageUnit, ODFrame* containingFrame, ODShape* frameShape, ODPart* part, ODTypeToken viewType, ODTypeToken presentation, ODULong frameGroup, ODBoolean isRoot, ODBoolean isOverlaid) { ... _fContainingFrame = containingFrame; if (_fContainingFrame != kODNULL) _fContainingFrame->Acquire(ev); _fPart = part; if (_fPart != kODNULL) _fPart->Acquire(ev); ... } |
In ODFrame's ReleaseAll(), the frame has to release the containing frame and the part. Otherwise, the containing frame and the part always have a RefCount greater than 0 and they can never be disposed of.
void ODFrameReleaseAll(ODFrame* somSelf, Environment* ev) { if (_fContainingFrame != kODNULL) _fContainingFrame->Release(ev); if (_fPart != kODNULL) _fPart->Release(ev); } |
The containing frame and the part may have RefCounts greater than 0 even after ODFrame's ReleaseAll() is called. This is because other objects may be keeping a reference to the containing frame or the part.
When the RefCount of a persistent object goes from 1 to 0, the part has to call its factory object (that is ODDraft). The code looks something like this:
void MyPartRelease(MyPart* somSelf, Environment* ev) { parent_Release(ev); // Which calls ODRefCntObject's Release(ev) if (somSelf->GetRefCount(ev) == 0) _fDraft->ReleasePart(ev, somSelf); } |
As mentioned above, ODDraft does not delete the released object immediately. Therefore, a part does not need to do any shutting down or deallocation when its RefCount goes down to 0. They are then done in ReleaseAll() and the destructor (that is, somUninit).
One should not consider a persistent object with a zero refcount a dead object because at any point the draft may increment its refcount and hand it to some other objects. Having a refcount of 0 simply means no one has a reference to the object.
However, if a part needs to function differently when there is no reference to it, it can use the RefCount to identify the situation and respond appropriately. For example, it may choose to get rid of any structure or service that is not needed anymore. For example, a communication part may choose to close its driver when there is no reference to it.
void CommPartRelease(CommPart* somSelf, Environment* ev) { parent_Release(ev); if (somSelf->GetRefCount(ev) == 0) { CloseDriver(); fDraft->ReleasePart(ev, somSelf); } } |
If the object does something other than calling its factory object when its RefCount goes down to 0, it needs to reinitialize itself when its RefCount is bumped from 0 to 1. This is being done in the object's Acquire() call. In general, the object should undo what it did in Release() when the RefCount went down to 0.
The following is an example of what the above-mentioned CommPart would do in response to its RefCount going from 0 to 1.
void CommPartAcquire(CommPart* somSelf, Environment* ev) { parent_Acquire(somSelf, ev); if (somSelf->GetRefCount(ev) == 1) { OpenDriver(); } } |
A persistent reference is used in a storage unit value to refer to another storage unit in the same document. This reference is persistent in the sense that it is preserved across sessions.
References are used in many places in OpenDoc:
This persistent reference in OpenDoc is typed ODStorageUnitRef. ODStorageUnitRef is a 32-bit value. One should never try to inspect or interpret a ODStorageUnitRef. ODStorageUnit and ODStorageUnitView provides a set of methods to manipulate ODStorageUnitRef.
void GetStrongStorageUnitRef (ODStorageUnit* embeddedSU, ODStorageUnitRef ref); void GetWeakStorageUnitRef (ODStorageUnit* embeddedSU, ODStorageUnitRef ref); ODBoolean IsStrongStorageUnitRef (ODStorageUnitRef ref); ODBoolean IsWeakStorageUnitRef (ODStorageUnitRef ref); ODStorageUnit* RemoveStorageUnitRef (ODStorageUnitRef ref); ODStorageUnitID GetIDFromStorageUnitRef (ODStorageUnitRef ref); ODBoolean IsValidStorageUnitRef (ODStorageUnitRef ref); |
ODStorageUnitRef can only be created from a ODStorageUnit which is focused to a value or a ODStorageUnitView (is to be focused to a value). The scope of the ODStorageUnitRef is then limited to the value from which it is created. It is illegal and dangerous to use ODStorageUnitRef in a different value. If used, it will almost certainly not refer to the correct storage unit rendering the resulting behavior unpredictable.
One can create virtually unlimited number of References in a storage unit value.
If a persistent reference is no longer needed, one should remove it from the value from which it is created. It is crucial to have unnecessary persistent references removed for efficiency, robustness and effective garbage collection.
ODStorageUnitRef is not recycled. Therefore, all ODStorageUnitRefs from a value are guaranteed to be unique.
The referring power of ODStorageUnitRef are preserved across sessions. One can write out a ODStorageUnitRef in the value, close and reopen the container and use the ODStorageUnitRef in that value to find the referred storage unit.
There are two kinds of ODStorageUnitRef: strong and weak. Their difference is important when a clone operation is performed using ODDraft::BeginClone and ODDraft::EndClone. In a clone operation, all the storage units referenced by a strong ODStorageUnitRef are copied. Consider the following example,
See Figure 85
Creating an auxiliary storage unit for a part:
void MyPart::InitPart(Environment* ev, ODStorageUnit* storageUnit) { ... // Create the Aux storage unit ODStorageUnit* auxStorageUnit = storageUnit->GetDraft(ev)->CreateStorageUnit(ev); // Add a property and value to store the persistent reference storageUnit->AddProperty(ev, kODPropContents); storageUnit->AddValue(ev, kODStrongStorageUnitRef); // storageUnit is focused to the newly created value already. // Create a persistent reference to the Aux storage unit in the // context of the create value. ODStorageUnitRef ref; storageUnit->GetStrongStorageUnitRef(ev, auxStorageUnit, ref); // The focus is unchanged and it is still pointing to the // created value. // Write out the persistent reference to the value. ODByteArray ba; ba._length = sizeof(ODStorageUnitRef); ba._maximum = sizeof(ODStorageUnitRef); ba._buffer = &ref; storageUnit->SetValue(ev, &ba); ... } |
Let's say that the part is instantiated later in a different session, the part can retrieve its auxiliary storage unit as follows:
void MyPart::InitPartFromStorage(Environment* ev, ODStorageUnit* storageUnit) { ... // Focus the storage unit to the right value storageUnit->Focus(ev, kODPropContents, kODPosUndefined, kODStrongStorageUnitRef, 0, kODPosUndefined); // Retrieve the persistent reference ODByteArray ba; storageUnit->GetValue(ev, sizeof(ODStorageUnitRef), &ba); // Use the retrieved persistent reference to get the ID for // the storage unit ODStorageUnitID ID = storageUnit->GetIDFromStorageUnitRef(ev, *((ODStorageUnitRef*) ba._buffer)); // Dispose of the buffer ODDisposePtr(ba._buffer); // Use the ID to get the Aux storage unit from the draft. ODStorageUnit* auxStorageUnit = storageUnit->GetDraft(ev)->GetStorageUnit(ev, id); ... } |
This section describes the basic usage of ODStorageUnit with actual examples and recipes. It is intended to supplement the class documentation. For detailed description of the ODStorageUnit methods, refer to the OpenDoc Programming Reference.
Recipe 1:
Given a ODStorageUnit, create multiple properties and values.
su->AddProperty(ev, kODPropContents); su->AddValue(ev, kMyKind); su->AddValue(ev, kKindTEXT); su->AddValue(ev, kKindPICT); su->AddProperty(ev, kPropAnnotations); su->AddValue(ev, kMyAnnotationsType); |
Recipe 2:
Given the ODStorageUnit in Recipe 1, set the focus to a certain value.
There is more than one way to get to the value:
su->Focus(ev, kODPropContents, // Property name kODPosUndefined, kMyKind, // Value type 0, kODPosUndefined); |
su->Focus(ev, kODPropContents, // Property name kODPosUndefined, kODNULL, 1, // Value index kODPosUndefined); |
su->Focus(ev, kODPropContents, // Property name kODPosUndefined, kODNULL, 0, kODPosFirstSib); // Position code |
There are two ways to create a cursor:
ODStorageUnitCursor* cursor = su->CreateCursor(ev, kODPropContents, kMyKind, 0); |
ODStorageUnitCursor* cursor = su->CreateCursorWithFocus(ev); |
One can then focus a storage unit using the created cursor:
su->FocusWithCursor(ev, cursor); |
Recipe 3:
Given the ODStorageUnit from Recipe 1, iterate through all the properties.
// Unfocus storage unit su->Focus(ev, kODNULL, kODPosAll, // Position code kODNULL, 0, kODPosAll); // Position code // Get the number of properties ODULong numProperties = su->CountProperties(ev); // Iterate through the properties for (i = 1; i <= numProperties; i++) { su->Focus(ev, kODNULL, kODPosNextSib, // Position code kODNULL, 0, kODPosUndefined); } |
Recipe 4:
Given the ODStorageUnit from Recipe 1, iterate through all the values in kODPropContents property.
// Focus to the desired property su->Focus(ev, kODPropContents, // Property name kODPosUndefined, kODNULL, 0, kODPosAll); // Position code // Get the number of values ODULong numValues = su->CountValues(ev); // Iterate through the values for (i = 1; i <= numValues; i++) { su->Focus(ev, kODNULL, kODPosSame, // Position code kODNULL, 0, kODPosNextSib); // Position code } |
Recipe 5:
Given the ODStorageUnit from Recipe 1, remove the kPropAnnotationsProperty.
// Focus to the desired property su->Focus(ev, kPropAnnotations, kODPosUndefined, kODNULL, 0, kODPosAll); // Remove the property su->Remove(ev); |
Recipe 6:
Given the ODStorageUnit from Recipe 1, remove the kKindPICT value in kODPropContents Property.
// Focus to the desired value su->Focus(ev, kODPropContents, kODPosUndefined, kODNULL, kKindPICT, kODPosUndefined); // Remove the value su->Remove(ev); |
Recipe 7:
Given the focused ODStorageUnit from Recipe 2, manipulate the data in the value. One can think of a value as a stream. For storage units to work in a distributed environment, all the data passed in or out through the storage unit function are in ODByteArray. The following is the structure of an ODByteArray (from the IDL emitter):
typedef struct { unsigned long _maximum; unsigned long _length; octet *_buffer; } _IDL_SEQUENCE_octet; typedef _IDL_SEQUENCE_octet ODByteArray; |
where:
To add data at a particular position:
su->SetOffset(ev, desiredPosition); // Set up byte array ODByteArray ba; ba._length = length; // Length of data to be written ba._maximum = maximum; // Maximum size of buffer ba._buffer = buffer; // Pointer to the data su->SetValue(ev, &ba); |
To add data at the current offset:
// No SetOffset is needed. // Just call SetValue. su->SetValue(ev, &ba); |
To insert data at a particular position:
su->SetOffset(ev, desiredPosition); su->InsertValue(ev, &ba); |
// Get the length of the value ODULong offset = su->GetSize(ev); // Set the offset to the end of the value su->SetOffset(ev, offset); su->SetValue(ev, &ba); |
To remove data from a particular position:
su->SetOffset(ev, offset); su->DeleteValue(ev, length); |
To get data from a particular position:
// Get to the right position su->SetOffset(ev, offset); // Set up byte array. // There is really nothing to do here because the fields of // ODByteArray are going to be filled out by GetValue. ODByteArray ba; // Get the data su->GetValue(ev, desiredLength, &ba); // Dispose the buffer created by GetValue when done ODDisposePtr(ba._buffer); |
A persistent object has two states:
The persistent state is stored in the associated storage unit while the runtime state is stored in the corresponding object instance.
Release versus Removal
When an object is released, only the runtime state is affected. For example, when the reference count of a persistent object goes down to 0 and is deleted, only the runtime state is destroyed while the persistent state remains unchanged.
However, when an object is removed, both the runtime and the persistent state are destroyed, which means the object instance and the storage unit cease to exist.
Removal without using draft function
Even though OpenDoc provides function to remove persistent objects created by the draft, a part editor does not need to remove any persistent objects using this function. This is because the underlying container suite is responsible for garbage collecting storage units that are no longer in use. A storage unit is not in use when it is no longer transitively strongly referenced by the draft properties storage unit.
Therefore, to remove a persistent object, all the part editor needs to do is to break or remove its reference to the storage unit of the persistent object.
// Focus to the value where the reference is to be removed. partSU->Focus(...); // Remove the storage unit ref partSU->RemoveStorageUnitRef(ev, ref); |
The garbage collection scheme is completely dependent on the container suite. Therefore, make sure that you write out references to the storage units of all of the desired persistent objects during externalize.
Removal using OpenDoc function
There are times when a part wants to remove a persistent object explicitly without relying on garbage collection. Here are a couple of examples:
Using the ODDraft function does not guarantee a successful removal. If the persistent object has a reference count of more than 1 when Remove is called, an exception will be thrown. It is the client's responsibility to anticipate and respond to the exception.
The following is a simple code fragment on the function usage:
SOM_TRY draft->RemovePart(ev, part); SOM_CATCH_ALL // Some error handling SOM_ENDTRY |
The OpenDoc storage system is designed to work with multiple container suites. In addition to the bento container suite, other vendors might want to provide alternate container suites.
To create a container, the client has to call the following ODStorageSystem method:
ODContainer CreateContainer(in ODContainerType containerType, in ODContainerID id); |
The first parameter of the method identifies the type of the container. To create a bento file container, the in parameter needs to be kODBentoFileContainer and the second parameter the FSSpec of the file where the container is to be created.
This binding is done at runtime. OpenDoc binding scans the editors folder(s) for container suite libraries. Each container suite library contains a nmap resource which lists the type(s) of containers that the container suite can create. OpenDoc binding then caches this information and uses it to determine which container suite library to use when ODStorageSystem::CreateContainer is called.
The following is the nmap resource of the bento container suite as defined in a private resource file:
#define kNMAPid1 128 #define kODBentoFileContainer "OSA:Bento:ContainerType:File" #define kODBentoMemoryContainer "OSA:Bento:ContainerType:Memory" #define kODFileContainerID "ODFileContainer" #define kODMemoryContainerID "ODMemContainer" // kODNameMappings, kODContainerSuite and // kODIsAnISOString are defined by OpenDoc. // They can be found in StdDef.r. resource kODNameMappings (kNMAPid1) { kODContainerSuite, { /* Array types: 2 elements */ /* [1] */ kODBentoFileContainer, kODIsAnISOString { kODFileContainerID }, /* [2] */ kODBentoMemoryContainer, kODIsAnISOString { kODMemoryContainerID } } }; |
As it is outlined in the resource, the bento container suite can create two kinds of containers: file and memory.
Other container suite providers need to include this nmap resource into their container suite libraries. Otherwise, their container suite is never bound and created.
The process of acquiring a container is almost the same as creating a container. The following ODStorageSystem method is used to acquire a previously created container:
ODContainer AcquireContainer(in ODContainerType containerType, in ODContainerID ID); |
The same process takes place to bind the container suite library for the container type supplied.
The default container types are interpreted as the bento container types. Therefore, supplying kODDefaultFileContainer to AcquireContainer or CreateContainer results in a bento file container being instantiated. Similarly, supplying kODDefaultMemoryContainer to the same methods results in a bento memory container being instantiated.
A container suite is an implementation of several storage classes (ODContainer, ODDocument, ODDraft, and ODStorageUnit) which provides the main functionality of of the OpenDoc storage system. The functionality includes the maintanence of documents, drafts, persistent objects and storage units.
The bento container suite (BCS) is a container suite based on bento (container manager) technology.
The bento container suite currently supports two kinds of containers: file container and in-memory containers File containers are persistent across sessions while in-memory containers are ephemeral.
In OpenDoc, every container is created through the ODStorageSystem.
ODByteArray ba; // Set the length to the size of the file spec which // contains meaningful data ba._length = sizeof(short) + sizeof(long) + fsSpec->name[0] + 1; ba._maximum = sizeof(FSSpec); // fsSpec is the FSSpec* of the file to be opened ba._buffer = fsSpec; ODContainer* newContainer = session->GetStorageSystem(ev)-> CreateContainer(ev,kODDefaultFileContainer,&ba); |
In OpenDoc, every container is created through the ODStorageSystem.
ODByteArray ba; ba._length = sizeof(ODHandle); ba._maximum = sizeof(ODHandle); // Handle is the Handle where the bento container is going to live ba._buffer = &handle; ODContainer* newContainer = session->GetStorageSystem(ev)-> CreateContainer(ev, kODDefaultMemoryContainer, ba); |
This is not recommended because there is no way to tell whether a container is a BCS file container or not. However, if you are sure that the container is a BCS file container and you really need to know the FSSpec, here is how you do it.
// Get the container ID ODContainerID containerID = container->GetID(ev); // The returned container ID contains the FSSpec in _buffer. // The returned ID may have _length smaller than sizeof(FSSpec). FSSpec* fsSpec = (FSSpec*) containerID->_buffer; // Use it ... // Dispose the _buffer in the byte array (that is, the container ID) ODDisposePtr(containerID->_buffer); |
Note:
This is not recommended.
The bento container suite supports garbage collection. It is triggered when a draft is saved. To not to lose your persistent objects, you should create references to all the persistent objects you are using during externalize.