/*
 * @DEC_COPYRIGHT@
 */
/*
 * HISTORY
 * $Log: h263_dispatch.c,v $
  * $EndLog$
 */
/*
**++
** FACILITY:  Workstation Multimedia  (WMM)  v1.0
**
** FILE NAME:   h263_dispatch.c
** MODULE NAME: h263_dispatch.c
**
** MODULE DESCRIPTION:
**    H.263 ICM driver message dispatch routine.
**
**    Functions
**
**      DriverProc              (Entry point into codec)
**      ICH263Message           (ICM message handler. Calls routines in h263.c)
**      ICH263ClientThread      (Thread for processing most messages)
**      ICH263ProcessThread     (Thread for processing compress/decompress)
**
**    Private functions:
**
** DESIGN OVERVIEW:
** 	Accept the DriverProc message and dispatch to the proper
**      handler.
**
**--
*/

#include <stdio.h>
#include <windows.h>
#include "h26x_int.h"

#ifdef _SLIBDEBUG_
#define _DEBUG_     0  /* detailed debuging statements */
#define _VERBOSE_   1  /* show progress */
#define _VERIFY_    1  /* verify correct operation */
#define _WARN_      1  /* warnings about strange behavior */
#endif

static CRITICAL_SECTION h263CritSect; /* Critical section for multi-thread protection */
static HMODULE ghModule=NULL;         /* Global handle to Module */

#define BOGUS_DRIVER_ID -1	/* Used during open on NT */

#ifdef WIN32
#define CHECKANDLOCK(x) if (((void *)lParam1==NULL) || (int)lParam2<sizeof(x)) \
	                        return ((unsigned int)ICERR_BADPARAM);
#define UNLOCK
#else
#define CHECKANDLOCK(x) { \
      int size = lParam2; \
      if (noChecklParam2 && size < sizeof(x)) \
	size = sizeof(x); \
      if (((void *)lParam1 == NULL) || size < sizeof(x)) \
	return ((unsigned int)ICERR_BADPARAM); \
}
#define UNLOCK
#endif


/***************************************************************************
 ***************************************************************************/

