text: 
  /***
  *dballoc.cpp
  *
  *  Copyright (C) 1992-93, Microsoft Corporation.  All Rights Reserved.
  *
  *Purpose:
  *  This file contains a debug implementation of the IMalloc interface.
  *
  *  This implementation is basically a simple wrapping of the C runtime,
  *  with additional work to detect memory leakage, and memory overwrite.
  *
  *  Leakage is detected by tracking each allocation in an address
  *  instance table, and then checking to see if the table is empty
  *  when the last reference to the allocator is released.
  *
  *  Memory overwrite is detected by placing a signature at the end
  *  of every allocated block, and checking to make sure the signature
  *  is unchanged when the block is freed.
  *
  *  This implementation also has additional param validation code, as
  *  well as additional check make sure that instances that are passed
  *  to Free() were actually allocated by the corresponding instance
  *  of the allocator.
  *
  *
  *  Creating an instance of this debug allocator that uses the default
  *  output interface would look like the following,
  *
  *
  *  BOOL init_application_instance()
  *  {
  *    HRESULT hresult;
  *    IMalloc FAR* pmalloc;
  *
  *    pmalloc = NULL;
  *
  *    if((hresult = OleStdCreateDbAlloc(0,&pmalloc))!=NOERROR)
  *      goto LReturn;
  *
  *    hresult = OleInitialize(pmalloc);
  *
  *    // release pmalloc to let OLE hold the only ref to the it. later
  *    // when OleUnitialize is called, memory leaks will be reported.
  *    if(pmalloc != NULL)
  *      pmalloc->Release();
  *
  *  LReturn:
  *
  *    return (hresult == NOERROR) ? TRUE : FALSE;
  *  }
  *
  *
  *  CONSIDER: could add an option to force error generation, something
  *   like DBALLOC_ERRORGEN
  *
  *  CONSIDER: add support for heap-checking. say for example,
  *   DBALLOC_HEAPCHECK would do a heapcheck every free? every 'n'
  *   calls to free? ...
  *
  *
  *Implementation Notes:
  *
  *  The method IMalloc::DidAlloc() is allowed to always return
  *  "Dont Know" (-1).  This method is called by Ole, and they take
  *  some appropriate action when they get this answer.
  
  The debugging allocator has the option to catch bugs where code is writing off
  the end of allocated memory.  This is implemented by using NT's virtual memory
  services.  To switch on this option, use
  
  #define DBALLOC_POWERDEBUG
  
  Note it will ONLY WORK ON NT.  This option consumes a very large amount of
  memory.  Basically, for every allocation, it cooks the allocation so that the
  end of the allocation will fall on a page boundary.  An extra page of memory
  is allocated after the requested memory.  The protection bits for this page
  are altered so that it is an error to read or write to it.  Any incident of
  writing past the end of allocated memory is trapped at the point of the error.
  
  This consumes a great deal of memory because at least two pages must be
  allocated for each allocation.
  *
  *****************************************************************************/
  
  
  // Note: this file is designed to be stand-alone; it includes a
  // carefully chosen, minimal set of headers.
  //
  // For conditional compilation we use the ole2 conventions,
  //    _MAC      = mac
  //    WIN32     = Win32 (NT really)
  //    <nothing> = defaults to Win16
  
  
  // REVIEW: the following needs to modified to handle _MAC
  #define STRICT
  #ifndef INC_OLE2
     #define INC_OLE2
  #endif
  
  #include <windows.h>
  
  #include "ole2.h"
  
  #if defined( __TURBOC__)
  #define __STDC__ (1)
  #endif
  
  #define WINDLL  1           // make far pointer version of stdargs.h
  #include <stdarg.h>
  
  #if defined( __TURBOC__)
  #undef __STDC__
  #endif
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <malloc.h>
  #include <string.h>
  #include <limits.h>
  
  #include "dballoc.h"
  
  
  #define DIM(X) (sizeof(X)/sizeof((X)[0]))
  
  #define UNREACHED 0
  
  #if defined(WIN32)
  # define MEMCMP(PV1, PV2, CB)	memcmp((PV1), (PV2), (CB))
  # define MEMCPY(PV1, PV2, CB)	memcpy((PV1), (PV2), (CB))
  # define MEMSET(PV,  VAL, CB)	memset((PV),  (VAL), (CB))
  # define MALLOC(CB)		malloc(CB)
  # define REALLOC(PV, CB)	realloc((PV), (CB))
  # define FREE(PV)		free(PV)
  
  #ifndef WIN32
  # define HEAPMIN()		_heapmin()
  #else
  # define HEAPMIN()
  #endif
  
  #elif defined(_MAC)
  # define MEMCMP(PV1, PV2)	ERROR -- NYI
  # define MEMCPY(PV1, PV2, CB)	ERROR -- NYI
  # define MEMSET(PV,  VAL, CB)	ERROR -- NYI
  # define MALLOC(CB)		ERROR -- NYI
  # define REALLOC(PV, CB)	ERROR -- NYI
  # define FREE(PV)		ERROR -- NYI
  # define HEAPMIN()		ERROR -- NYI
  #else
  # define MEMCMP(PV1, PV2, CB)	_fmemcmp((PV1), (PV2), (CB))
  # define MEMCPY(PV1, PV2, CB)	_fmemcpy((PV1), (PV2), (CB))
  # define MEMSET(PV,  VAL, CB)	_fmemset((PV),  (VAL), (CB))
  # define MALLOC(CB)		_fmalloc(CB)
  # define REALLOC(PV, CB)	_frealloc(PV, CB)
  # define FREE(PV)		_ffree(PV)
  # define HEAPMIN()		_fheapmin()
  #endif
  
  #if defined( __TURBOC__ )
  #define classmodel _huge
  #else
  #define classmodel FAR
  #endif
  
  class classmodel CStdDbOutput : public IDbOutput {
  public:
      static IDbOutput FAR* Create();
  
      // IUnknown methods
  
      STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
      STDMETHOD_(ULONG, AddRef)(void);
      STDMETHOD_(ULONG, Release)(void);
  
  
      // IDbOutput methods
  
      STDMETHOD_(void, Printf)(TCHAR FAR* szFmt, ...);
  
      STDMETHOD_(void, Assertion)(
        BOOL cond,
        TCHAR FAR* szExpr,
        TCHAR FAR* szFile,
        UINT uLine,
        TCHAR FAR* szMsg);
  
  
      void FAR* operator new(size_t cb){
        return MALLOC(cb);
      }
      void operator delete(void FAR* pv){
        FREE(pv);
      }
  
      CStdDbOutput(){
        m_refs = 0;
      }
  
  private:
      ULONG m_refs;
  
      TCHAR m_rgch[128]; // buffer for output formatting
  };
  
  
  //---------------------------------------------------------------------
  //                implementation of the debug allocator
  //---------------------------------------------------------------------
  
  class FAR CAddrNode
  {
  public:
      void FAR*      m_pv;	// instance
      SIZE_T	   m_cb;	// size of allocation in BYTES
      SIZE_T         m_nAlloc;	// the allocation pass count
      CAddrNode FAR* m_next;
  
      void FAR* operator new(size_t cb){
        return MALLOC(cb);
      }
      void operator delete(void FAR* pv){
        FREE(pv);
      }
  };
  
  
  class classmodel CDbAlloc : public IMalloc
  {
  public:
      static HRESULT Create(
        ULONG options, IDbOutput FAR* pdbout, IMalloc FAR* FAR* ppmalloc);
  
      // IUnknown methods
  
      STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
      STDMETHOD_(ULONG, AddRef)(void);
      STDMETHOD_(ULONG, Release)(void);
  
      // IMalloc methods
  
      STDMETHOD_(void FAR*, Alloc)(SIZE_T cb);
      STDMETHOD_(void FAR*, Realloc)(void FAR* pv, SIZE_T cb);
      STDMETHOD_(void, Free)(void FAR* pv);
      STDMETHOD_(SIZE_T, GetSize)(void FAR* pv);
      STDMETHOD_(int, DidAlloc)(void FAR* pv);
      STDMETHOD_(void, HeapMinimize)(void);
  
  
      void FAR* operator new(size_t cb){
        return MALLOC(cb);
      }
      void operator delete(void FAR* pv){
        FREE(pv);
      }
  
      CDbAlloc(){
        m_refs = 1;
        m_pdbout = NULL;
        m_cAllocCalls = 0;
        m_nBreakAtNthAlloc = 0;
        m_nBreakAtAllocSize = 0;
        MEMSET(m_rganode, 0, sizeof(m_rganode));
  #ifdef DBALLOC_POWERDEBUG
  	{
  		SYSTEM_INFO si;
  
  		GetSystemInfo(&si);
  		m_virtPgSz = si.dwPageSize;
  	}
  #endif // DBALLOC_POWERDEBUG
      }
  
  private:
  
      ULONG m_refs;
      ULONG m_cAllocCalls;		// total count of allocation calls
      ULONG m_nBreakAtNthAlloc;   // allocation number to break to debugger
                                  //  this value should be set typically in the
                                  //  debugger.
      ULONG m_nBreakAtAllocSize;  // allocation size to break to debugger
                                  //  this value should be set typically in the
                                  //  debugger.
      IDbOutput FAR* m_pdbout;		// output interface
      CAddrNode FAR* m_rganode[64];	// address instance table
  
  
      // instance table methods
  
      BOOL IsEmpty(void);
  
      void AddInst(void FAR* pv, ULONG nAlloc, SIZE_T cb);
      void DelInst(void FAR* pv);
      CAddrNode FAR* GetInst(void FAR* pv);
  
      void DumpInst(CAddrNode FAR* pn);
      void DumpInstTable(void);
  
      inline UINT HashInst(void FAR* pv) const {
        return ((UINT)((ULONG)pv >> 4)) % DIM(m_rganode);
  
      }
  
      // output method(s)
  
      inline void Assertion(
        BOOL cond,
        TCHAR FAR* szExpr,
        TCHAR FAR* szFile,
        UINT uLine,
        TCHAR FAR* szMsg)
      {
        m_pdbout->Assertion(cond, szExpr, szFile, uLine, szMsg);
      }
  
      #define ASSERT(X) Assertion(X, TEXT(#X), TEXT(__FILE__), __LINE__, NULL)
  
      #define ASSERTSZ(X, SZ) Assertion(X, TEXT(#X), TEXT(__FILE__), __LINE__, SZ)
  
      static const BYTE m_rgchSig[];
  
  #ifdef DBALLOC_POWERDEBUG
  	size_t m_virtPgSz;
  #endif // DBALLOC_POWERDEBUG
  };
  
  const BYTE CDbAlloc::m_rgchSig[] = { 0xDE, 0xAD, 0xBE, 0xEF };
  
  
  /***
  *HRESULT OleStdCreateDbAlloc(ULONG reserved, IMalloc** ppmalloc)
  * Purpose:
  *  Create an instance of CDbAlloc -- a debug implementation
  *  of IMalloc.
  *
  * Parameters:
  *   ULONG reserved              - reserved for future use. must be 0.
  *   IMalloc FAR* FAR* ppmalloc  - (OUT) pointer to an IMalloc interface
  *                                   of new debug allocator object
  * Returns:
  *   HRESULT
  *       NOERROR         - if no error.
  *       E_OUTOFMEMORY   - allocation failed.
  *
  ***********************************************************************/
  STDAPI OleStdCreateDbAlloc(ULONG reserved,IMalloc FAR* FAR* ppmalloc)
  {
      return CDbAlloc::Create(reserved, NULL, ppmalloc);
  }
  
  
  HRESULT
  CDbAlloc::Create(
      ULONG options,
      IDbOutput FAR* pdbout,
      IMalloc FAR* FAR* ppmalloc)
  {
      HRESULT hresult;
      CDbAlloc FAR* pmalloc;
  
  
      // default the instance of IDbOutput if the user didn't supply one
      if(pdbout == NULL && ((pdbout = CStdDbOutput::Create()) == NULL)){
        hresult = ResultFromScode(E_OUTOFMEMORY);
        goto LError0;
      }
  
      pdbout->AddRef();
  
      if((pmalloc = new FAR CDbAlloc()) == NULL){
        hresult = ResultFromScode(E_OUTOFMEMORY);
        goto LError1;
      }
  
      pmalloc->m_pdbout = pdbout;
  
      *ppmalloc = pmalloc;
  
      return NOERROR;
  
  LError1:;
      pdbout->Release();
      pmalloc->Release();
  
  LError0:;
      return hresult;
  }
  
  STDMETHODIMP
  CDbAlloc::QueryInterface(REFIID riid, void FAR* FAR* ppv)
  {
      if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IMalloc)){
        *ppv = this;
        AddRef();
        return NOERROR;
      }
      return ResultFromScode(E_NOINTERFACE);
  }
  
  STDMETHODIMP_(ULONG)
  CDbAlloc::AddRef()
  {
      return ++m_refs;
  }
  
  STDMETHODIMP_(ULONG)
  CDbAlloc::Release()
  {
      if(--m_refs == 0){
  
        // check for memory leakage
        if(IsEmpty()){
            m_pdbout->Printf(TEXT("No Memory Leaks.\n"));
        }else{
            m_pdbout->Printf(TEXT("Memory Leak Detected,\n"));
            DumpInstTable();
        }
  
        m_pdbout->Release();
        delete this;
        return 0;
      }
      return m_refs;
  }
  
  STDMETHODIMP_(void FAR*)
  CDbAlloc::Alloc(SIZE_T cb)
  {
      size_t size;
      void FAR* pv;
  
      ++m_cAllocCalls;
  
      if (m_nBreakAtNthAlloc && m_cAllocCalls == m_nBreakAtNthAlloc) {
          ASSERTSZ(FALSE, TEXT("DBALLOC: NthAlloc Break target reached\r\n"));
      } else if (m_nBreakAtAllocSize && cb == m_nBreakAtAllocSize) {
          ASSERTSZ(FALSE, TEXT("DBALLOC: AllocSize Break target reached\r\n"));
      }
  
  #ifndef DBALLOC_POWERDEBUG
      // REVIEW: need to add support for huge allocations (on win16)
      if((cb + sizeof(m_rgchSig)) > UINT_MAX)
        return NULL;
  
      size = (size_t)cb;
  
      if((pv = MALLOC(size + sizeof(m_rgchSig))) == NULL)
        return NULL;
  
      // set allocated block to some non-zero value
      MEMSET(pv, -1, size);
  
      // put signature at end of allocated block
      MEMCPY(((char FAR*)pv) + size, m_rgchSig, sizeof(m_rgchSig));
  
      AddInst(pv, m_cAllocCalls, size);
  #else
  	// for each allocate, allocate the amount of memory required, and
  	// one more page beyond that.  We will change the protection bits of
  	// the trailing page so that we get an access violation if someone
  	// writes beyond the end of their allocated memory
  	{
  		size_t allocpgs; // number of pages to allocate
  		DWORD dwOldProt; // previous protection of the last page
  		void *plastpg; // points to the beginning of the last page
  
  		// allocate at least one page for the allocation, and
  		// one to go after it
  		allocpgs = (cb + 2*m_virtPgSz) / m_virtPgSz;
  		pv = VirtualAlloc(NULL, allocpgs*m_virtPgSz,
  				MEM_COMMIT, PAGE_READWRITE);
  
  		// change the protection of the last page
  		plastpg = (void *)(((BYTE *)pv)+m_virtPgSz*(allocpgs-1));
  		VirtualProtect(plastpg, m_virtPgSz, PAGE_NOACCESS, &dwOldProt);
  
  		// figure out what pointer to return to the user
  		pv = (void *)(((BYTE *)plastpg)-cb);
  
  		// record the allocation
  		AddInst(pv, m_cAllocCalls, cb);
  	}
  #endif // DBALLOC_POWERDEBUG
  
      return pv;
  }
  
  STDMETHODIMP_(void FAR*)
  CDbAlloc::Realloc(void FAR* pv, SIZE_T cb)
  {
      size_t size;
      CAddrNode *pcan;
  
  #ifndef DBALLOC_POWERDEBUG
      // REVIEW: need to add support for huge realloc
      if((cb + sizeof(m_rgchSig)) > UINT_MAX)
        return NULL;
  #endif // DBALLOC_POWERDEBUG
  
      if(pv == NULL){
        return Alloc(cb);
      }
  
      ++m_cAllocCalls;
  
      ASSERT((pcan = GetInst(pv)) != NULL);
  #ifndef DBALLOC_POWERDEBUG
  
      DelInst(pv);
  
      if(cb == 0){
        Free(pv);
        return NULL;
      }
  
      size = (size_t)cb;
  
      if((pv = REALLOC(pv, size + sizeof(m_rgchSig))) == NULL)
        return NULL;
  
      // put signature at end of allocated block
      MEMCPY(((char FAR*)pv) + size, m_rgchSig, sizeof(m_rgchSig));
  
      AddInst(pv, m_cAllocCalls, size);
  #else
  	{
  		void *pnew;
  		DWORD dwOldProt;
  
  		// allocate new memory
  		pnew = Alloc(cb);
  
  		// copy in the previous material
  		memcpy(pnew, pcan->m_pv, pcan->m_cb);
  
  		// protect the old memory
  		VirtualProtect(pcan->m_pv, pcan->m_cb, PAGE_NOACCESS,
  				&dwOldProt);
  
  		DelInst(pv);
  		AddInst(pv, m_cAllocCalls, cb);
  		pv = pnew;
  	}
  #endif // DBALLOC_POWERDEBUG
  
      return pv;
  }
  
  STDMETHODIMP_(void)
  CDbAlloc::Free(void FAR* pv)
  {
      if (pv == NULL)
      {
          // Free of NULL is a NO-OP
          return;
      }
  
      CAddrNode FAR* pn;
      static TCHAR szSigMsg[] = TEXT("Signature Check Failed");
  
      pn = GetInst(pv);
  
      // check for attempt to free an instance we didnt allocate
      if(pn == NULL){
        ASSERTSZ(FALSE, TEXT("pointer freed by wrong allocator"));
        return;
      }
  
  #ifndef DBALLOC_POWERDEBUG
      // verify the signature
      if(MEMCMP(((char FAR*)pv) + pn->m_cb, m_rgchSig, sizeof(m_rgchSig)) != 0){
        m_pdbout->Printf(szSigMsg); m_pdbout->Printf(TEXT("\n"));
        DumpInst(GetInst(pv));
        ASSERTSZ(FALSE, szSigMsg);
      }
  
      // stomp on the contents of the block
      MEMSET(pv, 0xCC, ((size_t)pn->m_cb + sizeof(m_rgchSig)));
  
      DelInst(pv);
      FREE(pv);
  #else
  	{
  		DWORD dwOldProt;
  
  		// make the block inaccessible
  		VirtualProtect(pv, pn->m_cb, PAGE_NOACCESS, &dwOldProt);
  		DelInst(pv);
  	}
  #endif // DBALLOC_POWERDEBUG
  }
  
  
  STDMETHODIMP_(SIZE_T)
  CDbAlloc::GetSize(void FAR* pv)
  {
      CAddrNode FAR* pn;
  
      if (pv == NULL)
      {
          // GetSize is supposed to return a -1 when NULL is passed in.
          return (SIZE_T) -1;
      }
  
      pn = GetInst(pv);
  
      if (pn == NULL) {
          ASSERT(pn != NULL);
          return 0;
      }
  
      return pn->m_cb;
  }
  
  
  /***
  *PUBLIC HRESULT CDbAlloc::DidAlloc
  *Purpose:
  *  Answer if the given address belongs to a block allocated by
  *  this allocator.
  *
  *Entry:
  *  pv = the instance to lookup
  *
  *Exit:
  *  return value = int
  *    1 - did alloc
  *    0 - did *not* alloc
  *   -1 - dont know (according to the ole2 spec it is always legal
  *        for the allocator to answer "dont know")
  *
  ***********************************************************************/
  STDMETHODIMP_(int)
  CDbAlloc::DidAlloc(void FAR* pv)
  {
      return -1; // answer "I dont know"
  }
  
  
  STDMETHODIMP_(void)
  CDbAlloc::HeapMinimize()
  {
      HEAPMIN();
  }
  
  
  //---------------------------------------------------------------------
  //                      instance table methods
  //---------------------------------------------------------------------
  
  /***
  *PRIVATE CDbAlloc::AddInst
  *Purpose:
  *  Add the given instance to the address instance table.
  *
  *Entry:
  *  pv = the instance to add
  *  nAlloc = the allocation passcount of this instance
  *
  *Exit:
  *  None
  *
  ***********************************************************************/
  void
  CDbAlloc::AddInst(void FAR* pv, ULONG nAlloc, SIZE_T cb)
  {
      UINT hash;
      CAddrNode FAR* pn;
  
  
      ASSERT(pv != NULL);
  
      pn = (CAddrNode FAR*)new FAR CAddrNode();
  
      if (pn == NULL) {
          ASSERT(pn != NULL);
          return;
      }
  
      pn->m_pv = pv;
      pn->m_cb = cb;
      pn->m_nAlloc = nAlloc;
  
      hash = HashInst(pv);
      pn->m_next = m_rganode[hash];
      m_rganode[hash] = pn;
  }
  
  
  /***
  *UNDONE
  *Purpose:
  *  Remove the given instance from the address instance table.
  *
  *Entry:
  *  pv = the instance to remove
  *
  *Exit:
  *  None
  *
  ***********************************************************************/
  void
  CDbAlloc::DelInst(void FAR* pv)
  {
      CAddrNode FAR* FAR* ppn, FAR* pnDead;
  
      for(ppn = &m_rganode[HashInst(pv)]; *ppn != NULL; ppn = &(*ppn)->m_next){
        if((*ppn)->m_pv == pv){
  	pnDead = *ppn;
  	*ppn = (*ppn)->m_next;
  	delete pnDead;
  	// make sure it doesnt somehow appear twice
  	ASSERT(GetInst(pv) == NULL);
  	return;
        }
      }
  
      // didnt find the instance
      ASSERT(UNREACHED);
  }
  
  
  CAddrNode FAR*
  CDbAlloc::GetInst(void FAR* pv)
  {
      CAddrNode FAR* pn;
  
      for(pn = m_rganode[HashInst(pv)]; pn != NULL; pn = pn->m_next){
        if(pn->m_pv == pv)
          return pn;
      }
      return NULL;
  }
  
  
  void
  CDbAlloc::DumpInst(CAddrNode FAR* pn)
  {
      if (pn == NULL)
          return;
  
      m_pdbout->Printf(TEXT("[0x%lx]  nAlloc=%ld  size=%ld\n"),
        pn->m_pv, pn->m_nAlloc, GetSize(pn->m_pv));
  }
  
  
  /***
  *PRIVATE BOOL IsEmpty
  *Purpose:
  *  Answer if the address instance table is empty.
  *
  *Entry:
  *  None
  *
  *Exit:
  *  return value = BOOL, TRUE if empty, FALSE otherwise
  *
  ***********************************************************************/
  BOOL
  CDbAlloc::IsEmpty()
  {
      UINT u;
  
      for(u = 0; u < DIM(m_rganode); ++u){
        if(m_rganode[u] != NULL)
  	return FALSE;
      }
  
      return TRUE;
  }
  
  
  /***
  *PRIVATE CDbAlloc::Dump
  *Purpose:
  *  Print the current contents of the address instance table,
  *
  *Entry:
  *  None
  *
  *Exit:
  *  None
  *
  ***********************************************************************/
  void
  CDbAlloc::DumpInstTable()
  {
      UINT u;
      CAddrNode FAR* pn;
  
      for(u = 0; u < DIM(m_rganode); ++u){
        for(pn = m_rganode[u]; pn != NULL; pn = pn->m_next){
            DumpInst(pn);
        }
      }
  }
  
  
  //---------------------------------------------------------------------
  //                implementation of CStdDbOutput
  //---------------------------------------------------------------------
  
  IDbOutput FAR*
  CStdDbOutput::Create()
  {
      return (IDbOutput FAR*)new FAR CStdDbOutput();
  }
  
  STDMETHODIMP
  CStdDbOutput::QueryInterface(REFIID riid, void FAR* FAR* ppv)
  {
      if(IsEqualIID(riid, IID_IUnknown))
      {
        *ppv = this;
        AddRef();
        return NOERROR;
      }
      return ResultFromScode(E_NOINTERFACE);
  }
  
  STDMETHODIMP_(ULONG)
  CStdDbOutput::AddRef()
  {
      return ++m_refs;
  }
  
  STDMETHODIMP_(ULONG)
  CStdDbOutput::Release()
  {
      if(--m_refs == 0){
        delete this;
        return 0;
      }
      return m_refs;
  }
  
  STDMETHODIMP_(void)
  CStdDbOutput::Printf(TCHAR FAR* lpszFmt, ...)
  {
      va_list args;
      TCHAR szBuf[256];
  #if defined( OBSOLETE )
      TCHAR *pn, FAR* pf;
  static TCHAR rgchFmtBuf[128];
  static TCHAR rgchOutputBuf[128];
  
      // copy the 'far' format string to a near buffer so we can use
      // a medium model vsprintf, which only supports near data pointers.
      //
      pn = rgchFmtBuf, pf=szFmt;
      while(*pf != TEXT('\0'))
        *pn++ = *pf++;
      *pn = TEXT('\0');
  #endif
  
      va_start(args, lpszFmt);
  
  //    wvsprintf(rgchOutputBuf, rgchFmtBuf, args);
      wvsprintf(szBuf, lpszFmt, args);
  
      OutputDebugString(szBuf);
  }
  
  STDMETHODIMP_(void)
  CStdDbOutput::Assertion(
      BOOL cond,
      TCHAR FAR* szExpr,
      TCHAR FAR* szFile,
      UINT uLine,
      TCHAR FAR* szMsg)
  {
      if(cond)
        return;
  
  #ifdef _DEBUG
      // following is from compobj.dll (ole2)
      #ifdef UNICODE
         #ifndef NOASSERT
         FnAssert(szExpr, szMsg, szFile, uLine);
         #endif
      #else
         // we need to talk to comobj in UNICODE even though we are not defined
         // as UNICODE
         {
            WCHAR wszExpr[255], wszMsg[255], wszFile[255];
            mbstowcs(wszExpr, szExpr, 255);
            mbstowcs(wszMsg, szMsg, 255);
            mbstowcs(wszFile, szFile, 255);
            #ifndef NOASSERT
            FnAssert(wszExpr, wszMsg, wszFile, uLine);
            #endif
         }
      #endif
  #else
      // REVIEW: should be able to do something better that this...
      DebugBreak();
  #endif
  }
  