/* OdmNative100.cpp 0.27             UTF-8                   dh:2007-01-17
 *
 *             OdmNative100 CLASSES AND INTERFACE IMPLEMENTATIONS
 *             **************************************************
 *
 * This is the implementation of the OdmNative100 classes and the COM
 * objects they deliver.
 *
 *
 *                         Copyright © 2006 NuovoDoc
 *
 *              This software is released under the Open
 *              Document Management API License 1.0, an open-
 *              source software license modeled on the BSD
 *              License template.
 *
 *              A copy of the license should accompany the
 *              source code that includes this file.  If the
 *              license has has been separated from the code,
 *              obtain a copy at <http://DMware.info/license/>.
 */

#define OdmNative100_C_Version_ "0.27 " __FILE__ " " __TIMESTAMP__

    /* A consistent format for building descriptive texts that
       identify specific implementations.  See OdmNative100.hpp
       and OdmApplication::interfaceImplementation, below.
       */

#define odmAppVERSION 100
    /* ODMA-aware applications that rely on OdmNative100 require
       ODMA 1.0 or a compatible successor for operation.  This is
       the level supported by OdmNative100.

       FIXME: There should be sets of these defined in the Odma32api100.h
              headers and the application should pick the one it wants by
              making a definition.  This definition should influence what
              API is made available, perhaps along with other options.
       */

    /* FIXME: This pre-processor logic is required by the use of the Windows
          API function GetConsoleWindow in the OdmNative100 helper function
          tryConsoleWindow. (See OdmApplication::hasDefaultDMS and the
          function definitions that precede it.)

          When tryConsoleWindow uses dynamic discovery, the following
          definitions can be removed.

          FIXME: The code for establishing the parent window for dialog
                 boxes and communication in the OdmRegisterApp function
                 needs to be factored out into a separate function, with
                 a similar factoring in the odmjni100.cpp code.
       */

#ifdef _WIN32_WINNT
#if _WIN32_WINNT < 0x0500
#error This program requires compilation for Windows 2000 Pro or later.
#endif
#else
#define _WIN32_WINNT 0x0500
#endif

#ifdef WINVER
#if WINVER < 0x0500
#error This program requires compilation for Windows 2000 Pro or later.
#endif
#else
#define WINVER 0x0500
#endif


#include <windows.h>

#include <new>
    /* For std::nothrow, preventing exceptions being thrown from
       OdmBindNative100.
       */

#include "odma32types100.h"
    /* For data types and constants applicable to ODMA 1.0 access and
       important for Odma32api.h too.
       */

#include "Odma32api100.h"
    /* For entry points into the ODMA Connection Manager for ODMA 1.0
       functions.
       */

#include "OdmNative100.hpp"
    /* For the public interfaces of OdmNative100. */

#include "OdmNative100i.hpp"
    /* For private interfaces among OdmNative100 components. */

#include "OdmBindWorking100.hpp"
    /* For the factory method used in implementation of
       makeWorkingDocument.
       */

#include "OdmBindPending100.hpp"
    /* For the factory method used in implementation of
       makePendingDocument.
       */


/*             ODMAPPLICATION CLASS DEFINITION AND IMPLEMENTATION
               ************************************************** */

class OdmApplication : public IodmApplication100i
{ /* Class Implemented Internal to OdmNative100.cpp
     This class is defined privately and used for the
     current implementation of the IodmApplication100
     and IodmApplication100i interfaces.
     */

  private:
    volatile LONG refCount;
        /* Used to count the number of outstanding references to
           interfaces of the object */

    char appId[ODM_APPID_MAX];
        /* The Application ID for the application that this object
           represents to ODMA. */

    BOOL knowConMan;
        /* TRUE when it is known whether or not there is an available
           ODMA Connection Manager.  This is set so we don't keep
           trying to load the DLL every time someone asks. */

    HINSTANCE hConMan;
        /* This is the non-NULL handle for the Connection Manager
           DLL instance, if it has been successfully loaded. It is
           NULL when there is no Connection Manager available.*/

    BOOL knowDefaultDMS;
        /* TRUE when it is known whether or not there is an available
           default DMS.  This is set so that we will register our
           application with the Connection Manager at most once.
           */

    ODMHANDLE hOdmDefault;
        /* The default handle if there is one.  NULL otherwise.
               Depending on the version of Connection Manager available,
           there are ways to initiate a temporary default using a specific
           DMS ID.  This is only done to access known documents in the
           absence of a Default DMS for the application.  Such handles
           are kept separate.
           */