MMRESULT CALLBACK ICH263Message(DWORD_PTR driverHandle,
				    UINT      uiMessage,
				    LPARAM    lParam1,
				    LPARAM    lParam2,
				    H26XINFO *info)
{
    ICINFO *icinfo;
    LPBITMAPINFOHEADER lpbiIn;
    LPBITMAPINFOHEADER lpbiOut;
    ICDECOMPRESS *icDecompress;

    DWORD biSizeIn;
    DWORD biSizeOut;
    MMRESULT ret;

    _SlibDebug(_DEBUG_,
		ScDebugPrintf("ICH263Message(DriverID=%p, message=%d, lParam1=%p,lParam1=%p, info=%p)\n",
             driverHandle, uiMessage, lParam1, lParam2, info) );

    switch (uiMessage)
    {
        /*********************************************************************

	  ICM messages

	 *********************************************************************/	
    case ICM_CONFIGURE:
	/*
	 *  return ICERR_OK if you will do a configure box, error otherwise
	 */
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_CONFIGURE:\n") );
	if (lParam1 == -1)
	    return ICH263QueryConfigure(info) ? ICERR_OK :
		ICERR_UNSUPPORTED;
	else
	    return ICH263Configure(info);

    case ICM_ABOUT:
	/*
	 *  return ICERR_OK if you will do a about box, error otherwise
	 */
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_ABOUT::\n") );
	if (lParam1 == -1)
	    return ICH263QueryAbout(info) ? ICERR_OK :
		ICERR_UNSUPPORTED;
	else
	    return ICH263About(info);

    case ICM_GETSTATE:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_GETSTATE::\n") );
    if ((LPVOID)lParam1!=NULL) /* getting state size */
    {
      char *stateinfo=(char *)lParam1;  /* for debugging break point */
      memset(stateinfo, 0, 0x60);
    }
    return (0x60);

    case ICM_SETSTATE:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_SETSTATE::\n") );
    if ((LPVOID)lParam1!=NULL) /* getting state size */
    {
      char *stateinfo=(char *)lParam1; /* for debugging break point */
      int i=0;
      i=i+1;
    }
    if (info->dwRTP==EC_RTP_MODE_OFF) /* must be NetMeeting, turn on RTP */
      info->dwRTP=EC_RTP_MODE_A;
    return (0x60);

    case ICM_GETINFO:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_GETINFO::\n") );
	icinfo = (ICINFO FAR *)lParam1;
	if (icinfo == NULL)
	    return sizeof(ICINFO);

	if ((DWORD)lParam2 < sizeof(ICINFO))
	    return 0;
	ret = ICH263GetInfo(info, icinfo, (DWORD)lParam2);

	UNLOCK;
	return ret;

    case ICM_GETDEFAULTQUALITY:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_GETDEFAULTQUALITY::\n") );
	CHECKANDLOCK(DWORD);
	ret = ICH263GetDefaultQuality(info, (DWORD *)lParam1);
	UNLOCK;
	return ret;

    case ICM_GETQUALITY:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_GETQUALITY::\n") );
	CHECKANDLOCK(DWORD);
	ret = ICH263GetQuality(info, (DWORD *)lParam1);
	UNLOCK;
	return ret;

	case ICM_SETQUALITY:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_SETQUALITY::\n") );
	CHECKANDLOCK(DWORD);
	ret = ICH263SetQuality(info, (DWORD)lParam1);
	UNLOCK;
	return ret;

    case ICM_GETDEFAULTKEYFRAMERATE:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_GETDEFAULTKEYFRAMERATE:::\n") );
	return ((unsigned int)ICERR_UNSUPPORTED);


    case DECH26X_CUSTOM_ENCODER_CONTROL:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------DECH26X_CUSTOM_ENCODER_CONTROL:::\n") );
    return(ICH263CustomEncoder(info, (DWORD)lParam2, (DWORD)lParam2));
	

	/*********************************************************************

	  compression messages

    *********************************************************************/

    case ICM_COMPRESS_QUERY:
	_SlibDebug(_DEBUG_, ScDebugPrintf("------ICM_COMPRESS_QUERY::\n") );
	if ((lpbiIn = (LPBITMAPINFOHEADER)lParam1) == NULL)
	    return ((unsigned int)ICERR_BADPARAM);
	lpbiOut = (LPBITMAPINFOHEADER)lParam2;
    _SlibDebug(_DEBUG_, ScDebugPrintf(" lpbiIn: %s\n", BMHtoString(lpbiIn)) );
    _SlibDebug(_DEBUG_, ScDebugPrintf(" lpbiOut: %s\n", BMHtoString(lpbiOut)) );

	/* Lock the memory - have to lock the structure first, and iff
	* that works, lock the rest
	*/
	biSizeIn = lpbiIn->biSize;
	if (lpbiOut)
	    biSizeOut = lpbiOut->biSize;
	if ((biSizeIn != sizeof(BITMAPINFOHEADER))
	||  (lpbiOut && biSizeOut != sizeof(BITMAPINFOHEADER))) {
	    UNLOCK;
	    if ((biSizeIn < sizeof(BITMAPINFOHEADER))
	    ||  (lpbiOut && (biSizeOut < sizeof(BITMAPINFOHEADER))))
		return ((unsigned int)ICERR_BADPARAM);
	}

	ret = ICH263CompressQuery(info, lpbiIn, lpbiOut);

	UNLOCK;
	return ret;

    case ICM_COMPRESS_BEGIN:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_COMPRESS_BEGIN::\n") );
	lpbiIn = (LPBITMAPINFOHEADER)lParam1;
	lpbiOut = (LPBITMAPINFOHEADER)lParam2;
    _SlibDebug(_VERBOSE_, ScDebugPrintf(" lpbiIn: %s\n", BMHtoString(lpbiIn)) );
    _SlibDebug(_VERBOSE_, ScDebugPrintf(" lpbiOut: %s\n", BMHtoString(lpbiOut)) );
	if (lpbiIn == NULL || lpbiOut  == NULL)
	   return ((unsigned int)ICERR_BADPARAM);

	/* Lock the memory - have to lock the structure first, and iff
	* that works, lock the rest
	*/
	biSizeIn = lpbiIn->biSize;
	biSizeOut = lpbiOut->biSize;
	if (biSizeIn != sizeof(BITMAPINFOHEADER) ||
	    biSizeOut != sizeof(BITMAPINFOHEADER))
	{
      _SlibDebug(_VERBOSE_,
	 	ScDebugPrintf("biSizeIn or biSizeOut > sizeof(BITMAPINFOHEADER)\n") );
	  UNLOCK;
	  if ((biSizeIn < sizeof(BITMAPINFOHEADER))
	    ||  (biSizeOut < sizeof(BITMAPINFOHEADER)))
		return ((unsigned int)ICERR_BADPARAM);
	}

	ret = ICH263CompressBegin(info, lpbiIn, lpbiOut);
	UNLOCK;
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_COMPRESS_BEGIN:: Done\n") );
	return ret;

    case ICM_COMPRESS_GET_FORMAT:
	_SlibDebug(_DEBUG_, ScDebugPrintf("------ICM_COMPRESS_GET_FORMAT::\n") );
	/* Nobody uses lpbiIn in this function.  Don't lock anything,
	* the lower layers will have to do any necessary locking.
	*/
	return ICH263CompressGetFormat(info,
				       (LPBITMAPINFOHEADER)lParam1,
				       (LPBITMAPINFOHEADER)lParam2);

    case ICM_COMPRESS_GET_SIZE:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_COMPRESS_GET_SIZE::\n") );
	lpbiIn = (LPBITMAPINFOHEADER)lParam1;
	lpbiOut = (LPBITMAPINFOHEADER)lParam2;
    return(ICH263CompressGetSize(lpbiIn));

    case ICM_COMPRESS:
	_SlibDebug(_DEBUG_, ScDebugPrintf("------ICM_COMPRESS::\n") );
	ret = ICH263Compress(info, (ICCOMPRESS *) lParam1, (DWORD)lParam2);
	return ret;

    case ICM_COMPRESS_END:
    _SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_COMPRESS_END::\n") );
	return ICH263CompressEnd(info);
     /*********************************************************************

	  decompress messages

	 *********************************************************************/

    case ICM_DECOMPRESS_GET_FORMAT:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_DECOMPRESS_GET_FORMAT::\n") );
	/* Nobody uses lpbiIn in this function.  Don't lock anything,
	* the lower layers will have to do any necessary locking.
	*/
	return ICH263DecompressGetFormat(info,
					     (LPBITMAPINFOHEADER)lParam1,
					     (LPBITMAPINFOHEADER)lParam2);

    case ICM_DECOMPRESS_GET_PALETTE:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_DECOMPRESS_GET_PALETTE::\n") );
	if ((lpbiIn = (LPBITMAPINFOHEADER)lParam1) == NULL)
	    return ((unsigned int)ICERR_BADPARAM);
	if ((biSizeIn = lpbiIn->biSize) != sizeof(BITMAPINFOHEADER)) {
	    UNLOCK;
	    if (biSizeIn < sizeof(BITMAPINFOHEADER))
		return (unsigned int)ICERR_BADPARAM;
	}

	/*ret = ICH263DecompressGetPalette(info,
					 (LPBITMAPINFOHEADER)lParam1,
					 (LPBITMAPINFOHEADER)lParam2);
	*/
    ret = (MMRESULT)ICERR_BADPARAM;
	UNLOCK;
	return ret;

    case ICM_DECOMPRESS_SET_PALETTE:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_DECOMPRESS_SET_PALETTE::\n") );
    return ((unsigned int)ICERR_UNSUPPORTED);

    case ICM_DECOMPRESS_QUERY:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_DECOMPRESS_QUERY::\n") );
	if ((lpbiIn = (LPBITMAPINFOHEADER)lParam1) == NULL)
	    return ((unsigned int)ICERR_BADPARAM);
	lpbiOut = (LPBITMAPINFOHEADER)lParam2;
    _SlibDebug(_VERBOSE_, ScDebugPrintf(" lpbiIn: %s\n", BMHtoString(lpbiIn)) );
    _SlibDebug(_VERBOSE_, ScDebugPrintf(" lpbiOut: %s\n", BMHtoString(lpbiOut)) );
	/* Lock the memory - have to lock the structure first, and iff
	* that works, lock the rest
	*/
	biSizeIn = lpbiIn->biSize;
	if (lpbiOut)
	    biSizeOut = lpbiOut->biSize;
	if ((biSizeIn != sizeof(BITMAPINFOHEADER))
	||  (lpbiOut && biSizeOut != sizeof(BITMAPINFOHEADER))) {
	    UNLOCK;

	    if ((biSizeIn < sizeof(BITMAPINFOHEADER))
	    ||  (lpbiOut && (biSizeOut < sizeof(BITMAPINFOHEADER))))
		return (unsigned int)ICERR_BADPARAM;
	}

	ret = ICH263DecompressQuery(info, lpbiIn, lpbiOut);

	UNLOCK;
	return ret;

    case ICM_DECOMPRESS_BEGIN:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_DECOMPRESS_BEGIN::\n") );
	if (((lpbiIn = (LPBITMAPINFOHEADER)lParam1) == NULL)
	||  ((lpbiOut = (LPBITMAPINFOHEADER)lParam2) == NULL))
	    return ((unsigned int)ICERR_BADPARAM);
	/* Lock the memory - have to lock the structure first, and iff
	* that works, lock the rest
	*/
	biSizeIn = lpbiIn->biSize;
	biSizeOut = lpbiOut->biSize;
	if ((biSizeIn != sizeof(BITMAPINFOHEADER))
	||  (biSizeOut != sizeof(BITMAPINFOHEADER))) {
	    UNLOCK;
	    if ((biSizeIn < sizeof(BITMAPINFOHEADER))
	    ||  (biSizeOut < sizeof(BITMAPINFOHEADER)))
		return ((unsigned int)ICERR_BADPARAM);
	}

	ret = ICH263DecompressBegin(info, lpbiIn, lpbiOut);
	UNLOCK;
	return ret;

    case ICM_DECOMPRESS:
	_SlibDebug(_DEBUG_, ScDebugPrintf("------ICM_DECOMPRESS::\n") );
	icDecompress = (ICDECOMPRESS *)(void *)lParam1;
    if ((void *)lParam1==NULL && (void *)lParam2==NULL)
      return (unsigned int)ICERR_BADPARAM;
	ret = ICH263Decompress(info, (ICDECOMPRESS *) lParam1, (DWORD)lParam2);
	return ret;

    case ICM_DECOMPRESS_END:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_DECOMPRESS_END::\n") );
    ret = ICH263DecompressEnd(info);
	return ret;

    case ICM_COMPRESS_FRAMES_INFO:
	_SlibDebug(_VERBOSE_, ScDebugPrintf("------ICM_COMPRESS_FRAMES_INFO\n") );
    if ((LPVOID)lParam1==NULL ||
		lParam2<sizeof(ICCOMPRESSFRAMES))
		return (unsigned int)ICERR_BADPARAM;
    {
      ICCOMPRESSFRAMES *cf=(ICCOMPRESSFRAMES*)lParam1;
      _SlibDebug(_VERBOSE_,
		 ScDebugPrintf("Start=%ld, Count=%ld, Quality=%ld, DataRate=%ld (%5.1f Kb/s), KeyRate=%ld, FPS=%4.1f\n",
		  cf->lStartFrame,cf->lFrameCount,cf->lQuality,
		  cf->lDataRate, cf->lDataRate*8.0/1000, cf->lKeyRate, cf->dwRate*1.0/cf->dwScale) );
      /* info->dwBitrate=cf->lDataRate*8; */
      info->dwQuality=cf->lQuality;
      /* info->fFrameRate=(float)(cf->dwRate*1.0/cf->dwScale); */
    }
	return (unsigned int)ICERR_OK;
}
_SlibDebug(_VERBOSE_,
		ScDebugPrintf("ICH263Message(DriverID=%p, message=%d, lParam1=%p,lParam1=%p, info=%p) Unsupported\n",
             driverHandle, uiMessage, lParam1, lParam2, info) );
return ((unsigned int)ICERR_UNSUPPORTED);
}

