From 58f7d669dc759e487c4bb17f4499fc3b11bf2467 Mon Sep 17 00:00:00 2001 From: brad Date: Fri, 29 Oct 2004 06:30:59 +0000 Subject: [PATCH] open image files on demand rather than keeping them open: bug #393 --- src/actions.cpp | 2 - src/image.cpp | 208 +++++++++++++++++++++-------------------------- src/image.hpp | 59 +++----------- src/metacopy.cpp | 1 - src/types.hpp | 24 ++++++ 5 files changed, 128 insertions(+), 166 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 07ce6668..a08fa048 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -566,7 +566,6 @@ namespace Action { return -2; } int rc = image->readMetadata(); - image->detach(); if (rc) { std::cerr << path_ << ": Could not read metadata\n"; @@ -975,7 +974,6 @@ namespace { return -2; } int rc = sourceImage->readMetadata(); - sourceImage->detach(); if (rc) { std::cerr << source << ": Could not read metadata\n"; diff --git a/src/image.cpp b/src/image.cpp index 888323db..82c7950f 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -72,7 +72,7 @@ namespace Exiv2 { Caller owns the returned object and the auto-pointer ensures that it will be deleted. */ - Image::AutoPtr newExvInstance(const std::string& path, FILE* fp); + Image::AutoPtr newExvInstance(const std::string& path, bool create); //! Check if the file ifp is an EXV file. bool isExvType(FILE* ifp, bool advance); /*! @@ -80,7 +80,7 @@ namespace Exiv2 { Caller owns the returned object and the auto-pointer ensures that it will be deleted. */ - Image::AutoPtr newJpegInstance(const std::string& path, FILE* fp); + Image::AutoPtr newJpegInstance(const std::string& path, bool create); //! Check if the file ifp is a JPEG image. bool isJpegType(FILE* ifp, bool advance); @@ -110,35 +110,34 @@ namespace Exiv2 { Image::Type ImageFactory::getType(const std::string& path) const { - FILE* ifp = fopen(path.c_str(), "rb"); - if (!ifp) return Image::none; + FileCloser closer(fopen(path.c_str(), "rb")); + if (!closer.fp_) return Image::none; Image::Type type = Image::none; Registry::const_iterator b = registry_.begin(); Registry::const_iterator e = registry_.end(); for (Registry::const_iterator i = b; i != e; ++i) { - if (i->second.isThisType(ifp, false)) { + if (i->second.isThisType(closer.fp_, false)) { type = i->first; break; } } - fclose(ifp); return type; } // ImageFactory::getType Image::AutoPtr ImageFactory::open(const std::string& path) const { Image::AutoPtr image; - FILE* ifp = fopen(path.c_str(), "rb"); - if (!ifp) return image; + FileCloser closer(fopen(path.c_str(), "rb")); + if (!closer.fp_) return image; Registry::const_iterator b = registry_.begin(); Registry::const_iterator e = registry_.end(); for (Registry::const_iterator i = b; i != e; ++i) { - if (i->second.isThisType(ifp, false)) { - image = Image::AutoPtr(i->second.newInstance(path, ifp)); + if (i->second.isThisType(closer.fp_, false)) { + image = Image::AutoPtr(i->second.newInstance(path, false)); break; } } @@ -150,10 +149,9 @@ namespace Exiv2 { { Registry::const_iterator i = registry_.find(type); if (i != registry_.end()) { - return i->second.newInstance(path, 0); + return i->second.newInstance(path, true); } - Image::AutoPtr p; - return p; + return Image::AutoPtr(); } // ImageFactory::create @@ -171,33 +169,22 @@ namespace Exiv2 { JpegBase::JpegBase(const std::string& path, bool create, const byte initData[], size_t dataSize) - : fp_(0), path_(path), - sizeExifData_(0), pExifData_(0), sizeIptcData_(0), pIptcData_(0) + : path_(path), sizeExifData_(0), pExifData_(0), + sizeIptcData_(0), pIptcData_(0) { if (create) { - fp_ = fopen(path.c_str(), "w+b"); - if (fp_) initFile(initData, dataSize); + FILE* fp = fopen(path.c_str(), "w+b"); + if (fp) { + initFile(fp, initData, dataSize); + fclose(fp); + } } - else { - fp_ = fopen(path.c_str(), "rb"); - } - } - - JpegBase::JpegBase(const std::string& path, FILE* fp) - : fp_(fp), path_(path), sizeExifData_(0), - pExifData_(0), sizeIptcData_(0), pIptcData_(0) - { - assert(fp_); } - int JpegBase::initFile(const byte initData[], size_t dataSize) + int JpegBase::initFile(FILE* fp, const byte initData[], size_t dataSize) { - if (!fp_ || ferror(fp_)) return 4; - if (fwrite(initData, 1, dataSize, fp_) != dataSize) { - return 4; - } - fseek(fp_, 0, SEEK_SET); - if (ferror(fp_)) { + if (!fp || ferror(fp)) return 4; + if (fwrite(initData, 1, dataSize, fp) != dataSize) { return 4; } return 0; @@ -205,23 +192,15 @@ namespace Exiv2 { JpegBase::~JpegBase() { - if (fp_) fclose(fp_); delete[] pExifData_; delete[] pIptcData_; } - int JpegBase::detach() - { - if (fp_) fclose(fp_); - fp_ = 0; - return 0; - } - bool JpegBase::good() const { - if (fp_ == 0) return false; - rewind(fp_); - return isThisType(fp_, false); + FileCloser closer(fopen(path_.c_str(), "rb")); + if (closer.fp_ == 0 ) return false; + return isThisType(closer.fp_, false); } void JpegBase::clearMetadata() @@ -282,16 +261,16 @@ namespace Exiv2 { setComment(image.comment()); } - int JpegBase::advanceToMarker() const + int JpegBase::advanceToMarker(FILE *fp) const { int c = -1; // Skips potential padding between markers - while ((c=fgetc(fp_)) != 0xff) { + while ((c=fgetc(fp)) != 0xff) { if (c == EOF) return -1; } // Markers can start with any number of 0xff - while ((c=fgetc(fp_)) == 0xff) { + while ((c=fgetc(fp)) == 0xff) { if (c == EOF) return -1; } return c; @@ -299,12 +278,12 @@ namespace Exiv2 { int JpegBase::readMetadata() { - if (!fp_) return 1; - rewind(fp_); + FileCloser closer(fopen(path_.c_str(), "rb")); + if (!closer.fp_) return 1; // Ensure that this is the correct image type - if (!isThisType(fp_, true)) { - if (ferror(fp_) || feof(fp_)) return 1; + if (!isThisType(closer.fp_, true)) { + if (ferror(closer.fp_) || feof(closer.fp_)) return 1; return 2; } clearMetadata(); @@ -314,23 +293,23 @@ namespace Exiv2 { DataBuf buf(bufMinSize); // Read section marker - int marker = advanceToMarker(); + int marker = advanceToMarker(closer.fp_); if (marker < 0) return 2; while (marker != sos_ && marker != eoi_ && search > 0) { // Read size and signature (ok if this hits EOF) - bufRead = (long)fread(buf.pData_, 1, bufMinSize, fp_); - if (ferror(fp_)) return 1; + bufRead = (long)fread(buf.pData_, 1, bufMinSize, closer.fp_); + if (ferror(closer.fp_)) return 1; uint16_t size = getUShort(buf.pData_, bigEndian); if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { if (size < 8) return 2; // Seek to begining and read the Exif data - fseek(fp_, 8-bufRead, SEEK_CUR); + fseek(closer.fp_, 8-bufRead, SEEK_CUR); long sizeExifData = size - 8; pExifData_ = new byte[sizeExifData]; - fread(pExifData_, 1, sizeExifData, fp_); - if (ferror(fp_) || feof(fp_)) { + fread(pExifData_, 1, sizeExifData, closer.fp_); + if (ferror(closer.fp_) || feof(closer.fp_)) { delete[] pExifData_; pExifData_ = 0; return 1; @@ -342,10 +321,10 @@ namespace Exiv2 { else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { if (size < 16) return 2; // Read the rest of the APP13 segment - // needed if bufMinSize!=16: fseek(fp_, 16-bufRead, SEEK_CUR); + // needed if bufMinSize!=16: fseek(closer.fp_, 16-bufRead, SEEK_CUR); DataBuf psData(size - 16); - fread(psData.pData_, 1, psData.size_, fp_); - if (ferror(fp_) || feof(fp_)) return 1; + fread(psData.pData_, 1, psData.size_, closer.fp_); + if (ferror(closer.fp_) || feof(closer.fp_)) return 1; const byte *record = 0; uint16_t sizeIptc = 0; uint16_t sizeHdr = 0; @@ -365,10 +344,10 @@ namespace Exiv2 { // Jpegs can have multiple comments, but for now only read // the first one (most jpegs only have one anyway). Comments // are simple single byte ISO-8859-1 strings. - fseek(fp_, 2-bufRead, SEEK_CUR); + fseek(closer.fp_, 2-bufRead, SEEK_CUR); buf.alloc(size-2); - fread(buf.pData_, 1, size-2, fp_); - if (ferror(fp_) || feof(fp_)) return 1; + fread(buf.pData_, 1, size-2, closer.fp_); + if (ferror(closer.fp_) || feof(closer.fp_)) return 1; comment_.assign(reinterpret_cast(buf.pData_), size-2); while ( comment_.length() && comment_.at(comment_.length()-1) == '\0') { @@ -379,10 +358,10 @@ namespace Exiv2 { else { if (size < 2) return 2; // Skip the remainder of the unknown segment - if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2; + if (fseek(closer.fp_, size-bufRead, SEEK_CUR)) return 2; } // Read the beginning of the next segment - marker = advanceToMarker(); + marker = advanceToMarker(closer.fp_); if (marker < 0) return 2; } return 0; @@ -434,18 +413,18 @@ namespace Exiv2 { int JpegBase::writeMetadata() { - if (!fp_) return 1; - rewind(fp_); + FileCloser reader(fopen(path_.c_str(), "rb")); + if (!reader.fp_) return 1; // Write the output to a temporary file pid_t pid = getpid(); std::string tmpname = path_ + toString(pid); - FILE* ofl = fopen(tmpname.c_str(), "wb"); - if (!ofl) return -3; + FileCloser writer(fopen(tmpname.c_str(), "wb")); + if (!writer.fp_) return -3; - int rc = doWriteMetadata(ofl); - fclose(ofl); - fclose(fp_); + int rc = doWriteMetadata(reader.fp_, writer.fp_); + writer.close(); + reader.close(); if (rc == 0) { // Workaround for MSVCRT rename that does not overwrite existing files if (remove(path_.c_str()) != 0) rc = -4; @@ -458,26 +437,24 @@ namespace Exiv2 { // remove temporary file remove(tmpname.c_str()); } - // Reopen the file - fp_ = fopen(path_.c_str(), "rb"); - if (!fp_) return -1; - return rc; } // JpegBase::writeMetadata - int JpegBase::doWriteMetadata(FILE* ofp) const + int JpegBase::doWriteMetadata(FILE *ifp, FILE* ofp) const { - if (!fp_) return 1; + if (!ifp) return 1; + if (!ofp) return 4; + // Ensure that this is the correct image type - if (!isThisType(fp_, true)) { - if (ferror(fp_) || feof(fp_)) return 1; + if (!isThisType(ifp, true)) { + if (ferror(ifp) || feof(ifp)) return 1; return 2; } const long bufMinSize = 16; long bufRead = 0; DataBuf buf(bufMinSize); - const long seek = ftell(fp_); + const long seek = ftell(ifp); int count = 0; int search = 0; int insertPos = 0; @@ -490,7 +467,7 @@ namespace Exiv2 { if (writeHeader(ofp)) return 4; // Read section marker - int marker = advanceToMarker(); + int marker = advanceToMarker(ifp); if (marker < 0) return 2; // First find segments of interest. Normally app0 is first and we want @@ -498,45 +475,44 @@ namespace Exiv2 { // don't bother. while (marker != sos_ && marker != eoi_ && search < 3) { // Read size and signature (ok if this hits EOF) - bufRead = (long)fread(buf.pData_, 1, bufMinSize, fp_); - if (ferror(fp_)) return 1; + bufRead = (long)fread(buf.pData_, 1, bufMinSize, ifp); + if (ferror(ifp)) return 1; uint16_t size = getUShort(buf.pData_, bigEndian); if (marker == app0_) { if (size < 2) return 2; insertPos = count + 1; - if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2; + if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; } else if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { if (size < 8) return 2; skipApp1Exif = count; ++search; - if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2; + if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; } else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { if (size < 16) return 2; skipApp13Ps3 = count; ++search; - // needed if bufMinSize!=16: fseek(fp_, 16-bufRead, SEEK_CUR); + // needed if bufMinSize!=16: fseek(ifp, 16-bufRead, SEEK_CUR); psData.alloc(size - 16); // Load PS data now to allow reinsertion at any point - fread(psData.pData_, 1, psData.size_, fp_); - if (ferror(fp_) || feof(fp_)) return 1; + fread(psData.pData_, 1, psData.size_, ifp); + if (ferror(ifp) || feof(ifp)) return 1; } - else if (marker == com_ && skipCom == -1) - { + else if (marker == com_ && skipCom == -1) { if (size < 2) return 2; // Jpegs can have multiple comments, but for now only handle // the first one (most jpegs only have one anyway). skipCom = count; ++search; - if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2; + if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; } else { if (size < 2) return 2; - if (fseek(fp_, size-bufRead, SEEK_CUR)) return 2; + if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; } - marker = advanceToMarker(); + marker = advanceToMarker(ifp); if (marker < 0) return 2; ++count; } @@ -545,9 +521,9 @@ namespace Exiv2 { if (pIptcData_) ++search; if (!comment_.empty()) ++search; - fseek(fp_, seek, SEEK_SET); + fseek(ifp, seek, SEEK_SET); count = 0; - marker = advanceToMarker(); + marker = advanceToMarker(ifp); if (marker < 0) return 2; // To simplify this a bit, new segments are inserts at either the start @@ -556,8 +532,8 @@ namespace Exiv2 { // Segments are erased if there is no assigned metadata. while (marker != sos_ && search > 0) { // Read size and signature (ok if this hits EOF) - bufRead = (long)fread(buf.pData_, 1, bufMinSize, fp_); - if (ferror(fp_)) return 1; + bufRead = (long)fread(buf.pData_, 1, bufMinSize, ifp); + if (ferror(ifp)) return 1; // Careful, this can be a meaningless number for empty // images with only an eoi_ marker uint16_t size = getUShort(buf.pData_, bigEndian); @@ -624,7 +600,7 @@ namespace Exiv2 { if (fwrite(tmpBuf, 1, 12, ofp) != 12) return 4; if (fwrite(pIptcData_, 1, sizeIptcData_ , ofp) != (size_t)sizeIptcData_) return 4; // data is padded to be even (but not included in size) - if (sizeIptcData_ & 1 ) { + if (sizeIptcData_ & 1) { if (fputc(0, ofp)==EOF) return 4; } if (ferror(ofp)) return 4; @@ -636,35 +612,35 @@ namespace Exiv2 { if (ferror(ofp)) return 4; } } - if( marker == eoi_ ) { + if (marker == eoi_) { break; } else if (skipApp1Exif==count || skipApp13Ps3==count || skipCom==count) { --search; - fseek(fp_, size-bufRead, SEEK_CUR); + fseek(ifp, size-bufRead, SEEK_CUR); } else { if (size < 2) return 2; buf.alloc(size+2); - fseek(fp_, -bufRead-2, SEEK_CUR); - fread(buf.pData_, 1, size+2, fp_); - if (ferror(fp_) || feof(fp_)) return 1; + fseek(ifp, -bufRead-2, SEEK_CUR); + fread(buf.pData_, 1, size+2, ifp); + if (ferror(ifp) || feof(ifp)) return 1; if (fwrite(buf.pData_, 1, size+2, ofp) != (size_t)size+2) return 4; if (ferror(ofp)) return 4; } // Next marker - marker = advanceToMarker(); + marker = advanceToMarker(ifp); if (marker < 0) return 2; ++count; } // Copy rest of the stream - fseek(fp_, -2, SEEK_CUR); + fseek(ifp, -2, SEEK_CUR); fflush( ofp ); buf.alloc(4096); size_t readSize = 0; - while ((readSize=fread(buf.pData_, 1, buf.size_, fp_))) { + while ((readSize=fread(buf.pData_, 1, buf.size_, ifp))) { if (fwrite(buf.pData_, 1, readSize, ofp) != readSize) return 4; } if (ferror(ofp)) return 4; @@ -713,16 +689,16 @@ namespace Exiv2 { return isJpegType(ifp, advance); } - Image::AutoPtr newJpegInstance(const std::string& path, FILE* fp) + Image::AutoPtr newJpegInstance(const std::string& path, bool create) { Image::AutoPtr image; - if (fp == 0) { + if (create) { image = Image::AutoPtr(new JpegImage(path, true)); - if (!image->good()) image.reset(); } else { - image = Image::AutoPtr(new JpegImage(path, fp)); + image = Image::AutoPtr(new JpegImage(path, false)); } + if (!image->good()) image.reset(); return image; } @@ -766,16 +742,16 @@ namespace Exiv2 { return isExvType(ifp, advance); } - Image::AutoPtr newExvInstance(const std::string& path, FILE* fp) + Image::AutoPtr newExvInstance(const std::string& path, bool create) { Image::AutoPtr image; - if (fp == 0) { + if (create) { image = Image::AutoPtr(new ExvImage(path, true)); - if (!image->good()) image.reset(); } else { - image = Image::AutoPtr(new ExvImage(path, fp)); + image = Image::AutoPtr(new ExvImage(path, false)); } + if (!image->good()) image.reset(); return image; } diff --git a/src/image.hpp b/src/image.hpp index 8bb0ace8..5aa270dc 100644 --- a/src/image.hpp +++ b/src/image.hpp @@ -126,11 +126,6 @@ namespace Exiv2 { from the actual file until writeMetadata is called. */ virtual void clearMetadata() =0; - /*! - @brief Close associated image file but preserve buffered metadata. - @return 0 if successful. - */ - virtual int detach() =0; //@} //! @name Accessors @@ -179,7 +174,7 @@ namespace Exiv2 { //! Type for function pointer that creates new Image instances typedef Image::AutoPtr (*NewInstanceFct)(const std::string& path, - FILE* ifp); + bool create); //! Type for function pointer that checks image types typedef bool (*IsThisTypeFct)(FILE* ifp, bool advance); @@ -325,7 +320,6 @@ namespace Exiv2 { void clearComment(); void setMetadata(const Image& image); void clearMetadata(); - int detach(); //@} //! @name Accessors @@ -341,13 +335,6 @@ namespace Exiv2 { protected: //! @name Creators //@{ - /*! - @brief Constructor for subclasses that have already opened a - file stream on the specified path. - @param path Full path to image file. - @param fp File pointer to open file. - */ - JpegBase(const std::string& path, FILE* fp); /*! @brief Constructor that can either open an existing image or create a new image from scratch. If a new image is to be created, any @@ -409,7 +396,6 @@ namespace Exiv2 { private: // DATA - FILE* fp_; //!< Image file (read write) const std::string path_; //!< Image file name long sizeExifData_; //!< Size of the Exif data buffer byte* pExifData_; //!< Exif data buffer @@ -422,10 +408,11 @@ namespace Exiv2 { @brief Advances file stream to one byte past the next Jpeg marker and returns the marker. This method should be called when the file stream is positioned one byte past the end of a Jpeg segment. + @param fp File stream to advance @return the next Jpeg segment marker if successful;
-1 if a maker was not found before EOF;
*/ - int advanceToMarker() const; + int advanceToMarker(FILE *fp) const; /*! @brief Locates Photoshop formated Iptc data in a memory buffer. Operates on raw data (rather than file streams) to simplify reuse. @@ -450,23 +437,25 @@ namespace Exiv2 { uint16_t *const sizeHdr, uint16_t *const sizeIptc) const; /*! - @brief Write to the associated file stream with the provided data. + @brief Write to the specified file stream with the provided data. + @param fp File stream to be written to (should be "w+b" mode) @param initData Data to be written to the associated file @param dataSize Size in bytes of data to be written @return 0 if successful;
4 if the output file can not be written to;
*/ - int initFile(const byte initData[], size_t dataSize); + int initFile(FILE* fp, const byte initData[], size_t dataSize); /*! @brief Provides the main implementation of writeMetadata by writing all buffered metadata to associated file. - @param os Output stream to write to (e.g., a temporary file). + @param ifp Input file stream. Non-metadata is copied to output file. + @param ofp Output file stream to write to (e.g., a temporary file). @return 0 if successful;
- 1 if reading from associated file failed;
- 2 if the file does not contain a valid image;
- 4 if the temporary output file can not be written to;
+ 1 if reading from input file failed;
+ 2 if the input file does not contain a valid image;
+ 4 if the output file can not be written to;
*/ - int doWriteMetadata(FILE* ofp) const; + int doWriteMetadata(FILE *ifp, FILE* ofp) const; // NOT Implemented //! Default constructor. @@ -481,7 +470,6 @@ namespace Exiv2 { @brief Helper class to access JPEG images */ class JpegImage : public JpegBase { - friend Image::AutoPtr newJpegInstance(const std::string& path, FILE* fp); friend bool isJpegType(FILE* ifp, bool advance); public: //! @name Creators @@ -528,17 +516,6 @@ namespace Exiv2 { static const byte soi_; // SOI marker static const byte blank_[]; // Minimal Jpeg image - //! @name Creators - //@{ - /*! - @brief Constructor to be used when a Jpeg file has already - been opened. Meant for internal factory use. - @param path Full path to opened image file. - @param fp File pointer to open file. - */ - JpegImage(const std::string& path, FILE* fp) : JpegBase(path, fp) {} - //@} - // NOT Implemented //! Default constructor JpegImage(); @@ -550,7 +527,6 @@ namespace Exiv2 { //! Helper class to access %Exiv2 files class ExvImage : public JpegBase { - friend Image::AutoPtr newExvInstance(const std::string& path, FILE* fp); friend bool isExvType(FILE* ifp, bool advance); public: //! @name Creators @@ -597,17 +573,6 @@ namespace Exiv2 { static const char exiv2Id_[]; // Exv identifier static const byte blank_[]; // Minimal exiv file - //! @name Creators - //@{ - /*! - @brief Constructor to be used when an Exv file has already - been opened. Meant for internal factory use. - @param path Full path to opened image file. - @param fp File pointer to open file. - */ - ExvImage(const std::string& path, FILE* fp) : JpegBase(path, fp) {} - //@} - // NOT Implemented //! Default constructor ExvImage(); diff --git a/src/metacopy.cpp b/src/metacopy.cpp index 595b4320..24452a8c 100644 --- a/src/metacopy.cpp +++ b/src/metacopy.cpp @@ -62,7 +62,6 @@ try { ": Could not read metadata from (" << params.read_ << ")\n"; return 5; } - readImg->detach(); Exiv2::Image::AutoPtr writeImg = Exiv2::ImageFactory::instance().open(params.write_); diff --git a/src/types.hpp b/src/types.hpp index 2593d69e..0490f829 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -153,6 +153,30 @@ namespace Exiv2 { byte* pData_; }; // class DataBuf + /*! + @brief Utility class that closes a file stream pointer upon destruction. + Its primary use is to be a stack variable in functions that need + to ensure files get closed. Useful when functions return errors + from many locations. + */ + class FileCloser { + // Not implemented + //! Copy constructor + FileCloser(const FileCloser&); + //! Assignment operator + FileCloser& operator=(const FileCloser&); + public: + //! Default constructor + FileCloser() : fp_(0) {} + //! Constructor with an initial buffer size + FileCloser(FILE *fp) : fp_(fp) {} + //! Destructor, deletes the allocated buffer + ~FileCloser() { close(); } + void close() { if (fp_) fclose(fp_); fp_ = 0; } + //! The file stream pointer + FILE *fp_; + }; // class FileCloser + // ***************************************************************************** // free functions