    ODMHANDLE hOdmWorking;
        /* The available working DMS.  If there is a Default DMS, its
           handle is also here.  If there is no Default, the first openDoc
           success will create one.

           There is never more than one handle, and it will be included
           in hOdmWorking.  This is the handle, if any, that should be
           UnRegistered during release of this class via its interface.
           */


  public:
    OdmApplication( const char pszAppId[] );
        /* The Application ID is established permanently at the
           time the ODMApplication object is constructed. */

    /* These method declarations identify those virtual methods that
       will are overloaded for instances of this class.  The methods
       are defined out-of-line with the OdmApplication::<<method>>
       definitions following the class declaration, below. */

    /*  IUnknown Methods */
    virtual HRESULT WINAPI QueryInterface(REFIID rIID, void** ppv);
    virtual ULONG WINAPI AddRef(void);
    virtual ULONG WINAPI Release(void);

    /*  IodmApplication100 Methods */
    virtual LPCSTR WINAPI interfaceImplementation(void);
    virtual BOOL WINAPI hasConMan(void);
    virtual BOOL WINAPI hasDefaultDMS(void);
    virtual ODMSTATUS WINAPI selectDocID(LPSTR pszDocID, BOOL *pFlag);
    virtual ODMSTATUS WINAPI
                openDoc(LPCSTR pszDocID, LPSTR pszDocLoc, BOOL viewMode);
    virtual HRESULT WINAPI
                makeWorkingDocument
                    (LPCSTR pszDocId, LPSTR pszDocLoc, BOOL viewMode,
                     REFIID rIID, void **ppIface);
    virtual ODMSTATUS WINAPI
                startNewDoc(LPSTR pszDocId, LPCSTR pszDocFormatName );
    virtual HRESULT WINAPI
                makePendingDocument
                    (LPCSTR pszDocId, LPCSTR pszDocLoc,
                     REFIID rIID, void **ppIface);

    /*  IodmApplication100i internal-coordination Methods */
    virtual HINSTANCE WINAPI hConManLib(void);
    virtual ODMHANDLE WINAPI hWorkingDMS(void);


    }; /* class OdmApplication */


/*                    ODMAPPLICATION100 UTILITY FUNCTIONS
                      *********************************** */


static HRESULT verifyAppId( const char  pszAppId[] )
{   /* Enforce the ODMJNI 1.0 Restrictions on Application IDs. */

    char ascii;

    if (pszAppId == NULL) return E_POINTER;



    if (pszAppId[0] == '\0') return E_INVALIDARG;
        /* empty string is not permitted */

    for (int i = 0; i < ODM_APPID_MAX; i++)
    {   /* If we don't exit by pszAppId[ODM_APPID_MAX - 1] the
           Application ID is too long.
           */

        ascii = pszAppId[i];

        if (ascii == '\0') return S_OK;
            /* Any null character after the first means we made it. */

        if (  ascii < '\x30' || ascii > '\x7A'
                                /* < '0' or > 'z' */

                || ( ascii > '\x5A' && ascii < '\x61' )
                                /* > 'Z' and < 'a' */

                || ( ascii > '\x39' && ascii < '\x41' )
                                /* > '9' and < 'A' */
              )

             break;
        }

    return E_INVALIDARG;

    } /* verifyAppId */


/*                   ODMAPPLICATION OBJECT IMPLEMENTATION
                     ************************************ */


OdmApplication::OdmApplication( const char pszAppId[] )
                            : refCount(0),
                              knowConMan(FALSE), hConMan(NULL),
                              knowDefaultDMS(FALSE), hOdmDefault(NULL),
                              hOdmWorking(NULL)
{   /* Perform the minimum initialization for the lifecycle
       of the object. */

    appId[0] = '\0';
        /* The value that signals failure of the constructor to
           OdmApplication::QueryInterface.
           */

    if (S_OK != verifyAppId(pszAppId))
         return;
            /* The constructor fails.  QueryInterface will refuse to
               return interfaces and the object will be destroyed.
               */

    char *dest = appId;
    int numLeft = sizeof(appId);

    while ( *dest++ = *pszAppId++)
          if (!--numLeft)
              {appId[0] = '\0'; return;}
        /* OK, let's save the verified Application ID, copying up to and
           including the '\0'.
           XXX: We put in a safety check on the copy and fail out as a
                simple defensive measure (probably against a maintenance
                mistake).
           */

    } /* OdmApplication::OdmApplication */



static const IID IID_IodmUnknown = IID_IodmUnknown_ ;
    /* Standard IUnknown IID uniquelly named to avoid conflicts. */

static const IID IID_IodmApplication100 = IID_IodmApplication100_ ;
    /* IodmApplication100 IID of the current header level. */

