mirror of
https://github.com/microsoft/Microsoft-3D-Movie-Maker.git
synced 2024-11-22 10:22:40 +01:00
1511 lines
33 KiB
C++
1511 lines
33 KiB
C++
/* Copyright (c) Microsoft Corporation.
|
|
Licensed under the MIT License. */
|
|
|
|
/***************************************************************************
|
|
Author: ShonK
|
|
Project: Kauai
|
|
Reviewed:
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Shared (platform independent) file APIs.
|
|
|
|
***************************************************************************/
|
|
#include "util.h"
|
|
ASSERTNAME
|
|
|
|
|
|
FTG FIL::vftgCreator = '____';
|
|
PFIL FIL::_pfilFirst;
|
|
MUTX FIL::_mutxList;
|
|
|
|
|
|
RTCLASS(FIL)
|
|
RTCLASS(BLCK)
|
|
RTCLASS(MSFIL)
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a file.
|
|
***************************************************************************/
|
|
FIL::FIL(FNI *pfni, ulong grffil)
|
|
{
|
|
AssertPo(pfni, ffniFile);
|
|
_fni = *pfni;
|
|
_grffil = grffil;
|
|
|
|
// add it to the linked list
|
|
_mutxList.Enter();
|
|
_Attach(&_pfilFirst);
|
|
_mutxList.Leave();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor. This is private.
|
|
***************************************************************************/
|
|
FIL::~FIL(void)
|
|
{
|
|
// make sure the file is closed.
|
|
_Close(fTrue);
|
|
|
|
_mutxList.Enter();
|
|
_Attach(pvNil);
|
|
_mutxList.Leave();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to open an existing file. Increments the open count.
|
|
***************************************************************************/
|
|
PFIL FIL::PfilOpen(FNI *pfni, ulong grffil)
|
|
{
|
|
AssertPo(pfni, ffniFile);
|
|
PFIL pfil;
|
|
|
|
Assert(!(grffil & ffilTemp), "can't open a file as temp");
|
|
if (pvNil != (pfil = PfilFromFni(pfni)))
|
|
{
|
|
if (!pfil->FSetGrffil(grffil))
|
|
return pvNil;
|
|
|
|
// increment the open count
|
|
pfil->AddRef();
|
|
return pfil;
|
|
}
|
|
|
|
if ((pfil = NewObj FIL(pfni, grffil)) == pvNil)
|
|
goto LFail;
|
|
|
|
if (!pfil->_FOpen(fFalse, grffil))
|
|
{
|
|
delete pfil;
|
|
LFail:
|
|
PushErc(ercFileOpen);
|
|
return pvNil;
|
|
}
|
|
|
|
AssertPo(pfil, 0);
|
|
return pfil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Create a new file. Increments the open count.
|
|
***************************************************************************/
|
|
PFIL FIL::PfilCreate(FNI *pfni, ulong grffil)
|
|
{
|
|
AssertPo(pfni, ffniFile);
|
|
PFIL pfil;
|
|
|
|
if (pvNil != (pfil = FIL::PfilFromFni(pfni)))
|
|
{
|
|
Bug("trying to create an open file");
|
|
return pvNil;
|
|
}
|
|
|
|
grffil |= ffilWriteEnable;
|
|
if ((pfil = NewObj FIL(pfni, grffil)) == pvNil)
|
|
goto LFail;
|
|
|
|
if (!pfil->_FOpen(fTrue, grffil))
|
|
{
|
|
delete pfil;
|
|
LFail:
|
|
PushErc(ercFileCreate);
|
|
return pvNil;
|
|
}
|
|
|
|
AssertPo(pfil, 0);
|
|
return pfil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to create a temp file in the same directory as fni with
|
|
the same ftg, or, if pfni is nil, in the standard place with vftgTemp.
|
|
The file is not marked.
|
|
***************************************************************************/
|
|
PFIL FIL::PfilCreateTemp(FNI *pfni)
|
|
{
|
|
AssertNilOrPo(pfni, ffniFile);
|
|
FNI fni;
|
|
|
|
if (pvNil != pfni)
|
|
{
|
|
fni = *pfni;
|
|
if (!fni.FGetUnique(pfni->Ftg()))
|
|
goto LFail;
|
|
}
|
|
else if (!fni.FGetTemp())
|
|
{
|
|
LFail:
|
|
PushErc(ercFileCreate);
|
|
return pvNil;
|
|
}
|
|
|
|
return PfilCreate(&fni, ffilTemp | ffilWriteEnable | ffilDenyWrite);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
If we have the file indicated by fni open, returns the pfil, otherwise
|
|
returns pvNil. Doesn't affect the open count.
|
|
***************************************************************************/
|
|
PFIL FIL::PfilFromFni(FNI *pfni)
|
|
{
|
|
AssertPo(pfni, ffniFile);
|
|
PFIL pfil;
|
|
bool fRet;
|
|
|
|
_mutxList.Enter();
|
|
for (pfil = _pfilFirst; pfil != pvNil; pfil = pfil->PfilNext())
|
|
{
|
|
AssertPo(pfil, 0);
|
|
pfil->_mutx.Enter();
|
|
fRet = pfni->FEqual(&pfil->_fni);
|
|
pfil->_mutx.Leave();
|
|
if (fRet)
|
|
break;
|
|
}
|
|
_mutxList.Leave();
|
|
|
|
return pfil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the file flags according to grffil and grffilMask. Write enabling
|
|
is only set, never cleared. Same with marking.
|
|
***************************************************************************/
|
|
bool FIL::FSetGrffil(ulong grffil, ulong grffilMask)
|
|
{
|
|
AssertThis(0);
|
|
bool fRet = fFalse;
|
|
|
|
grffil &= grffilMask;
|
|
|
|
_mutx.Enter();
|
|
|
|
// make sure the permissions are high enough
|
|
if ((~_grffil & grffil & kgrffilPerm) && !_FOpen(fFalse, grffil))
|
|
{
|
|
PushErc(ercFilePerm);
|
|
goto LRet;
|
|
}
|
|
|
|
// adjust the mark flag
|
|
if (grffil & ffilMark)
|
|
_grffil |= ffilMark;
|
|
|
|
// adjust the temp flag
|
|
if (grffilMask & ffilTemp)
|
|
{
|
|
if (grffil & ffilTemp)
|
|
_grffil |= ffilTemp;
|
|
else
|
|
_grffil &= ~ffilTemp;
|
|
}
|
|
|
|
fRet = fTrue;
|
|
|
|
LRet:
|
|
_mutx.Leave();
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Decrement the open count. If it is zero and the file isn't marked,
|
|
the file is closed.
|
|
***************************************************************************/
|
|
void FIL::Release(void)
|
|
{
|
|
AssertThis(0);
|
|
if (_cactRef <= 0)
|
|
{
|
|
Bug("calling Release without an AddRef");
|
|
_cactRef = 0;
|
|
return;
|
|
}
|
|
if (--_cactRef == 0 && !(_grffil & ffilMark))
|
|
delete this;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get a string representing the path of the file.
|
|
***************************************************************************/
|
|
void FIL::GetStnPath(PSTN pstn)
|
|
{
|
|
AssertThis(0);
|
|
|
|
_mutx.Enter();
|
|
_fni.GetStnPath(pstn);
|
|
_mutx.Leave();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the temporary status of a file.
|
|
***************************************************************************/
|
|
void FIL::SetTemp(bool fTemp)
|
|
{
|
|
AssertThis(0);
|
|
|
|
_mutx.Enter();
|
|
if (fTemp)
|
|
_grffil |= ffilTemp;
|
|
else
|
|
_grffil &= ~ffilTemp;
|
|
_mutx.Leave();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Rename the file to the given file name. If an open file exists with
|
|
the same name, we rename it and swap names with it as a temporary file.
|
|
Otherwise, we delete any existing file with the same name. The rules
|
|
for *pfni are the same as for FRename.
|
|
***************************************************************************/
|
|
bool FIL::FSetFni(FNI *pfni)
|
|
{
|
|
AssertPo(pfni, ffniFile);
|
|
PFIL pfilOld;
|
|
|
|
if (pvNil != (pfilOld = FIL::PfilFromFni(pfni)))
|
|
{
|
|
if (this == pfilOld)
|
|
return fTrue;
|
|
|
|
if (!FSwapNames(pfilOld))
|
|
return fFalse;
|
|
|
|
pfilOld->SetTemp(fTrue);
|
|
return fTrue;
|
|
}
|
|
|
|
// delete any existing file with this name, then rename our
|
|
// file to the given name
|
|
if (pfni->TExists() != tNo)
|
|
pfni->FDelete();
|
|
return FRename(pfni);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to clear the marks for files.
|
|
***************************************************************************/
|
|
void FIL::ClearMarks(void)
|
|
{
|
|
PFIL pfil;
|
|
|
|
_mutxList.Enter();
|
|
for (pfil = _pfilFirst; pfil != pvNil; pfil = pfil->PfilNext())
|
|
{
|
|
AssertPo(pfil, 0);
|
|
pfil->FSetGrffil(ffilNil, ffilMark);
|
|
}
|
|
_mutxList.Leave();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to close any files that are unmarked and have 0 open count.
|
|
***************************************************************************/
|
|
void FIL::CloseUnmarked(void)
|
|
{
|
|
PFIL pfil, pfilNext;
|
|
|
|
_mutxList.Enter();
|
|
for (pfil = _pfilFirst; pfil != pvNil; pfil = pfilNext)
|
|
{
|
|
AssertPo(pfil, 0);
|
|
pfilNext = pfil->PfilNext();
|
|
if (!(pfil->_grffil & ffilMark) && pfil->_cactRef == 0)
|
|
delete pfil;
|
|
}
|
|
_mutxList.Leave();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to close all files.
|
|
***************************************************************************/
|
|
void FIL::ShutDown(void)
|
|
{
|
|
PFIL pfil;
|
|
|
|
_mutxList.Enter();
|
|
for (pfil = _pfilFirst; pfil != pvNil; pfil = pfil->PfilNext())
|
|
{
|
|
AssertPo(pfil, 0);
|
|
pfil->_Close(fTrue);
|
|
}
|
|
_mutxList.Leave();
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a pfil.
|
|
***************************************************************************/
|
|
void FIL::AssertValid(ulong grf)
|
|
{
|
|
PFIL pfil;
|
|
|
|
FIL_PAR::AssertValid(fobjAllocated);
|
|
|
|
_mutx.Enter();
|
|
AssertPo(&_fni, ffniFile);
|
|
_mutx.Leave();
|
|
|
|
_mutxList.Enter();
|
|
for (pfil = _pfilFirst; pfil != pvNil; pfil = pfil->PfilNext())
|
|
{
|
|
if (pfil == this)
|
|
break;
|
|
}
|
|
_mutxList.Leave();
|
|
|
|
Assert(this == pfil, "not in file list");
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Determine if the given range is within cbTot.
|
|
***************************************************************************/
|
|
priv bool _FRangeIn(long cbTot, long cb, long ib)
|
|
{
|
|
return FIn(ib, 0, cbTot + 1) && FIn(cb, 0, cbTot - ib + 1);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a piece of a flo into pv.
|
|
***************************************************************************/
|
|
bool FLO::FReadRgb(void *pv, long cbRead, FP dfp)
|
|
{
|
|
AssertThis(ffloReadable);
|
|
|
|
if (!_FRangeIn(this->cb, cbRead, dfp))
|
|
{
|
|
Bug("reading outside flo");
|
|
return fFalse;
|
|
}
|
|
|
|
if (cbRead == 0)
|
|
return fTrue;
|
|
return this->pfil->FReadRgb(pv, cbRead, this->fp + dfp);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write a piece of a flo from pv.
|
|
***************************************************************************/
|
|
bool FLO::FWriteRgb(void *pv, long cbWrite, FP dfp)
|
|
{
|
|
AssertThis(0);
|
|
|
|
if (!_FRangeIn(this->cb, cbWrite, dfp))
|
|
{
|
|
Bug("writing outside flo");
|
|
return fFalse;
|
|
}
|
|
|
|
if (cbWrite == 0)
|
|
return fTrue;
|
|
return this->pfil->FWriteRgb(pv, cbWrite, this->fp + dfp);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Copy data from this flo to another.
|
|
***************************************************************************/
|
|
bool FLO::FCopy(PFLO pfloDst)
|
|
{
|
|
AssertThis(ffloReadable);
|
|
AssertPo(pfloDst, 0);
|
|
byte rgb[1024];
|
|
long cbBlock, cbT;
|
|
void *pv;
|
|
bool fRet = fFalse;
|
|
|
|
if (this->cb != pfloDst->cb)
|
|
{
|
|
Bug("different sized FLOs");
|
|
return fFalse;
|
|
}
|
|
|
|
if (this->cb <= size(rgb) ||
|
|
!FAllocPv(&pv, cbBlock = this->cb, fmemNil, mprForSpeed))
|
|
{
|
|
pv = (void *)rgb;
|
|
cbBlock = size(rgb);
|
|
}
|
|
|
|
for (cbT = 0; cbT < this->cb; cbT += cbBlock)
|
|
{
|
|
if (cbBlock > this->cb - cbT)
|
|
cbBlock = this->cb - cbT;
|
|
// read the source
|
|
if (!this->pfil->FReadRgb(pv, cbBlock, this->fp + cbT))
|
|
goto LFail;
|
|
// write to the dest
|
|
if (!pfloDst->pfil->FWriteRgb(pv, cbBlock, pfloDst->fp + cbT))
|
|
goto LFail;
|
|
}
|
|
fRet = fTrue;
|
|
|
|
LFail:
|
|
if ((void *)rgb != pv)
|
|
FreePpv(&pv);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate an hq and read the flo into it.
|
|
***************************************************************************/
|
|
bool FLO::FReadHq(HQ *phq, long cbRead, FP dfp)
|
|
{
|
|
AssertThis(ffloReadable);
|
|
AssertVarMem(phq);
|
|
bool fT;
|
|
|
|
if (!_FRangeIn(this->cb, cbRead, dfp))
|
|
{
|
|
Bug("reading outside flo 2");
|
|
return fFalse;
|
|
}
|
|
|
|
if (!FAllocHq(phq, cbRead, fmemNil, mprNormal))
|
|
return fFalse;
|
|
fT = FReadRgb(PvLockHq(*phq), cbRead, dfp);
|
|
UnlockHq(*phq);
|
|
if (!fT)
|
|
FreePhq(phq);
|
|
return fT;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the contents of an hq to the flo.
|
|
***************************************************************************/
|
|
bool FLO::FWriteHq(HQ hq, long dfp)
|
|
{
|
|
AssertThis(0);
|
|
AssertHq(hq);
|
|
bool fRet;
|
|
long cbWrite = CbOfHq(hq);
|
|
|
|
if (!_FRangeIn(this->cb, cbWrite, dfp))
|
|
{
|
|
Bug("writing outside flo 2");
|
|
return fFalse;
|
|
}
|
|
|
|
fRet = FWriteRgb(PvLockHq(hq), cbWrite, dfp);
|
|
UnlockHq(hq);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Translate the text in a flo from the given osk to the current osk.
|
|
If the text changes, creates a temp file and redirects the flo to the
|
|
temp file (and releases a ref count on the pfil).
|
|
***************************************************************************/
|
|
bool FLO::FTranslate(short osk)
|
|
{
|
|
AssertThis(0);
|
|
short oskSig;
|
|
byte rgbSrc[512];
|
|
byte rgbDst[1024];
|
|
void *pvSrc;
|
|
void *pvDst;
|
|
long cchDst, cch;
|
|
long cbBlock, cbT;
|
|
PFIL pfilNew;
|
|
FP fpSrc, fpDst;
|
|
bool fRet = fFalse;
|
|
|
|
// look for a unicode byte order signature
|
|
oskSig = MacWin(koskSbMac, koskSbWin);
|
|
if (this->cb >= size(wchar) && this->cb % size(wchar) == 0)
|
|
{
|
|
wchar chw;
|
|
|
|
if (!FReadRgb(&chw, size(wchar), 0))
|
|
return fFalse;
|
|
if (chw == kchwUnicode)
|
|
{
|
|
oskSig = MacWin(koskUniMac, koskUniWin);
|
|
this->fp += size(wchar);
|
|
this->cb -= size(wchar);
|
|
}
|
|
else if (chw == kchwUnicodeSwap)
|
|
{
|
|
oskSig = MacWin(koskUniWin, koskUniMac);
|
|
this->fp += size(wchar);
|
|
this->cb -= size(wchar);
|
|
}
|
|
}
|
|
|
|
// determine the probable osk
|
|
if (oskSig != osk)
|
|
{
|
|
if (oskNil == osk)
|
|
osk = oskSig;
|
|
else
|
|
{
|
|
long dcb = CbCharOsk(osk) - CbCharOsk(oskSig);
|
|
|
|
if (dcb < 0 || dcb == 0 && CbCharOsk(osk) == size(wchar))
|
|
osk = oskSig;
|
|
}
|
|
}
|
|
|
|
if (osk == koskCur)
|
|
return fTrue;
|
|
|
|
if (pvNil == (pfilNew = FIL::PfilCreateTemp()))
|
|
return fFalse;
|
|
|
|
if (this->cb <= size(rgbSrc) ||
|
|
!FAllocPv(&pvSrc, cbBlock = this->cb, fmemNil, mprForSpeed))
|
|
{
|
|
pvSrc = (void *)rgbSrc;
|
|
cbBlock = size(rgbDst);
|
|
}
|
|
|
|
pvDst = (void *)rgbDst;
|
|
cchDst = size(rgbDst) / size(achar);
|
|
fpSrc = this->fp;
|
|
fpDst = 0;
|
|
for (cbT = 0; cbT < this->cb; cbT += cbBlock)
|
|
{
|
|
if (cbBlock > this->cb - cbT)
|
|
cbBlock = this->cb - cbT;
|
|
|
|
// read the source
|
|
if (!this->pfil->FReadRgbSeq(pvSrc, cbBlock, &fpSrc))
|
|
goto LFail;
|
|
|
|
// translate
|
|
cch = CchTranslateRgb(pvSrc, cbBlock, osk, pvNil, 0);
|
|
if (cch <= 0)
|
|
continue;
|
|
|
|
if (cch > cchDst)
|
|
{
|
|
if (pvDst != (void *)rgbDst)
|
|
FreePpv(&pvDst);
|
|
cchDst = cch;
|
|
if (!FAllocPv(&pvDst, cchDst * size(achar), fmemNil, mprNormal))
|
|
goto LFail;
|
|
}
|
|
if (cch != CchTranslateRgb(pvSrc, cbBlock, osk, (achar *)pvDst, cch))
|
|
{
|
|
Bug("why did CchTranslateRgb fail?");
|
|
goto LFail;
|
|
}
|
|
|
|
// write to the dest
|
|
if (!pfilNew->FWriteRgbSeq(pvDst, cch * size(achar), &fpDst))
|
|
goto LFail;
|
|
}
|
|
fRet = fTrue;
|
|
ReleasePpo(&this->pfil);
|
|
this->pfil = pfilNew;
|
|
this->fp = 0;
|
|
this->cb = this->pfil->FpMac();
|
|
pfilNew = pvNil;
|
|
|
|
LFail:
|
|
if ((void *)rgbSrc != pvSrc)
|
|
FreePpv(&pvSrc);
|
|
if (pvDst != (void *)rgbDst)
|
|
FreePpv(&pvDst);
|
|
ReleasePpo(&pfilNew);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert this is a valif FLO.
|
|
***************************************************************************/
|
|
void FLO::AssertValid(ulong grfflo)
|
|
{
|
|
AssertPo(pfil, 0);
|
|
AssertIn(fp, 0, kcbMax);
|
|
AssertIn(cb, 0, kcbMax);
|
|
FP fpMac = pfil->FpMac();
|
|
|
|
if (pfil->ElError() < kelSeek)
|
|
{
|
|
AssertIn(fp, 0, fpMac + 1);
|
|
if (grfflo & ffloReadable)
|
|
AssertIn(fp + cb, cb, fpMac + 1);
|
|
}
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a data block.
|
|
***************************************************************************/
|
|
BLCK::BLCK(PFLO pflo, bool fPacked)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertPo(pflo, 0);
|
|
|
|
_flo = *pflo;
|
|
_flo.pfil->AddRef();
|
|
_hq = hqNil;
|
|
_fPacked = FPure(fPacked);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a data block.
|
|
***************************************************************************/
|
|
BLCK::BLCK(PFIL pfil, FP fp, long cb, bool fPacked)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertPo(pfil, 0);
|
|
|
|
_flo.pfil = pfil;
|
|
_flo.pfil->AddRef();
|
|
_flo.fp = fp;
|
|
_flo.cb = cb;
|
|
_hq = hqNil;
|
|
_fPacked = FPure(fPacked);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Another constructor for a data block. Assumes ownership of the hq
|
|
(and sets *phq to hqNil).
|
|
***************************************************************************/
|
|
BLCK::BLCK(HQ *phq, bool fPacked)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertVarMem(phq);
|
|
AssertHq(*phq);
|
|
|
|
_flo.pfil = pvNil;
|
|
_hq = *phq;
|
|
*phq = hqNil;
|
|
_ibMin = 0;
|
|
_ibLim = CbOfHq(_hq);
|
|
_fPacked = FPure(fPacked);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Another constructor for a data block.
|
|
***************************************************************************/
|
|
BLCK::BLCK(void)
|
|
{
|
|
AssertBaseThis(0);
|
|
_flo.pfil = pvNil;
|
|
_hq = hqNil;
|
|
_fPacked = fFalse;
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
The destructor.
|
|
***************************************************************************/
|
|
BLCK::~BLCK(void)
|
|
{
|
|
AssertThis(0);
|
|
Free();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the data block to refer to the given flo.
|
|
***************************************************************************/
|
|
void BLCK::Set(PFLO pflo, bool fPacked)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pflo, 0);
|
|
|
|
Free();
|
|
_flo = *pflo;
|
|
_flo.pfil->AddRef();
|
|
_fPacked = FPure(fPacked);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the data block to refer to the given range on the file.
|
|
***************************************************************************/
|
|
void BLCK::Set(PFIL pfil, FP fp, long cb, bool fPacked)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pfil, 0);
|
|
|
|
Free();
|
|
_flo.pfil = pfil;
|
|
_flo.pfil->AddRef();
|
|
_flo.fp = fp;
|
|
_flo.cb = cb;
|
|
_fPacked = FPure(fPacked);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the data block to the given hq. Assumes ownership of the hq and
|
|
sets *phq to hqNil.
|
|
***************************************************************************/
|
|
void BLCK::SetHq(HQ *phq, bool fPacked)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(phq);
|
|
AssertHq(*phq);
|
|
|
|
Free();
|
|
_hq = *phq;
|
|
*phq = hqNil;
|
|
_ibMin = 0;
|
|
_ibLim = CbOfHq(_hq);
|
|
_fPacked = FPure(fPacked);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Free the block (make it empty).
|
|
***************************************************************************/
|
|
void BLCK::Free(void)
|
|
{
|
|
AssertThis(0);
|
|
ReleasePpo(&_flo.pfil);
|
|
FreePhq(&_hq);
|
|
_fPacked = fFalse;
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return an hq to the data. If the blck is a memory based block, the
|
|
block is also "freed". If the block hasn't been packed or unpacked
|
|
or had its min or lim moved, the hq returned is the one originally
|
|
passed to the constructor or SetHq.
|
|
***************************************************************************/
|
|
HQ BLCK::HqFree(bool fPackedOk)
|
|
{
|
|
AssertThis(0);
|
|
HQ hq;
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("accessing packed data");
|
|
return hqNil;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
_flo.FReadHq(&hq);
|
|
return hq;
|
|
}
|
|
|
|
if (pvNil != _hq)
|
|
{
|
|
hq = _hq;
|
|
_hq = hqNil;
|
|
_fPacked = fFalse;
|
|
|
|
if (_ibMin > 0)
|
|
{
|
|
byte *qrgb = (byte *)QvFromHq(hq);
|
|
BltPb(qrgb + _ibMin, qrgb, _ibLim - _ibMin);
|
|
_ibLim -= _ibMin;
|
|
}
|
|
if (CbOfHq(hq) > _ibLim)
|
|
AssertDo(FResizePhq(&hq, _ibLim, fmemNil, mprNormal), 0);
|
|
AssertThis(0);
|
|
return hq;
|
|
}
|
|
|
|
return hqNil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return the length of the data block.
|
|
***************************************************************************/
|
|
long BLCK::Cb(bool fPackedOk)
|
|
{
|
|
AssertThis(fPackedOk ? 0 : fblckUnpacked);
|
|
|
|
if (pvNil != _flo.pfil)
|
|
return _flo.cb;
|
|
if (hqNil != _hq)
|
|
return _ibLim - _ibMin;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Create a temporary buffer.
|
|
***************************************************************************/
|
|
bool BLCK::FSetTemp(long cb, bool fForceFile)
|
|
{
|
|
AssertThis(0);
|
|
PFIL pfil;
|
|
|
|
if (!fForceFile && cb < (1L << 23) /* 8 MB */)
|
|
{
|
|
// try to allocate enough mem
|
|
HQ hq;
|
|
|
|
if (FAllocHq(&hq, cb, fmemNil, mprNormal))
|
|
{
|
|
SetHq(&hq, _fPacked);
|
|
return fTrue;
|
|
}
|
|
}
|
|
|
|
if (pvNil == (pfil = FIL::PfilCreateTemp()))
|
|
return fFalse;
|
|
|
|
Set(pfil, 0, cb, _fPacked);
|
|
ReleasePpo(&pfil);
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Move the beginning of the block. Doesn't change the location of the
|
|
end of the block. Fails if you try to move before the beginning of
|
|
the physical storage or after the lim of the block.
|
|
***************************************************************************/
|
|
bool BLCK::FMoveMin(long dib)
|
|
{
|
|
AssertThis(0);
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
if (!FIn(dib, -_flo.fp, _flo.cb + 1))
|
|
return fFalse;
|
|
_flo.fp += dib;
|
|
_flo.cb -= dib;
|
|
return fTrue;
|
|
}
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
if (!FIn(dib + _ibMin, 0, _ibLim + 1))
|
|
return fFalse;
|
|
_ibMin += dib;
|
|
return fTrue;
|
|
}
|
|
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Move the end of the block. Doesn't change the location of the
|
|
beginning of the block. Fails if you try to move before the min of the
|
|
block or after the end of the physical storage.
|
|
***************************************************************************/
|
|
bool BLCK::FMoveLim(long dib)
|
|
{
|
|
AssertThis(0);
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
if (!FIn(dib, -_flo.cb, kcbMax - _flo.fp))
|
|
return fFalse;
|
|
_flo.cb += dib;
|
|
return fTrue;
|
|
}
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
if (!FIn(dib + _ibLim, _ibMin, CbOfHq(_hq) + 1))
|
|
return fFalse;
|
|
_ibLim += dib;
|
|
return fTrue;
|
|
}
|
|
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a range of bytes from the data block.
|
|
***************************************************************************/
|
|
bool BLCK::FReadRgb(void *pv, long cb, long ib, bool fPackedOk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPvCb(pv, cb);
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("reading packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if (!_FRangeIn(Cb(fTrue), cb, ib))
|
|
{
|
|
Bug("reading outside blck");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
return _flo.FReadRgb(pv, cb, ib);
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
CopyPb((byte *)QvFromHq(_hq) + ib + _ibMin, pv, cb);
|
|
return fTrue;
|
|
}
|
|
|
|
Assert(cb == 0 && ib == 0, 0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write a range of bytes to the data block.
|
|
***************************************************************************/
|
|
bool BLCK::FWriteRgb(void *pv, long cb, long ib, bool fPackedOk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPvCb(pv, cb);
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("writing packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if (!_FRangeIn(Cb(fTrue), cb, ib))
|
|
{
|
|
Bug("writing outside blck");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
return _flo.FWriteRgb(pv, cb, ib);
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
CopyPb(pv, (byte *)QvFromHq(_hq) + ib + _ibMin, cb);
|
|
return fTrue;
|
|
}
|
|
|
|
Assert(cb == 0 && ib == 0, 0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a range of bytes from the data block and put it in an hq.
|
|
***************************************************************************/
|
|
bool BLCK::FReadHq(HQ *phq, long cb, long ib, bool fPackedOk)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(phq);
|
|
|
|
*phq = hqNil;
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("reading packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if (!_FRangeIn(Cb(fTrue), cb, ib))
|
|
{
|
|
Bug("reading outside blck 2");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
return _flo.FReadHq(phq, cb, ib);
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
if (!FAllocHq(phq, cb, fmemNil, mprNormal))
|
|
return fFalse;
|
|
CopyPb((byte *)QvFromHq(_hq) + ib + _ibMin, QvFromHq(*phq), cb);
|
|
return fTrue;
|
|
}
|
|
|
|
Assert(cb == 0 && ib == 0, 0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write an hq to the data block.
|
|
***************************************************************************/
|
|
bool BLCK::FWriteHq(HQ hq, long ib, bool fPackedOk)
|
|
{
|
|
AssertThis(0);
|
|
AssertHq(hq);
|
|
long cb = CbOfHq(hq);
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("writing packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if (!_FRangeIn(Cb(fTrue), cb, ib))
|
|
{
|
|
Bug("writing outside blck 2");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
return _flo.FWriteHq(hq, ib);
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
CopyPb(QvFromHq(hq), (byte *)QvFromHq(_hq) + ib + _ibMin, cb);
|
|
return fTrue;
|
|
}
|
|
|
|
Assert(cb == 0 && ib == 0, 0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the block to a flo.
|
|
***************************************************************************/
|
|
bool BLCK::FWriteToFlo(PFLO pfloDst, bool fPackedOk)
|
|
{
|
|
AssertThis(fblckReadable);
|
|
AssertPo(pfloDst, 0);
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("copying packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if (Cb(fTrue) != pfloDst->cb)
|
|
{
|
|
Bug("flo is wrong size");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
return _flo.FCopy(pfloDst);
|
|
|
|
if (hqNil != _hq)
|
|
{
|
|
bool fRet;
|
|
fRet = pfloDst->FWrite(PvAddBv(PvLockHq(_hq), _ibMin));
|
|
UnlockHq(_hq);
|
|
return fRet;
|
|
}
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write this block to another block.
|
|
***************************************************************************/
|
|
bool BLCK::FWriteToBlck(PBLCK pblckDst, bool fPackedOk)
|
|
{
|
|
AssertThis(fblckReadable);
|
|
AssertPo(pblckDst, 0);
|
|
long cb;
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("copying packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if ((cb = Cb(fTrue)) != pblckDst->Cb(fTrue))
|
|
{
|
|
Bug("blck is wrong size");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != pblckDst->_flo.pfil)
|
|
return FWriteToFlo(&pblckDst->_flo, fPackedOk);
|
|
|
|
if (hqNil != pblckDst->_hq)
|
|
{
|
|
bool fRet;
|
|
fRet = FReadRgb((byte *)PvLockHq(pblckDst->_hq) + pblckDst->_ibMin,
|
|
cb, 0, fTrue);
|
|
UnlockHq(pblckDst->_hq);
|
|
return fRet;
|
|
}
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get a flo to the data in the block.
|
|
***************************************************************************/
|
|
bool BLCK::FGetFlo(PFLO pflo, bool fPackedOk)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(pflo);
|
|
|
|
if (!fPackedOk && _fPacked)
|
|
{
|
|
Bug("accessing packed data");
|
|
return fFalse;
|
|
}
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
*pflo = _flo;
|
|
pflo->pfil->AddRef();
|
|
return fTrue;
|
|
}
|
|
|
|
if (hqNil != _hq && _ibLim > _ibMin)
|
|
{
|
|
bool fRet;
|
|
|
|
if (pvNil == (pflo->pfil = FIL::PfilCreateTemp()))
|
|
goto LFail;
|
|
pflo->fp = 0;
|
|
pflo->cb = _ibLim - _ibMin;
|
|
fRet = pflo->FWrite(PvAddBv(PvLockHq(_hq), _ibMin));
|
|
UnlockHq(_hq);
|
|
if (!fRet)
|
|
{
|
|
LFail:
|
|
ReleasePpo(&pflo->pfil);
|
|
TrashVar(pflo);
|
|
return fFalse;
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
pflo->pfil = pvNil;
|
|
pflo->fp = pflo->cb = 0;
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return whether the block is packed. If the block is compressed, but
|
|
determining the compression type failed, *pcfmt is set to cfmtNil and
|
|
true is returned.
|
|
***************************************************************************/
|
|
bool BLCK::FPacked(long *pcfmt)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrVarMem(pcfmt);
|
|
|
|
if (pvNil != pcfmt &&
|
|
(!_fPacked || !vpcodmUtil->FGetCfmtFromBlck(this, pcfmt)))
|
|
{
|
|
*pcfmt = cfmtNil;
|
|
}
|
|
|
|
return _fPacked;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
If the block is unpacked, pack it. If cfmt is cfmtNil, use the default
|
|
packing format, otherwise use the one specified. If the block is
|
|
already packed, this doesn't change the packing format.
|
|
***************************************************************************/
|
|
bool BLCK::FPackData(long cfmt)
|
|
{
|
|
AssertThis(0);
|
|
HQ hq;
|
|
|
|
if (_fPacked)
|
|
return fTrue;
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
if (!_flo.FReadHq(&hq))
|
|
return fFalse;
|
|
if (!vpcodmUtil->FCompressPhq(&hq, cfmt))
|
|
{
|
|
FreePhq(&hq);
|
|
AssertThis(fblckUnpacked | fblckFile);
|
|
return fFalse;
|
|
}
|
|
SetHq(&hq, fTrue);
|
|
}
|
|
else if (hqNil == _hq)
|
|
return fFalse;
|
|
else
|
|
{
|
|
AssertHq(_hq);
|
|
if (_ibMin != 0 || _ibLim != CbOfHq(_hq))
|
|
{
|
|
hq = HqFree();
|
|
SetHq(&hq, fFalse);
|
|
}
|
|
Assert(_ibMin == 0 && _ibLim == CbOfHq(_hq), 0);
|
|
if (!vpcodmUtil->FCompressPhq(&_hq, cfmt))
|
|
{
|
|
AssertThis(fblckUnpacked | fblckMemory);
|
|
return fFalse;
|
|
}
|
|
_ibMin = 0;
|
|
_ibLim = CbOfHq(_hq);
|
|
_fPacked = fTrue;
|
|
}
|
|
|
|
AssertThis(fblckPacked | fblckMemory);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
If the block is packed, unpack it.
|
|
***************************************************************************/
|
|
bool BLCK::FUnpackData(void)
|
|
{
|
|
AssertThis(0);
|
|
HQ hq;
|
|
|
|
if (!_fPacked)
|
|
return fTrue;
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
if (!_flo.FReadHq(&hq))
|
|
return fFalse;
|
|
if (!vpcodmUtil->FDecompressPhq(&hq))
|
|
{
|
|
FreePhq(&hq);
|
|
AssertThis(fblckPacked | fblckFile);
|
|
return fFalse;
|
|
}
|
|
SetHq(&hq, fFalse);
|
|
}
|
|
else if (hqNil == _hq)
|
|
return fFalse;
|
|
else
|
|
{
|
|
AssertHq(_hq);
|
|
if (_ibMin != 0 || _ibLim != CbOfHq(_hq))
|
|
{
|
|
hq = HqFree();
|
|
SetHq(&hq, fTrue);
|
|
}
|
|
Assert(_ibMin = 0 && _ibLim == CbOfHq(_hq), 0);
|
|
if (!vpcodmUtil->FDecompressPhq(&_hq))
|
|
{
|
|
AssertThis(fblckPacked | fblckMemory);
|
|
return fFalse;
|
|
}
|
|
_ibMin = 0;
|
|
_ibLim = CbOfHq(_hq);
|
|
_fPacked = fFalse;
|
|
}
|
|
|
|
AssertThis(fblckUnpacked | fblckMemory);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return the amount of memory the block is using (roughly).
|
|
***************************************************************************/
|
|
long BLCK::CbMem(void)
|
|
{
|
|
AssertThis(0);
|
|
|
|
if (pvNil == _flo.pfil && hqNil != _hq)
|
|
return CbOfHq(_hq);
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a BLCK.
|
|
***************************************************************************/
|
|
void BLCK::AssertValid(ulong grfblck)
|
|
{
|
|
BLCK_PAR::AssertValid(0);
|
|
|
|
if (pvNil != _flo.pfil)
|
|
{
|
|
AssertPo(&_flo, (grfblck & fblckReadable) ? ffloReadable: 0);
|
|
Assert(hqNil == _hq, "both the _flo and _hq are non-nil");
|
|
Assert(!(grfblck & fblckMemory) || (grfblck & fblckFile),
|
|
"block should be memory based");
|
|
}
|
|
else if (hqNil != _hq)
|
|
{
|
|
Assert(!(grfblck & fblckFile) || (grfblck & fblckMemory),
|
|
"block should be file based");
|
|
AssertHq(_hq);
|
|
long cb = CbOfHq(_hq);
|
|
|
|
AssertIn(_ibMin, 0, cb + 1);
|
|
AssertIn(_ibLim, _ibMin, cb + 1);
|
|
}
|
|
|
|
Assert(_fPacked || !(grfblck & fblckPacked), "block should be packed");
|
|
Assert(!_fPacked || !(grfblck & fblckUnpacked), "block should be unpacked");
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Mark memory for the BLCK.
|
|
***************************************************************************/
|
|
void BLCK::MarkMem(void)
|
|
{
|
|
AssertValid(0);
|
|
BLCK_PAR::MarkMem();
|
|
MarkHq(_hq);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a file based message sink.
|
|
***************************************************************************/
|
|
MSFIL::MSFIL(PFIL pfil)
|
|
{
|
|
AssertNilOrPo(pfil, 0);
|
|
|
|
_fError = fFalse;
|
|
_pfil = pvNil;
|
|
_fpCur = 0;
|
|
if (pvNil != pfil)
|
|
SetFile(pfil);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor for a file based message sink.
|
|
***************************************************************************/
|
|
MSFIL::~MSFIL(void)
|
|
{
|
|
AssertThis(0);
|
|
ReleasePpo(&_pfil);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a MSFIL.
|
|
***************************************************************************/
|
|
void MSFIL::AssertValid(ulong grf)
|
|
{
|
|
MSFIL_PAR::AssertValid(0);
|
|
AssertNilOrPo(_pfil, 0);
|
|
Assert(_fError || pvNil == _pfil || _fpCur == _pfil->FpMac() ||
|
|
_pfil->ElError() != elNil, "bad _fpCur");
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Set the current file to use for the MSFIL.
|
|
***************************************************************************/
|
|
void MSFIL::SetFile(PFIL pfil)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrPo(pfil, 0);
|
|
|
|
_fError = fFalse;
|
|
if (pfil != pvNil)
|
|
pfil->AddRef();
|
|
ReleasePpo(&_pfil);
|
|
_pfil = pfil;
|
|
_fpCur = 0;
|
|
if (pvNil != _pfil)
|
|
{
|
|
_fError |= !_pfil->FSetFpMac(0);
|
|
#ifdef UNICODE
|
|
wchar chw = kchwUnicode;
|
|
_fError |= !_pfil->FWriteRgbSeq(&chw, size(wchar), &_fpCur);
|
|
#endif //UNICODE
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return the output file and give the caller our reference count on it.
|
|
***************************************************************************/
|
|
PFIL MSFIL::PfilRelease(void)
|
|
{
|
|
AssertThis(0);
|
|
PFIL pfil = _pfil;
|
|
_pfil = pvNil;
|
|
return pfil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Dump a line to the file.
|
|
***************************************************************************/
|
|
void MSFIL::ReportLine(PSZ psz)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrPo(_pfil, 0);
|
|
achar rgch[2] = { kchReturn, kchLineFeed };
|
|
|
|
Report(psz);
|
|
if (pvNil != _pfil)
|
|
{
|
|
_fError |= !_pfil->FWriteRgbSeq(rgch,
|
|
MacWin(size(achar), 2 * size(achar)), &_fpCur);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Dump some text to the file.
|
|
***************************************************************************/
|
|
void MSFIL::Report(PSZ psz)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrPo(_pfil, 0);
|
|
|
|
if (pvNil == _pfil)
|
|
{
|
|
SetFile(FIL::PfilCreateTemp());
|
|
if (pvNil == _pfil)
|
|
{
|
|
_fError = fTrue;
|
|
return;
|
|
}
|
|
}
|
|
|
|
_fError |= !_pfil->FWriteRgbSeq(psz, CchSz(psz) * size(achar), &_fpCur);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return whether there has been an error writing to this message sink.
|
|
***************************************************************************/
|
|
bool MSFIL::FError(void)
|
|
{
|
|
AssertThis(0);
|
|
return _fError;
|
|
}
|
|
|