/*
**++
**  FUNCTIONAL_NAME:            DriverProc
**
**  FUNCTIONAL_DESCRIPTION:
**      main and only entry point for the server into this driver
**
**  FORMAL PARAMETERS:
**      client pointer
**      driverHandle, returned by us on a DRV_OPEN message
**      driverID allocated and passed by the server
**      message to handle
**      parameters
**
**  RETURN VALUE:
**
**  COMMENTS:
**
**  DESIGN:
**
**/

#define DLLEXPORT	

MMRESULT DLLEXPORT APIENTRY
DriverProc(
        DWORD_PTR       dwDriverID,
        HDRVR	        hDriver,
        UINT            message,
        LPARAM          lParam1,
        LPARAM          lParam2
)

{
  MMRESULT ret = DRV_OK;
  BOOL notfound = FALSE;
  H26XINFO *info = NULL;
  void     *client = 0;  /* Dummy client pointer for other calls. */
  /*
   * On NT, use __try {} __except() {} to catch
   * error conditions.
   */
#ifdef HANDLE_EXCEPTIONS
 __try { /* try..except */
  __try { /* try..finally */
#endif
    /*
     * Protect threads from interfering with each other.
     * To protect against a crash at shutdown, make sure
     * that the critical section has not been deleted.
     */
  if( h263CritSect.DebugInfo )
      EnterCriticalSection( &h263CritSect ) ;
  _SlibDebug(_DEBUG_,
	  ScDebugPrintf("DriverProc(DriverID=%p,hDriver=%p,message=%d,lParam1=%p,lParam2=%p)\n",
           dwDriverID, hDriver, message, lParam1, lParam2) );
	

  switch (message) {
    case DRV_LOAD:
        ret = DRV_OK;
	    _SlibDebug(_VERBOSE_, ScDebugPrintf("------DRV_LOAD returns %d\n", ret) );
        break;

    case DRV_ENABLE:
	    ret = 0;
	    _SlibDebug(_VERBOSE_, ScDebugPrintf("------DRV_ENABLE returns %d\n", ret) );
        break;

    case DRV_DISABLE:
        ret = DRV_OK;
	    _SlibDebug(_VERBOSE_, ScDebugPrintf("------DRV_DISABLE returns %d\n", ret) );
        break;

    case DRV_FREE:
        ret = DRV_OK;
	    _SlibDebug(_VERBOSE_, ScDebugPrintf("------DRV_FREE returns %d\n", ret) );
        break;

    case DRV_OPEN:
	    /* If lParam2 is NULL, then the app is trying to do a CONFIGURE.
         * Return BOGUS_DRIVER_ID, since we don't support CONFIGURE.
	     */
	    if( lParam2 == (LONG_PTR) NULL )
	    	ret = (MMRESULT)BOGUS_DRIVER_ID;
	    else /* lParam2 is an ICOPEN structure, let's really open */
	        ret = (MMRESULT)((ULONG_PTR)ICH263Open((void *) lParam2));
	    _SlibDebug(_VERBOSE_, ScDebugPrintf("------DRV_OPEN returns %d\n", ret) );
	    break;

	case DRV_CLOSE:
	    _SlibDebug(_VERBOSE_,
			ScDebugPrintf("------DRV_CLOSE. client %d DriverID %p\n",
                 client, dwDriverID) );
        ret = 0 ;
        if ((INT_PTR)dwDriverID != BOGUS_DRIVER_ID)
        {
          info = IChic2info((HIC)dwDriverID);
          if (info)
	    	ICH263Close(info, FALSE);
          else
            ret = ((unsigned int)ICERR_BADHANDLE);
        }
		break;

    case DRV_QUERYCONFIGURE:
	    /*
	     * this is a GLOBAL query configure
	     */
	    ret = ICH263QueryConfigure(info);
		break;

    case DRV_CONFIGURE:
	    /*
	     * this is a GLOBAL configure ('cause we don't get a configure
	     * for each of our procs, we must have just one configure)
	     */

	    ret = ICH263Configure(info);
		break;

    case DRV_INSTALL:
    case DRV_REMOVE:
    case DRV_EXITSESSION:
        break;

    default:
        info = IChic2info((HIC)dwDriverID);
        if (info)
	      ret = ICH263Message(dwDriverID,
                              message,
                              lParam1,
                              lParam2,
                              info ) ;
        else
	      ret = ((unsigned int)ICERR_BADHANDLE) ;
   }

#ifdef HANDLE_EXCEPTIONS
} __finally {
#endif /* HANDLE_EXCEPTIONS */
    /*
     * Leave the critical section, so we don't
     * deadlock.
     */
  if( h263CritSect.DebugInfo )
      LeaveCriticalSection( &h263CritSect );
#ifdef HANDLE_EXCEPTIONS
} /* try..finally */
} __except(EXCEPTION_EXECUTE_HANDLER) {
 /*
  * NT exception handler. If anything went
  * wrong in the __try {} section, we will
  * end up here.
  */
#if defined(EXCEPTION_MESSAGES) && defined(H263_SUPPORT)
    // MessageBox(NULL, "Exception in H263 DriverProc", "Warning", MB_OK);
#elif defined(EXCEPTION_MESSAGES)
    // MessageBox(NULL, "Exception in H261 DriverProc", "Warning", MB_OK);
#endif
    /*
     * Return an error code.
     */
    return((MMRESULT)ICERR_INTERNAL);
} /* try..except */
#endif /* HANDLE_EXCEPTIONS */
  _SlibDebug(_DEBUG_||_VERBOSE_, ScDebugPrintf("return is %d\n", ret) );
  return ret;
}