static const IID IID_IodmApplication100i = IID_IodmApplication100i_ ;
    /* IodmApplication100i IID of the current header level. */



HRESULT WINAPI
    OdmApplication::QueryInterface(REFIID rIID, void **ppIface)

{   /* Simple single-inheritance implementation. */

    if (ppIface != NULL)
         *ppIface = NULL;
    else return E_POINTER;

    if (&rIID == NULL)
         return E_POINTER;
            /* Never store into a null pointer.  Never reference
               through a null pointer.  We are not cycling these
               QueryInterfaces so hard that we are pained by this
               amount of defensive programming.

               The OdmBindApplication100 code depends on this
               code defending itself.  */

    if (appId[0] == '\0')
         return E_INVALIDARG;
            /* Never return an interface if our constructor failed.
               The signal is the presence of a null Application ID
               string value, signalling an invalid AppId parameter. */

    if (  rIID == IID_IodmApplication100
             || rIID == IID_IodmApplication100i
             || rIID == IID_IodmUnknown
          )

         *ppIface = (IodmApplication100i *) this;
                /* relying on the fact of single inheritance
                   to satisfy all requests with the same binary
                   interface.
                   */

    else return E_NOINTERFACE;

    ((IUnknown *)(*ppIface)) -> AddRef();
        /* Count the interface reference we have just given out */

    return S_OK;

    } /* OdmApplication::QueryInterface */



ULONG WINAPI
    OdmApplication::AddRef(void)

{   /* Simple reference-count operation.
       XXX: If this were to wrap around, it's not clear
         that there is anything useful to be done about it.
       */

    return InterlockedIncrement(&refCount);
        /* Maintain thread-safety even though most
           operations should all be done on a single
           GUI thread.
           */
    } /* OdmApplication::AddRef */



ULONG WINAPI
    OdmApplication::Release(void)

{   /* Simple reference-count decrease. */

    if (InterlockedDecrement(&refCount))
         return 1;
            /* Note that the object can be deleted by another
               release before this one returns.  No accesses
               to anything are assured following the decrement.
               */

    /* We are the last use standing and we must depart cleanly. */

            /* *** ANY MORE RELEASE CODE GOES HERE *** */

    /* Release any handle for the defaultDMS instance of Connection
       Manager before releasing the Connection Manager DLL. */

    if (hOdmWorking != NULL)
         { /* Release the connection manager for the one and only
              working DMS. */

           /* Get the entry point for OdmUnRegisterApp.
              This is the only place where we need this entry.
              XXX: Other uses of GetProcAddress should be cached.  But
                   this one only happens once, here in the destructor
                   process.
              */

           pfnODMUNREGISTERAPP
                pOdmUnRegisterApp
                    = (pfnODMUNREGISTERAPP)
                            GetProcAddress(hConMan, "ODMUnRegisterApp");
                            /* Entry point for ODMUnregisterApp */

           if (pOdmUnRegisterApp == NULL)
              { /* We have a default that we can't release.
                   FIXME: We are in a world of hurting and can't do
                   much at this point.  This would appear to be a
                   loggable exception, but we really don't want to
                   get into anything more that could have this
                   release fail and leave garbage in memory.
                   */
                }

           else { /* Release the working handle.
                     */

                  pOdmUnRegisterApp(hOdmWorking);

                  hOdmDefault = hOdmWorking = NULL;
                        /* superstitious tidying up. */
                  }

           }


    /* Release the Connection Manager last, if we have it */

    if(hConMan != NULL)
         { /* Release the DLL */

           if(!FreeLibrary(hConMan))
                { /* FIXME: This is definitely a loggable event.
                     There needs to be more error hardening for this situation.  Also, we don't want loader error
                     messages possibly being presented to the user.
                     */
                  }

           hConMan = NULL;
                /* more superstitious clean-up ritual.
                   It can't hurt. */

           }


    /* Con Te Partirò */

    delete this;
        /* When we are the final release, there can be no
           others and *this is ours to delete.  Afterwards,
           no references to the object fields and members
           are valid.
           */

    return 0;

    } /* OdmApplication::Release */



LPCSTR WINAPI
    OdmApplication::interfaceImplementation(void)

{   /* Identify the interface definitions followed by the implementation.
       NOTE: We identify the public interface, not the internal ones.
       */

    return "defined in " OdmNative100_H_Version_ ",\n"
           "implemented in " OdmNative100_C_Version_ ",\n"
           #if defined(_MSC_EXTENSIONS)
                " MS-extended"
           #endif
           #if defined(__cplusplus)
                " C++"
           #elif defined(__STDC__)
                " Standard C"
           #elif defined(_MSC_VER)
                " Visual C"
           #else
                " C Language"
           #endif

           #if defined(_DLL)
                " DLL"
           #endif
           #if defined(_WIN32)
                " (Win32)"
           #endif
          " compiled " __DATE__  " at " __TIME__
           ;

    } /* OdmApplication::interfaceImplementation */



BOOL WINAPI
    OdmApplication::hasConMan(void)

{   /* Tell them what we know about the Connection Manager.
       If we don't know yet, find out just once. */

    if (!knowConMan)
         { knowConMan = TRUE;
               /* However it goes, we'll know it now. */
           hConMan = LoadLibraryA("ODMA32.dll");
               /* XXX: Do not compile ODMNative100.cpp with default UNICODE.
                  See the Platform SDK Windows API Reference for details of
                  this function.
                  */
               /* FIXME: It is preferable to specify the exact path to the
                  Windows System Library and not do a general search for
                  the ConnectionManager.  This must be repaired in
                  production.
                  */
               /* FIXME: Error information can be obtained from the runtime
                  to determine why this function returns NULL when it does.
                  For now, we simply report that we don't have it.
                  */
           }

    return hConMan != NULL;

    } /* OdmApplication::hasConMan */



struct WndRec {HWND hFoundWnd; int numFoundWnd; };
    /* Structure used in hAppWnd to hold results from
       EnumThreadWindows via hWndFound, below.
       */


static BOOL CALLBACK hWndFound(HWND found, WndRec *pWndRec)

{   /* Count the number of HWNDs found for hAppWnd,
       remembering the first one.
       */

    if (pWndRec -> numFoundWnd == 0)
         (pWndRec -> hFoundWnd) = found;
    (pWndRec -> numFoundWnd)++;

    return TRUE;
        /* Keep going because we want to see how many turn up, even
           though we don't make any use of it so far. */

    }  /* hWndFound */


static HWND WINAPI tryConsoleWindow(void)

{   /* See if there is a ConsoleWindow that we can find to use as
       the Application Window for ODMA DMS modal dialog association.
       */

    return GetConsoleWindow();
            /* FIXME: This call requires compilation with special settings
                  for _WIN32_WINNT and WINVER.  This function should be
                  replaced by a dynamic-discovery procedure using
                  kernel32.dll and GetProcAddress.

                  When this is replaced, the special API-version setting
                  preprocessor code can be removed from the beginning of
                  this file.
               */

    } /* tryConsoleWindow */


static HWND WINAPI hAppWnd(void)

{   /* Determine a Window Handle for the DMS to use in creating modal
       dialogs.
       */

    WndRec found = {NULL, 0};
        /* To get the first HWND and the number of HWNDs found */

    EnumThreadWindows(  GetCurrentThreadId(),
                        (WNDENUMPROC) hWndFound,
                        (LPARAM) &found
                        );

        /* XXX: We don't know if this will always do the job in a
                non-console application and whether this works or
                not under JNI.
                */

    if (found.numFoundWnd)
         return found.hFoundWnd;
    else return tryConsoleWindow();

    } /* hAppWnd */


BOOL WINAPI
    OdmApplication::hasDefaultDMS(void)

{   /* Likewise, tell them what we know about the DefaultDMS.
       If we don't know yet, find out just once.  This is the same
       model as hasConMan() but for a different condition.
       */

    if (!knowDefaultDMS && hasConMan())

         {  /* Only with a Connection Manager is having a
               default DMS possible.
               */

            pfnODMREGISTERAPP
                pOdmRegisterApp
                    = (pfnODMREGISTERAPP)
                          GetProcAddress(hConMan, "ODMRegisterApp");

            if (pOdmRegisterApp == NULL)
                 { /* We can't find an essential entry point in the
                      ODMA Connection Manager.  This is a reportable
                      exception..
                         FIXME: We're basically crippled at this
                      point.  We have no Default DMS for sure, and
                      it is unlikely that other functions will work.
                      Basically, OdmNative is going to silently fail
                      to operate.  Technically, it is as if there is
                      no Connection Manager, but it is too late for
                      that.
                      */
                }

            else { /* Attempt to register this application and obtain
                      the handle to use for Default DMS operation.
                      */

                   ODMSTATUS
                       odmStatus
                           = (pOdmRegisterApp)
                                (  &hOdmDefault,   /* ODMHANDLE dest. */
                                   odmAppVERSION,  /* Our dependency. */
                                   appId,          /* Our Application ID */
                                   hAppWnd(),      /* hWnd of OdmApp */
                                   NULL            /* reserved */
                                   );

                       /* XXX: Dialog-creation requests will typically
                               operate simply as if there is no associated
                               client window when hAppWnd() is NULL here.
                          */

                   if (odmStatus != ODM_SUCCESS)
                        hOdmDefault = NULL;
                            /* defensive-programming maneuver */
                   else hOdmWorking = hOdmDefault;
                            /* An established Default DMS handle is
                               automatically the Working DMS for non-
                               default operations too.
                               */

                   }

            }

    knowDefaultDMS = TRUE;
                /* However it went, we now know */

    return (hOdmDefault != NULL);

    } /* OdmApplication::hasDefaultDMS */