/*
 * Dummy DriverPostReply routine, it does nothing.
 * It should never be called on NT.
 */

DriverPostReply(void *client,
		DWORD ret,
		DWORD arg )
{
    return 0;
}

#ifndef INITCRT
#define INITCRT
#endif
#ifdef INITCRT
BOOL WINAPI     _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
#endif

/*
 * This DllEntryPoint is needed on NT in order for
 * an application to connect to a dll properly.
 */

DLLEXPORT BOOL WINAPI
DllEntryPoint(
	      HINSTANCE hinstDLL,
	      DWORD fdwReason,
	      LPVOID lpReserved)
{

    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:

      /*
       * We're being loaded - save our handle in a global.
       */

      ghModule = (HMODULE) hinstDLL;

      /*
       * Initialize the Critical Section that we use
       * to ensure Threads don't stomp on each other.
       */

      InitializeCriticalSection( &h263CritSect ) ;

      /*
       * A Process is also a thread, so deliberately
       * fall into DLL_THREAD_ATTACH.
       */

    case DLL_THREAD_ATTACH:

      /*
       * A Thread have been created that may
       * be calling this DLL.
       * Initialize the C run_time library.
       */

#ifdef INITCRT

      if( !_CRT_INIT( hinstDLL, fdwReason, lpReserved ) )
	return FALSE ;

#endif /* INITCRT */

      break;

    case DLL_PROCESS_DETACH:

      /*
       * We are shutting down. Perform some cleanup
       * so that lingering threads won't try to work
       * and maybe access violate.
       */

      TerminateH263() ;

      /*
       * Delete the Critical Section that we created
       * at load time.
       */

      DeleteCriticalSection( &h263CritSect ) ;

      /*
       * A Process is also a thread so deliberately
       * fall through to DLL_THREAD_DETACH.
       */

    case DLL_THREAD_DETACH:

      /*
       * Close down the C run-time library.
       */

#ifdef INITCRT

      if( !_CRT_INIT( hinstDLL, fdwReason, lpReserved ) )
	return FALSE ;

#endif /* INITCRT */

      break;
    }

    return (TRUE);
}