ODMSTATUS WINAPI
    OdmApplication::selectDocID(LPSTR pszDocID, BOOL *pViewMode)

{   /* This step is used to select an ID.  The openKnownDocument or
       viewKnownDocument methods are then available for going the
       rest of the way once we know whether we have an ID to use and
       what the access recommendation is.
       */

    if (pszDocID == NULL || pViewMode == NULL)
         return ODM_E_FAIL;

    pszDocID[0] = '\0';
            /* Ensure some consistent result, no matter what. */

    if (!hasDefaultDMS())
         return ODM_E_NODMS;

    pfnODMSELECTDOC
        pOdmSelectDoc
            = (pfnODMSELECTDOC)
                  GetProcAddress(hConMan, "ODMSelectDoc");
        /* The entry point for OdmSelectDoc, if any.
           FIXME: If we thought this was going to happen a lot
              in a typical implementation, we should obtain this
              ProcAddress inside of the hasDefaultDMS success case.
              We'll worry about GetProcAddress optimizations after
              we have the functions all working.
           */

    if (pOdmSelectDoc == NULL)
         return ODM_E_FAIL;

    DWORD selectFlags = 0;
        /* We have no special requirements. */

    ODMSTATUS odmStatus =
                      (pOdmSelectDoc)(hOdmDefault,
                                      pszDocID,
                                      &selectFlags);

    if (odmStatus == ODM_SUCCESS)
         {   /* Normalize the ViewMode requirement See the description
                of recommended ODMSelectDoc flag treatment in
                Odma32api100.h
                */

             selectFlags &= (ODM_MODIFYMODE | ODM_VIEWMODE);
                    /* Ignore bits having no meaning for ODMA 1.0 */

             *pViewMode
                 = (  selectFlags
                           ? selectFlags
                               != ODM_MODIFYMODE
                           : FALSE
                       );

             return ODM_SUCCESS;
             }

    switch (odmStatus)
    {   /* Consolidate the various result codes. */
        case ODM_E_OTHERAPP:
            return ODM_E_CANCEL;
        case ODM_E_CANCEL:
        case ODM_E_APPSELECT:
            return odmStatus;
        }

    return ODM_E_FAIL;

    } /* OdmApplication::selectDocID */



ODMSTATUS WINAPI
    OdmApplication::openDoc(  LPCSTR pszDocId,
                               LPSTR pszDocLoc,
                                BOOL viewMode
                              )

{   /* Implement a streamlined version of the ODMA ODMOpenDoc function.

       FIXME: This is a helper function.  With re-abstraction of the
              OdmNative interfaces, this method should submerge into
              OdmNative100i.  There it is a helper function for the
              different uses of openDoc.  With care, we might be able
              to make it private to the class and not expose it in an
              interface at all.
       */

    if (pszDocId == NULL || pszDocLoc == NULL) return ODM_E_FAIL;

        /* FIXME: This code assumes that the Document ID and the
                  Document location are well-formed.  We need to filter
                  to ensure that we never pass bogus material to a DMS.
                  */

    if (hOdmWorking == NULL)
         hasDefaultDMS();
            /* If we don't have hOdmWorking, see if we can use the
               Default DMS for it. */
    if (hOdmWorking == NULL) return ODM_E_FAIL;
            /* If there's no default to use, give up.
               FIXME: To cover the one odd case, we will need to fish the
                      DMS ID out of the DocId and see if we can make that
                      DMS the default DMS, and then get a DMS connection.
                      If we can do all of that, we can use the resulting
                      handle for hOdmWorking from now on. Otherwise, we
                      fail again and try again another time.
               */

    /* At this point there is an hOdmWorking, so there must be a valid
       hConMan too. */

    pfnODMOPENDOC
        pOdmOpenDoc
            = (pfnODMOPENDOC)
                  GetProcAddress(hConMan, "ODMOpenDoc");

                  /* FIXME: To avoid repetitive GetProcAddress operations,
                            whatever cost they add, we should cache the
                            pointers on demand in the way that is done for
                            handles and other pointers in OdmNative and
                            OdmJni operations.

                            This could also centralize the problem of
                            having GetProcAddress failures.
                            */

    if (pOdmOpenDoc == NULL)
         return ODM_E_FAIL;

    DWORD openFlags = (viewMode ? ODM_VIEWMODE : ODM_MODIFYMODE);
        /* We don't use silent mode, although a dialog is unlikely
           except when there are problems.
           */

    pszDocLoc[0] = '\0';
        /* Defensive introduction of null document for default */

    ODMSTATUS odmStatus
        = pOdmOpenDoc( hOdmWorking, openFlags, pszDocId, pszDocLoc );

    switch (odmStatus)
    {   /* Reduce to only those result codes that are defined for ODMA 1.0
           and that are potentially meaningful in the application. */

        case ODM_SUCCESS:
        case ODM_E_ACCESS:  /* The user has no rights to access the
                               document in the requested mode.  Try
                               viewMode TRUE if not done already.
                               */
        case ODM_E_INUSE:   /* The document is in use by someone else
                               at the moment.  They could be advised to
                               try later.
                               */
        case ODM_E_DOCID:   /* The Document ID is ill-formed or the document
                               does not exist, can't be found in the
                               local ODMA configuration.
                               */
                return odmStatus;
        }

    return ODM_E_FAIL;
        /* Result code for all other cases including ODM_E_FAIL */

    } /* OdmApplication::openDoc */


HRESULT WINAPI
    OdmApplication::makeWorkingDocument
                        (  LPCSTR pszDocId,
                            LPSTR pszDocLoc,
                             BOOL viewMode,
                           REFIID rIID,
                             void **ppIface
                           )

{   /* Manufacture a Working Document instance and deliver an interface
       for it.  We let the factory function do all of the work.

       FIXME: It looks like this should be an OdmApplication100i helper
              function too, at some point.  Probably all functions that
              return ODMSTATUS or that take DocLoc parameters will end
              up being helper functions or private to OdmApplication or,
              best of all, static in OdmNative100.cpp.
       */

    return OdmBindWorking100
                (this, pszDocId, pszDocLoc, viewMode, rIID, ppIface);

    } /* OdmApplication::makeWorkingDocument */



ODMSTATUS WINAPI
    OdmApplication::startNewDoc
                        (   LPSTR pszDocId,
                           LPCSTR pszDocFormatName )

{   /* Use a null implementation until we implement acceptNewDocument
       behavior.

       FIXME: This must be a helper function in a re-abstracted OdmNative.
              Also, we must filter pszDocFormatName or have that be a
              precondition on the entry to startNewDoc.
       */


    if (  pszDocId == NULL
          || pszDocFormatName == NULL
          )
         return ODM_E_FAIL;
            /* Defend ourself from going off the rails, at least.

               FIXME: We should at least make sure there is a null char in
                      pszDocFormatName[] within the size limit required.
                      We have promised to never allow bogus data to pass
                      through to the ODMA API.
            */

    hasDefaultDMS();
    if (hOdmWorking == NULL)
         return ODM_E_APPSELECT;
            /* If no Default DMS, we provide the default behavior. */

    pfnODMNEWDOC pODMNewDoc
        = (pfnODMNEWDOC) GetProcAddress(hConMan, "ODMNewDoc");
        /* FIXME: This operation should be cached in OdmNative so that
                  redundant GetProcAddress operations are not performed.
                  */

    if (pODMNewDoc == NULL)
         return ODM_E_FAIL;

    char preDocId[ODM_DOCID_MAX+10] = {'\0'};
            /* Add a little extra padding for over-run protection
               FIXME: We might need stronger defenses against a defective
                      or malicious DMS
               */

    ODMSTATUS rc = pODMNewDoc(  hOdmWorking, preDocId,
                                             ODM_SILENT,
                                             pszDocFormatName,
                                             NULL
                                );

    if (rc == ODM_E_USERINT)
              rc = pODMNewDoc(  hOdmWorking, preDocId,
                                             0,
                                             pszDocFormatName,
                                             NULL
                                );
              /* XXX: In this case, one would think that the
                      provisional document ID is the one to use and we
                      should skip the SaveAs operation.  We need to find
                      out what any DMS that does this expects.
                 */


        /* Work silently if possible, but allow dialog if not */


    if (rc == ODM_SUCCESS)
    {   /* We can go to the next level and use SaveAs to obtain a
           a version that will accept the new material.  Note that
           the preDocId document is never opened and we do not
           report that ID to the application at all.
           */

        if (preDocId[ODM_DOCID_MAX-1] != '\0')
             return ODM_E_FAIL;
         /* XXX: There are more defenses called-for here.  This is
                 enough to prevent us from passing on a buffer overrun
            */

        pfnODMSAVEAS pODMSaveAs
            = (pfnODMSAVEAS) GetProcAddress(hConMan, "ODMSaveAs");
                    /* FIXME: This operation should be cached in OdmNative
                              so that redundant GetProcAddress operations
                              are not performed.
                       */

        if (pODMSaveAs == NULL)
             return ODM_E_FAIL;

        char newDocId[ODM_DOCID_MAX+10] = {'\0'};
            /* Add a little extra padding for over-run protection
               FIXME: We might need stronger defenses against a defective
                      or malicious DMS
               */

        rc = pODMSaveAs(hOdmWorking, preDocId, newDocId, pszDocFormatName,
                                     NULL, NULL
                                     );

        if (rc != ODM_SUCCESS)
             return rc;

        if (newDocId[ODM_DOCID_MAX-1] != '\0')
             return ODM_E_FAIL;
                    /* A modest level of protection even if we
                       don't return this string. */

        char *src = (newDocId[0] == '\0' ? preDocId : newDocId);

        while (*pszDocId++ = *src++) ;
           /* XXX: This transfer is safe so long as pszDocId[] is big
                   enough.  We avoid using the string library because
                   we intend to keep use in DLLs as small as possible, with
                   only essential static runtime library dependencies.
                   */

        }

    return rc;

    } /* OdmApplication::startNewDoc */


HRESULT WINAPI
    OdmApplication::makePendingDocument
                        (  LPCSTR pszDocId,
                           LPCSTR pszDocLoc,
                           REFIID rIID,
                             void **ppIface
                           )
{   /* Manufacture a Pending Document instance and deliver an interface
       for it.  We let the factory function do all of the work.

       FIXME: It looks like this should be an OdmApplication100i helper
              function too, at some point.  Probably all functions that
              return ODMSTATUS or that take DocLoc parameters will end
              up being helper functions or private to OdmApplication or,
              best of all, static in OdmNative100.cpp.
       */

    return OdmBindPending100
                (this, pszDocId, pszDocLoc, rIID, ppIface);

    } /* OdmApplication::makePendingDocument */



HINSTANCE WINAPI
    OdmApplication::hConManLib(void)

{   /* Make sure we know if there is a Connection Manager.
       Then return the handle we know for it.

       This method is provided to document objects so that they
       are able to perform GetProcAddress operations against the
       ODMA Connection Manager DLL loaded by OdmApplication.  */

    hasConMan();

    return hConMan;

    } /* OdmApplication::hConManLib */



ODMHANDLE WINAPI
    OdmApplication::hWorkingDMS(void)

{   /* See if we can provide a Working DMS handle.

       This method is provided to document objects so that they
       can pass the Default DMS handle to functions of the ODMA API.
       */

    if (hOdmWorking == NULL)
         hasDefaultDMS();
            /* If there is not one at this point, the only way we can
               obtain it is if a Default DMS has not been established
               yet.
               */

    return hOdmWorking;

    } /* OdmApplication::hWorkingDMS */






/*              OdmBindNative100: BIND THE OdmApplication OBJECT
                ************************************************ */

HRESULT WINAPI
    OdmBindNative100(  const char  pszAppId[],
                                       /* Clean Basic Latin string of
                                          the Application ID */

                           REFIID  rIID,
                                       /* The requested interface */

                             void  **ppIface
                                       /* The location to receive the
                                          Interface pointer or NULL */
                       )

{   /* Create a null-implementation for starters. */

    HRESULT rc = E_FAIL;

    OdmApplication *pOdmApp = new(std::nothrow) OdmApplication(pszAppId);

    if (pOdmApp == NULL) return E_OUTOFMEMORY;

        /* We have an OdmApplication object with refCount = 0 and with
           nothing created that a simple delete can't handle at this point.
           */

    rc = pOdmApp -> QueryInterface(rIID, ppIface);
            /* It is assumed that the IodmApplication100 interface
               defends itself and will not crash. */

    if (rc != S_OK) delete pOdmApp;
        /* If we can't produce the interface the customer wants,
           delete the OdmApplication and pass on the bad news.

           If the interface is delivered, refCount = 1 now and it is
           up to the caller to release the interface and allow the
           implementing object to clean up and delete itself.
           (See the code for OdmApplication::Release.)
           */

    return rc;

    } /* OdmBindNative100 */



/* 0.27 2007-01-17-15:28 Implement makePendingDocument function.
   0.26 2007-01-17-15:05 Get clean compile and Check05 regression.
   0.25 2007-01-16-19:49 Add the implementation for startNewDoc.
   0.24 2006-12-31-18:17 Get clean compile and make other touch-ups from
        code review.  The OdmNative100.obj is now 10,253 bytes.
   0.23 2006-12-30-21:30 Add the additional methods required by upgrade to
        OdmNative100.hpp 0.21.  These methods, although implemented by null
        behaviors or incomplete behaviors, are all of those required for
        ODMJNI 1.0.
   0.22 2006-12-30-16:15 Replace the hDefaultDMS method by hWorkingDMS and
        add the logic necessary for that feature of IodmNative100i 0.21.
        Also move appId verification to the constructor so there is no
        sneak path, and adjust QueryInterface to block on a failed
        constructor.
   0.21 2006-12-20-15:46 Modify hWndFound to keep the first one rather than
        the last one enumerated just to see if it makes any difference.  If
        not, we need to be more sophisticated in filtering handles.
   0.20 2006-11-25-21:20 Identify as the version employed in the 0.20alpha
        integration of OdmNative100.  Inspect for completeness in use by
        reviewers of the code.
   0.19 2006-11-24-10:12 Add separate tryGetConsole method for isolation
        of GetConsoleWindow usage and later substitution of dynamic
        discovery of the function's support on the current system.
   0.18 2006-11-23-22:04 Use GetVersion() as a guard before attempting
        to use GetConsoleVersion.  This still might not run on Win98.
        We'll have to try it.  [2006-11-24: It doesn't help.  Binding
        to kernel32.dll happens at load time, so the only way to allow
        degraded operation on Windows versions below Windows 2000 is to
        perform a dynamic GetProcAddress for GetConsoleWindow.
   0.17 2006-11-23-21:12 Use EnumThreadWindows and if that finds no
        windows, use GetConsoleWindow().
   0.16 2006-11-23-20:40 Use EnumThreadWindows to see if we can find
        the correct window either way.
   0.15 2006-11-23-18:46 Try GetConsoleWindow() to see what we can do
        for determining the window of our application for associating
        modal dialogs.
   0.14 2006-11-23-17:50 Implement IodmApplication100i as an additional
        interface for private use within implementations of other classes
        and functions.
   0.13 2006-11-23-15:26 Experiment with having static methods here.  The
        experiment was unsuccessful.  VC++ 2005 error C2724 says this should
        not be done, but it is considered an error, not a warning.
   0.12 2006-11-23-14:54 Change selectDoc to selectDocID to reflect the
        actual behavior, and clean up the viewmode determination case.  We
        are up to 6,933 bytes.
   0.11 2006-11-22-21:52 Get initial selectDoc operation so I can then
        check hWnd and also static methods.
   0.10 2006-11-22-19:03 Eliminate explicit array sizes in selectDoc
        signatures.
   0.09 2006-11-22-18:10 Switch to Odma32types100.h for the ODMA 1.0 Types
        and Values declarations.  Have empty selectDoc method.
   0.08 2006-11-20-13:05 Implement hasDefaultDMS with proper retention
        and ultimate release of any returned ODMHANDLE.  The .obj is now up
        to 6,569 bytes.
   0.07 2006-11-19-01:49 Implement interfaceImplementation() and provide
        version information that combines the source of definition with
        the compiled source of implementation.
   0.06 2006-11-18-20:09 Implement the hasConMan functionality with proper
        timing of release, etc.  The .obj is now up to 5,867 bytes.
   0.05 2006-11-17-23:54 Implement the IUnknown functionality.
   0.04 2006-11-17-18:58 Add the OdmApplication declaration and used
        dummy implementation functions to confirm construction and
        destruction only.
   0.03 2006-11-17-17:42 Build an Application ID validator and add it to
        the validations done by OdmBindNative100 before it even attempts
        to construct an OdmApplication instance.
   0.02 2006-11-16-21:18 Adjust to use REFIID for rIID parameter and be
        consistent with QueryInterface on this score.  Use a pure C++
        entry point as well.  That takes the .obj to 646 bytes.
   0.01 2006-11-16-13:58 Add null implementation of OdmBindNative.  This
        makes a 624 byte .obj file.
   0.00 2006-11-15-21:49 Provide skeleton for implementation of the basic
        construction class for all of the methods so we build an initial
        construction class and then a simple factory class.

   $Header: /ODMdev/info/odma/OdmNative100/OdmNative100.cpp 31    07-01-17 15:38 Orcmid $
   */

/*                     *** end of OdmNative100.cpp ***                  */

