1156 lines
30 KiB
C
1156 lines
30 KiB
C
|
/****************************** Module Header ******************************\
|
||
|
* Module Name: Srvr.c Server Main module
|
||
|
*
|
||
|
* Purpose: Includes All the server communication related routines.
|
||
|
*
|
||
|
* Created: Oct 1990.
|
||
|
*
|
||
|
* Copyright (c) 1985, 1986, 1987, 1988, 1989 Microsoft Corporation
|
||
|
*
|
||
|
* History:
|
||
|
* Raor: Wrote the original version.
|
||
|
* curts created portable version for WIN16/32
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <shellapi.h>
|
||
|
#include "cmacs.h"
|
||
|
#include "ole.h"
|
||
|
#include "dde.h"
|
||
|
#include "srvr.h"
|
||
|
|
||
|
// LOWWORD - BYTE 0 major verision, BYTE1 minor version,
|
||
|
// HIWORD is reserved
|
||
|
|
||
|
#define OLE_VERSION 0x0901L
|
||
|
|
||
|
|
||
|
extern ATOM aOLE;
|
||
|
extern ATOM aSysTopic;
|
||
|
extern ATOM aStdExit;
|
||
|
extern ATOM aStdCreate;
|
||
|
extern ATOM aStdOpen;
|
||
|
extern ATOM aStdEdit;
|
||
|
extern ATOM aStdCreateFromTemplate;
|
||
|
extern ATOM aStdShowItem;
|
||
|
extern ATOM aProtocols;
|
||
|
extern ATOM aTopics;
|
||
|
extern ATOM aFormats;
|
||
|
extern ATOM aStatus;
|
||
|
extern ATOM cfNative;
|
||
|
extern ATOM aEditItems;
|
||
|
extern ATOM aStdClose;
|
||
|
|
||
|
|
||
|
extern HANDLE hdllInst;
|
||
|
|
||
|
#ifdef WIN16
|
||
|
extern BOOL bProtMode;
|
||
|
#endif
|
||
|
|
||
|
extern FARPROC lpTerminateClients;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
BOOL bShowed = FALSE;
|
||
|
void ShowVersion (void);
|
||
|
#endif
|
||
|
|
||
|
|
||
|
DWORD APIENTRY OleQueryServerVersion ()
|
||
|
{
|
||
|
return OLE_VERSION;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************** Public Function ****************************\
|
||
|
* OLESTATUS FAR PASCAL OleRegisterServer (lpclass, lpolesrvr, lplhsrvr)
|
||
|
*
|
||
|
* OleRegisterServer: Registers the server with the server library.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* 1. Ptr to the server class.
|
||
|
* 2. Ptr to the olesrvr. This is private to the server app.
|
||
|
* (Typically this is the ptr to the private storage area of
|
||
|
* server app server related info).
|
||
|
* 3. Ptr to the LHSRVR. Place where to pass back the long
|
||
|
* handle of the server in DLL (This is private to the DLL).
|
||
|
*
|
||
|
* return values:
|
||
|
* returns OLE_OK if the server is successfully registered .
|
||
|
* else returns the corresponding error.
|
||
|
*
|
||
|
*
|
||
|
* History:
|
||
|
* Raor: Wrote it,
|
||
|
\***************************************************************************/
|
||
|
|
||
|
OLESTATUS APIENTRY OleRegisterServer (
|
||
|
LPCSTR lpclass, // class name
|
||
|
LPOLESERVER lpolesrvr, // ole srvr(private to srvr app)
|
||
|
LHSRVR FAR * lplhsrvr, // where we pass back our private handle
|
||
|
HINSTANCE hInst,
|
||
|
OLE_SERVER_USE useFlags
|
||
|
){
|
||
|
HANDLE hsrvr = NULL;
|
||
|
LPSRVR lpsrvr = NULL;
|
||
|
ATOM aExe = (ATOM)0;
|
||
|
|
||
|
Puts ("OleRegisterServer");
|
||
|
|
||
|
#ifdef WIN16
|
||
|
if (!bProtMode)
|
||
|
return OLE_ERROR_PROTECT_ONLY;
|
||
|
#endif
|
||
|
|
||
|
PROBE_READ(lpclass);
|
||
|
PROBE_WRITE(lpolesrvr);
|
||
|
PROBE_WRITE(lplhsrvr);
|
||
|
|
||
|
// add the app atom to global list
|
||
|
if (!ValidateSrvrClass (lpclass, &aExe))
|
||
|
return OLE_ERROR_CLASS;
|
||
|
|
||
|
hsrvr = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_DDESHARE, sizeof (SRVR));
|
||
|
if (! (hsrvr && (lpsrvr = (LPSRVR)GlobalLock (hsrvr))))
|
||
|
goto errReturn;
|
||
|
|
||
|
// set the signature handle and the app atom.
|
||
|
lpsrvr->sig[0] = 'S';
|
||
|
lpsrvr->sig[1] = 'R';
|
||
|
lpsrvr->hsrvr = hsrvr;
|
||
|
lpsrvr->aClass = GlobalAddAtom (lpclass);
|
||
|
lpsrvr->lpolesrvr = lpolesrvr;
|
||
|
lpsrvr->relLock = TRUE; // set the release lock.
|
||
|
lpsrvr->aExe = aExe;
|
||
|
lpsrvr->useFlags = useFlags;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
ASSERT ((useFlags == OLE_SERVER_SINGLE || useFlags == OLE_SERVER_MULTI), "invalid server options");
|
||
|
#endif
|
||
|
|
||
|
// Create the servre window and do not show it.
|
||
|
if (!(lpsrvr->hwnd = CreateWindow ("SrvrWndClass", "Srvr",
|
||
|
WS_OVERLAPPED,0,0,0,0,NULL,NULL, hdllInst, NULL)))
|
||
|
goto errReturn;
|
||
|
|
||
|
// save the ptr to the srever struct in the window.
|
||
|
SetWindowLongPtr (lpsrvr->hwnd, 0, (LONG_PTR)lpsrvr);
|
||
|
|
||
|
// Set the signature.
|
||
|
SetWindowWord (lpsrvr->hwnd, WW_LE, WC_LE);
|
||
|
SetWindowLongPtr (lpsrvr->hwnd, WW_HANDLE, (LONG_PTR)hInst);
|
||
|
*lplhsrvr = (LONG_PTR)lpsrvr;
|
||
|
|
||
|
return OLE_OK;
|
||
|
|
||
|
errReturn:
|
||
|
if (lpsrvr){
|
||
|
if (lpsrvr->hwnd)
|
||
|
DestroyWindow (lpsrvr->hwnd);
|
||
|
|
||
|
if (lpsrvr->aClass)
|
||
|
GlobalDeleteAtom (lpsrvr->aClass);
|
||
|
|
||
|
if (lpsrvr->aExe)
|
||
|
GlobalDeleteAtom (lpsrvr->aExe);
|
||
|
|
||
|
GlobalUnlock (hsrvr);
|
||
|
}
|
||
|
|
||
|
if (hsrvr)
|
||
|
GlobalFree (hsrvr);
|
||
|
|
||
|
return OLE_ERROR_MEMORY;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// ValidateSrvrClass checks whether the given server class is valid by
|
||
|
// looking in the win.ini.
|
||
|
|
||
|
BOOL INTERNAL ValidateSrvrClass (
|
||
|
LPCSTR lpclass,
|
||
|
ATOM FAR * lpAtom
|
||
|
){
|
||
|
char buf[MAX_STR];
|
||
|
LONG cb = MAX_STR;
|
||
|
char key[MAX_STR];
|
||
|
LPSTR lptmp;
|
||
|
LPSTR lpbuf;
|
||
|
char ch;
|
||
|
|
||
|
lstrcpy (key, lpclass);
|
||
|
lstrcat (key, "\\protocol\\StdFileEditing\\server");
|
||
|
|
||
|
if (RegQueryValue (HKEY_CLASSES_ROOT, key, buf, &cb))
|
||
|
return FALSE;
|
||
|
|
||
|
if (!buf[0])
|
||
|
return FALSE;
|
||
|
|
||
|
// Get exe name without path and then get an atom for that
|
||
|
|
||
|
lptmp = lpbuf = (LPSTR)buf;
|
||
|
while ((ch = *lptmp++) && ch != '\0') {
|
||
|
if (ch == '\\' || ch == ':')
|
||
|
lpbuf = lptmp;
|
||
|
}
|
||
|
*lpAtom = GlobalAddAtom (lpbuf);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************** Public Function ****************************\
|
||
|
* OLESTATUS FAR PASCAL OleRevokeServer (lhsrvr)
|
||
|
*
|
||
|
* OlerevokeServer: Unregisters the server which has been registered.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* 1. DLL server handle.
|
||
|
*
|
||
|
*
|
||
|
* return values:
|
||
|
* returns OLE_OK if the server is successfully unregisterd.
|
||
|
* ( It is Ok for the app free the associated space).
|
||
|
* If the unregistration is intiated, returns OLE_STARTED.
|
||
|
* Calls the Server class release entry point when the server
|
||
|
* can be released.
|
||
|
*
|
||
|
* History:
|
||
|
* Raor: Wrote it,
|
||
|
\***************************************************************************/
|
||
|
|
||
|
OLESTATUS APIENTRY OleRevokeServer (
|
||
|
LHSRVR lhsrvr
|
||
|
){
|
||
|
HWND hwndSrvr;
|
||
|
LPSRVR lpsrvr;
|
||
|
|
||
|
Puts ("OleRevokeServer");
|
||
|
|
||
|
if (!CheckServer (lpsrvr = (LPSRVR)lhsrvr))
|
||
|
return OLE_ERROR_HANDLE;
|
||
|
|
||
|
if (lpsrvr->bTerminate && lpsrvr->termNo)
|
||
|
return OLE_WAIT_FOR_RELEASE;
|
||
|
|
||
|
hwndSrvr = lpsrvr->hwnd;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
ASSERT (hwndSrvr, "Illegal server handle ")
|
||
|
#endif
|
||
|
|
||
|
// Terminate the conversation with all clients.
|
||
|
// If there are any clients to be terminated
|
||
|
// return back with OLE_STARTED and srvr relase
|
||
|
// will be called for releasing the server finally.
|
||
|
|
||
|
// we are terminating.
|
||
|
lpsrvr->bTerminate = TRUE;
|
||
|
lpsrvr->termNo = 0;
|
||
|
|
||
|
// send ack if Revoke is done as a result of StdExit
|
||
|
if (lpsrvr->fAckExit) {
|
||
|
LPARAM lparamNew = MAKE_DDE_LPARAM(WM_DDE_ACK, 0x8000, lpsrvr->hDataExit);
|
||
|
|
||
|
// Post the acknowledge to the client
|
||
|
if (!PostMessageToClient (lpsrvr->hwndExit, WM_DDE_ACK, (WPARAM)lpsrvr->hwnd,
|
||
|
lparamNew))
|
||
|
{
|
||
|
// if the window died or post failed, delete the atom.
|
||
|
GlobalFree (lpsrvr->hDataExit);
|
||
|
DDEFREE(WM_DDE_ACK,lparamNew);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// revoks all the documents registered with this server.
|
||
|
RevokeAllDocs (lpsrvr);
|
||
|
|
||
|
// enumerate all the clients which are in your list and post the
|
||
|
// termination.
|
||
|
EnumProps (hwndSrvr, (PROPENUMPROC)lpTerminateClients);
|
||
|
// post all the messages with yield which have been collected in enum
|
||
|
// UnblockPostMsgs (hwndSrvr, TRUE);
|
||
|
|
||
|
// reset the release lock. Now it is ok to release the server
|
||
|
// when all the doc clients and server clients have sent back the
|
||
|
// termination.
|
||
|
|
||
|
lpsrvr->relLock = FALSE;
|
||
|
return ReleaseSrvr (lpsrvr);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// ReleaseSrvr: Called when ever a matching WM_TERMINATE is received
|
||
|
// from doc clients or the server clients of a particular server.
|
||
|
// If there are no more terminates pending, it is ok to release the server.
|
||
|
// Calls the server app "release" proc for releasing the server.
|
||
|
|
||
|
int INTERNAL ReleaseSrvr (
|
||
|
LPSRVR lpsrvr
|
||
|
){
|
||
|
|
||
|
HANDLE hsrvr;
|
||
|
|
||
|
|
||
|
// release srvr is called only when everything is
|
||
|
// cleaned and srvr app can post WM_QUIT
|
||
|
|
||
|
if (lpsrvr->bTerminate){
|
||
|
// only if we are revoking server then see whether it is ok to
|
||
|
// call Release.
|
||
|
|
||
|
// First check whethere any docs are active.
|
||
|
// Doc window is a child window for server window.
|
||
|
|
||
|
if (lpsrvr->termNo || GetWindow (lpsrvr->hwnd, GW_CHILD))
|
||
|
return OLE_WAIT_FOR_RELEASE;
|
||
|
|
||
|
// if the block queue is not empty, do not quit
|
||
|
if (!IsBlockQueueEmpty(lpsrvr->hwnd))
|
||
|
return OLE_WAIT_FOR_RELEASE;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (lpsrvr->relLock)
|
||
|
return OLE_WAIT_FOR_RELEASE; // server is locked. So, delay releasing
|
||
|
|
||
|
// Inform server app it is time to clean up and post WM_QUIT.
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpsrvr->lpolesrvr, WRITE_ACCESS))
|
||
|
ASSERT(0, "Invalid LPOLESERVER")
|
||
|
else if (!CheckPointer (lpsrvr->lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT(0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpsrvr->lpolesrvr->lpvtbl->Release,
|
||
|
"Invalid pointer to Release method")
|
||
|
#endif
|
||
|
|
||
|
(*lpsrvr->lpolesrvr->lpvtbl->Release)(lpsrvr->lpolesrvr);
|
||
|
|
||
|
if (lpsrvr->aClass)
|
||
|
GlobalDeleteAtom (lpsrvr->aClass);
|
||
|
if (lpsrvr->aExe)
|
||
|
GlobalDeleteAtom (lpsrvr->aExe);
|
||
|
DestroyWindow (lpsrvr->hwnd);
|
||
|
GlobalUnlock (hsrvr = lpsrvr->hsrvr);
|
||
|
GlobalFree (hsrvr);
|
||
|
return OLE_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
//TerminateClients: Call back for the enum properties.
|
||
|
|
||
|
BOOL FAR PASCAL TerminateClients (
|
||
|
HWND hwnd,
|
||
|
LPSTR lpstr,
|
||
|
HANDLE hdata
|
||
|
){
|
||
|
LPSRVR lpsrvr;
|
||
|
|
||
|
UNREFERENCED_PARAMETER(lpstr);
|
||
|
|
||
|
lpsrvr = (LPSRVR)GetWindowLongPtr (hwnd, 0);
|
||
|
|
||
|
// If the client already died, no terminate.
|
||
|
if (IsWindowValid ((HWND)hdata)) {
|
||
|
lpsrvr->termNo++;
|
||
|
|
||
|
// irrespective of the post, incremet the count, so
|
||
|
// that client does not die.
|
||
|
|
||
|
PostMessageToClientWithBlock ((HWND)hdata, WM_DDE_TERMINATE, (WPARAM)hwnd, (LPARAM)0);
|
||
|
}
|
||
|
else
|
||
|
ASSERT (FALSE, "TERMINATE: Client's System chanel is missing");
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT FAR PASCAL SrvrWndProc (
|
||
|
HWND hwnd,
|
||
|
UINT msg,
|
||
|
WPARAM wParam,
|
||
|
LPARAM lParam
|
||
|
){
|
||
|
|
||
|
LPSRVR lpsrvr;
|
||
|
WORD status = 0;
|
||
|
HANDLE hdata;
|
||
|
OLESTATUS retval;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
HWND hwndClient;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
if (AddMessage (hwnd, msg, wParam, lParam, WT_SRVR))
|
||
|
return 0L;
|
||
|
|
||
|
lpsrvr = (LPSRVR)GetWindowLongPtr (hwnd, 0);
|
||
|
|
||
|
|
||
|
switch (msg){
|
||
|
|
||
|
case WM_TIMER:
|
||
|
UnblockPostMsgs (hwnd, FALSE);
|
||
|
|
||
|
// if no more blocked message empty the queue.
|
||
|
if (IsBlockQueueEmpty (hwnd))
|
||
|
KillTimer (hwnd, wParam);
|
||
|
|
||
|
if (lpsrvr->bTerminate && IsBlockQueueEmpty(lpsrvr->hwnd))
|
||
|
// Now see wheteher we can release the server .
|
||
|
ReleaseSrvr (lpsrvr);
|
||
|
break;
|
||
|
|
||
|
case WM_CREATE:
|
||
|
DEBUG_OUT ("Srvr create window", 0)
|
||
|
break;
|
||
|
|
||
|
case WM_DDE_INITIATE:
|
||
|
#ifdef FIREWALLS
|
||
|
ASSERT (lpsrvr, "No server window handle in server window");
|
||
|
#endif
|
||
|
|
||
|
DEBUG_OUT ("Srvr: DDE init",0);
|
||
|
if (lpsrvr->bTerminate){
|
||
|
DEBUG_OUT ("Srvr: No action due to termination process",0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// class is not matching, so it is not definitely for us.
|
||
|
// for apps sending the EXE for initiate, do not allow if the app
|
||
|
// is mutiple server.
|
||
|
|
||
|
if (!(lpsrvr->aClass == (ATOM)(LOWORD(lParam)) ||
|
||
|
(lpsrvr->aExe == (ATOM)(LOWORD(lParam)) && IsSingleServerInstance ())))
|
||
|
|
||
|
break;
|
||
|
|
||
|
if (!HandleInitMsg (lpsrvr, lParam)) {
|
||
|
if (!(aSysTopic == (ATOM)(HIWORD(lParam)))) {
|
||
|
|
||
|
// if the server window is not the right window for
|
||
|
// DDE conversation, then try with the doc windows.
|
||
|
SendMsgToChildren (hwnd, msg, wParam, lParam);
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// We can enterain this client. Put him in our client list
|
||
|
// and acknowledge the intiate.
|
||
|
|
||
|
if (!AddClient (hwnd, (HWND)wParam, (HWND)wParam))
|
||
|
break;
|
||
|
|
||
|
lpsrvr->cClients++;
|
||
|
lpsrvr->bnoRelease = FALSE;
|
||
|
// add the atoms and post acknowledge
|
||
|
|
||
|
DuplicateAtom (LOWORD(lParam));
|
||
|
DuplicateAtom (HIWORD(lParam));
|
||
|
|
||
|
SendMessage ((HWND)wParam, WM_DDE_ACK, (WPARAM)hwnd, lParam);
|
||
|
break;
|
||
|
|
||
|
case WM_DDE_EXECUTE: {
|
||
|
HANDLE hData = GET_WM_DDE_EXECUTE_HDATA(wParam,lParam);
|
||
|
|
||
|
DEBUG_OUT ("srvr: execute", 0)
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
// find the client in the client list.
|
||
|
ASSERT (lpsrvr, "No server window handle in server window");
|
||
|
hwndClient = FindClient (lpsrvr->hwnd, (HWND)wParam);
|
||
|
ASSERT (hwndClient, "Client is missing from the server")
|
||
|
#endif
|
||
|
// Are we terminating
|
||
|
if (lpsrvr->bTerminate) {
|
||
|
DEBUG_OUT ("Srvr: sys execute after terminate posted",0)
|
||
|
// !!! are we supposed to free the data
|
||
|
GlobalFree (hData);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
retval = SrvrExecute (hwnd, hData, (HWND)wParam);
|
||
|
SET_MSG_STATUS (retval, status)
|
||
|
|
||
|
if (!lpsrvr->bTerminate) {
|
||
|
LPARAM lparamNew = MAKE_DDE_LPARAM(WM_DDE_ACK,status,hData);
|
||
|
|
||
|
if (!PostMessageToClient ((HWND)wParam, WM_DDE_ACK, (WPARAM)hwnd, lparamNew))
|
||
|
{
|
||
|
GlobalFree (hData);
|
||
|
DDEFREE(WM_DDE_ACK,lparamNew);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case WM_DDE_TERMINATE:
|
||
|
DEBUG_OUT ("Srvr: DDE terminate",0)
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
// find the client in the client list.
|
||
|
hwndClient = FindClient (lpsrvr->hwnd, (HWND)wParam);
|
||
|
ASSERT (hwndClient, "Client is missing from the server")
|
||
|
#endif
|
||
|
DeleteClient (lpsrvr->hwnd, (HWND)wParam);
|
||
|
lpsrvr->cClients--;
|
||
|
|
||
|
if (lpsrvr->bTerminate){
|
||
|
if ((--lpsrvr->termNo == 0) && (IsBlockQueueEmpty (lpsrvr->hwnd)))
|
||
|
// Now see wheteher we can release the server .
|
||
|
ReleaseSrvr (lpsrvr);
|
||
|
|
||
|
// if we released the server, then
|
||
|
// by the time we come here,, we have destroyed the window
|
||
|
|
||
|
}else {
|
||
|
// If client intiated the terminate. post matching terminate
|
||
|
PostMessageToClient ((HWND)wParam, WM_DDE_TERMINATE, (WPARAM)hwnd, (LPARAM)0);
|
||
|
|
||
|
// callback release tell the srvr app, it can exit if needs.
|
||
|
// Inform server app it is time to clean up and post WM_QUIT.
|
||
|
// only if no docs present.
|
||
|
#if 0
|
||
|
if (lpsrvr->cClients == 0
|
||
|
&& (GetWindow (lpsrvr->hwnd, GW_CHILD) == NULL)) {
|
||
|
#endif
|
||
|
if (QueryRelease (lpsrvr)){
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpsrvr->lpolesrvr, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVER")
|
||
|
else if (!CheckPointer (lpsrvr->lpolesrvr->lpvtbl,
|
||
|
WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpsrvr->lpolesrvr->lpvtbl->Release,
|
||
|
"Invalid pointer to Release method")
|
||
|
#endif
|
||
|
|
||
|
(*lpsrvr->lpolesrvr->lpvtbl->Release) (lpsrvr->lpolesrvr);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
case WM_DDE_REQUEST: {
|
||
|
ATOM aItem = GET_WM_DDE_REQUEST_ITEM(wParam,lParam);
|
||
|
|
||
|
if (lpsrvr->bTerminate || !IsWindowValid ((HWND) wParam))
|
||
|
goto RequestErr;
|
||
|
|
||
|
if(RequestDataStd (lParam, (HANDLE FAR *)&hdata) != OLE_OK){
|
||
|
LPARAM lparamNew = MAKE_DDE_LPARAM(WM_DDE_ACK,0x8000, aItem);
|
||
|
|
||
|
// if request failed, then acknowledge with error.
|
||
|
if (!PostMessageToClient ((HWND)wParam, WM_DDE_ACK, (WPARAM)hwnd,lparamNew))
|
||
|
{
|
||
|
DDEFREE(WM_DDE_ACK,lparamNew);
|
||
|
RequestErr:
|
||
|
if (aItem)
|
||
|
GlobalDeleteAtom (aItem);
|
||
|
}
|
||
|
} else { // post the data message and we are not asking for any
|
||
|
// acknowledge.
|
||
|
LPARAM lparamNew = MAKE_DDE_LPARAM(WM_DDE_REQUEST,hdata,aItem);
|
||
|
|
||
|
if (!PostMessageToClient ((HWND)wParam, WM_DDE_DATA, (WPARAM)hwnd, lparamNew)) {
|
||
|
GlobalFree (hdata);
|
||
|
DDEFREE(WM_DDE_REQUEST,lparamNew);
|
||
|
goto RequestErr;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case WM_DESTROY:
|
||
|
DEBUG_OUT ("Srvr: Destroy window",0)
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
DEBUG_OUT ("Srvr: Default message",0)
|
||
|
return DefWindowProc (hwnd, msg, wParam, lParam);
|
||
|
|
||
|
}
|
||
|
|
||
|
return 0L;
|
||
|
|
||
|
}
|
||
|
|
||
|
BOOL INTERNAL HandleInitMsg (
|
||
|
LPSRVR lpsrvr,
|
||
|
LPARAM lParam
|
||
|
){
|
||
|
|
||
|
|
||
|
// If it is not system or Ole, this is not the server.
|
||
|
if (!((aSysTopic == (ATOM)(HIWORD(lParam))) ||
|
||
|
(aOLE == (ATOM)(HIWORD(lParam)))))
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
|
||
|
// single instance MDI accept
|
||
|
if (lpsrvr->useFlags == OLE_SERVER_SINGLE)
|
||
|
return TRUE;
|
||
|
|
||
|
|
||
|
// this server is multiple instance. So, check for any clients or docs.
|
||
|
if (!GetWindow (lpsrvr->hwnd, GW_CHILD) && !lpsrvr->cClients)
|
||
|
return TRUE;
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// AddClient: Adds the client as property to the server
|
||
|
// window. Key is the string generated from the window
|
||
|
// handle and the data is the window itself.
|
||
|
|
||
|
|
||
|
BOOL INTERNAL AddClient (
|
||
|
HWND hwnd,
|
||
|
HANDLE hkey,
|
||
|
HANDLE hdata
|
||
|
){
|
||
|
char buf[20];
|
||
|
|
||
|
MapToHexStr ((LPSTR)buf, hkey);
|
||
|
return SetProp (hwnd, (LPSTR)buf, hdata);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//DeleteClient: deletes the client from the server clients list.
|
||
|
|
||
|
BOOL INTERNAL DeleteClient (
|
||
|
HWND hwnd,
|
||
|
HANDLE hkey
|
||
|
){
|
||
|
char buf[20];
|
||
|
|
||
|
MapToHexStr ((LPSTR)buf, hkey);
|
||
|
return (RemoveProp(hwnd, (LPSTR)buf)!= NULL);
|
||
|
}
|
||
|
|
||
|
// FindClient: Finds whether a given client is
|
||
|
// in the server client list.
|
||
|
|
||
|
HANDLE INTERNAL FindClient (
|
||
|
HWND hwnd,
|
||
|
HANDLE hkey
|
||
|
){
|
||
|
|
||
|
char buf[20];
|
||
|
|
||
|
|
||
|
MapToHexStr ((LPSTR)buf, hkey);
|
||
|
return GetProp (hwnd, (LPSTR)buf);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// SrvrExecute: takes care of the WM_DDE_EXEXCUTE for the
|
||
|
// server.
|
||
|
|
||
|
|
||
|
OLESTATUS INTERNAL SrvrExecute (
|
||
|
HWND hwnd,
|
||
|
HANDLE hdata,
|
||
|
HWND hwndClient
|
||
|
){
|
||
|
ATOM aCmd;
|
||
|
BOOL fActivate;
|
||
|
|
||
|
LPSTR lpdata = NULL;
|
||
|
HANDLE hdup = NULL;
|
||
|
OLESTATUS retval = OLE_ERROR_MEMORY;
|
||
|
|
||
|
LPSTR lpdocname;
|
||
|
LPSTR lptemplate;
|
||
|
|
||
|
LPOLESERVERDOC lpoledoc = NULL;
|
||
|
LPDOC lpdoc = NULL;
|
||
|
LPSRVR lpsrvr;
|
||
|
LPOLESERVER lpolesrvr;
|
||
|
LPSTR lpnextarg;
|
||
|
LPSTR lpclassname;
|
||
|
LPSTR lpitemname;
|
||
|
LPSTR lpopt;
|
||
|
char buf[MAX_STR];
|
||
|
WORD wCmdType;
|
||
|
|
||
|
// !!! this code can be lot simplified if we do the argument scanning
|
||
|
// seperately and return the ptrs to the args. Rewrite later on.
|
||
|
|
||
|
if (!(hdup = DuplicateData (hdata)))
|
||
|
goto errRtn;
|
||
|
|
||
|
if (!(lpdata = GlobalLock (hdup)))
|
||
|
goto errRtn;
|
||
|
|
||
|
DEBUG_OUT (lpdata, 0)
|
||
|
|
||
|
lpsrvr = (LPSRVR)GetWindowLongPtr (hwnd, 0);
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
ASSERT (lpsrvr, "Srvr: srvr does not exist");
|
||
|
#endif
|
||
|
|
||
|
lpolesrvr = lpsrvr->lpolesrvr;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
ASSERT ((CheckPointer (lpolesrvr, WRITE_ACCESS)),
|
||
|
"Srvr: lpolesrvr does not exist");
|
||
|
#endif
|
||
|
|
||
|
if (*lpdata++ != '[') // commands start with the left sqaure bracket
|
||
|
goto errRtn;
|
||
|
|
||
|
retval = OLE_ERROR_SYNTAX;
|
||
|
// scan upto the first arg
|
||
|
if (!(wCmdType = ScanCommand (lpdata, WT_SRVR, &lpdocname, &aCmd)))
|
||
|
goto errRtn;
|
||
|
|
||
|
if (wCmdType == NON_OLE_COMMAND) {
|
||
|
if (!UtilQueryProtocol (lpsrvr->aClass, PROTOCOL_EXECUTE))
|
||
|
retval = OLE_ERROR_PROTOCOL;
|
||
|
else {
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpolesrvr->lpvtbl->Execute,
|
||
|
"Invalid pointer to Exit method")
|
||
|
#endif
|
||
|
|
||
|
retval = (*lpolesrvr->lpvtbl->Execute) (lpolesrvr, hdata);
|
||
|
}
|
||
|
|
||
|
goto errRtn1;
|
||
|
}
|
||
|
|
||
|
if (aCmd == aStdExit){
|
||
|
if (*lpdocname)
|
||
|
goto errRtn1;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpolesrvr->lpvtbl->Exit, "Invalid pointer to Exit method")
|
||
|
#endif
|
||
|
lpsrvr->fAckExit = TRUE;
|
||
|
lpsrvr->hwndExit = hwndClient;
|
||
|
lpsrvr->hDataExit = hdata;
|
||
|
retval = (*lpolesrvr->lpvtbl->Exit) (lpolesrvr);
|
||
|
lpsrvr->fAckExit = FALSE;
|
||
|
goto end2;
|
||
|
}
|
||
|
|
||
|
// scan the next argument.
|
||
|
if (!(lpnextarg = ScanArg(lpdocname)))
|
||
|
goto errRtn;
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// [StdShowItem("docname", "itemname"[, "true"])]
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if (aCmd == aStdShowItem) {
|
||
|
|
||
|
// first find the documnet. If the doc does not exist, then
|
||
|
// blow it off.
|
||
|
|
||
|
if (!(lpdoc = FindDoc (lpsrvr, lpdocname)))
|
||
|
goto errRtn1;
|
||
|
|
||
|
lpitemname = lpnextarg;
|
||
|
|
||
|
if( !(lpopt = ScanArg(lpitemname)))
|
||
|
goto errRtn1;
|
||
|
|
||
|
// scan for the optional parameter
|
||
|
// Optional can be only TRUE or FALSE.
|
||
|
|
||
|
fActivate = FALSE;
|
||
|
if (*lpopt) {
|
||
|
|
||
|
if( !(lpnextarg = ScanBoolArg (lpopt, (BOOL FAR *)&fActivate)))
|
||
|
goto errRtn1;
|
||
|
|
||
|
if (*lpnextarg)
|
||
|
goto errRtn1;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// scan it. But, igonre the arg.
|
||
|
retval = DocShowItem (lpdoc, lpitemname, !fActivate);
|
||
|
goto end2;
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// [StdCloseDocument ("docname")]
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if (aCmd == aStdClose) {
|
||
|
if (!(lpdoc = FindDoc (lpsrvr, lpdocname)))
|
||
|
goto errRtn1;
|
||
|
|
||
|
if (*lpnextarg)
|
||
|
goto errRtn1;
|
||
|
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpdoc->lpoledoc, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERDOC")
|
||
|
else if (!CheckPointer (lpdoc->lpoledoc->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERDOCVTBL")
|
||
|
else
|
||
|
ASSERT (lpdoc->lpoledoc->lpvtbl->Close,
|
||
|
"Invalid pointer to Close method")
|
||
|
#endif
|
||
|
|
||
|
retval = (*lpdoc->lpoledoc->lpvtbl->Close)(lpdoc->lpoledoc);
|
||
|
goto end2;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (aCmd == aStdOpen) {
|
||
|
// find if any document is already open.
|
||
|
// if the doc is open, then no need to call srvr app.
|
||
|
if (FindDoc (lpsrvr, lpdocname)){
|
||
|
retval = OLE_OK;
|
||
|
goto end1;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (aCmd == aStdCreate || aCmd == aStdCreateFromTemplate) {
|
||
|
lpclassname = lpdocname;
|
||
|
lpdocname = lpnextarg;
|
||
|
if( !(lpnextarg = ScanArg(lpdocname)))
|
||
|
goto errRtn1;
|
||
|
|
||
|
}
|
||
|
|
||
|
// check whether we can create/open more than one doc.
|
||
|
|
||
|
if ((lpsrvr->useFlags == OLE_SERVER_MULTI) &&
|
||
|
GetWindow (lpsrvr->hwnd, GW_CHILD))
|
||
|
goto errRtn;
|
||
|
|
||
|
|
||
|
|
||
|
// No Doc. register the document. lpoledoc is being probed
|
||
|
// for validity. So, pass some writeable ptr. It is not
|
||
|
// being used to access anything yet
|
||
|
|
||
|
if (OleRegisterServerDoc ((LHSRVR)lpsrvr, lpdocname,
|
||
|
(LPOLESERVERDOC)NULL, (LHDOC FAR *)&lpdoc))
|
||
|
goto errRtn;
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// [StdOpenDocument ("docname")]
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Documnet does not exit.
|
||
|
|
||
|
if(aCmd == aStdOpen) {
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpolesrvr->lpvtbl->Open, "Invalid pointer to Open method")
|
||
|
#endif
|
||
|
|
||
|
retval = (*lpolesrvr->lpvtbl->Open)(lpolesrvr, (LHDOC)lpdoc,
|
||
|
lpdocname, (LPOLESERVERDOC FAR *) &lpoledoc);
|
||
|
goto end;
|
||
|
}
|
||
|
else {
|
||
|
lpdoc->fEmbed = TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// [StdNewDocument ("classname", "docname")]
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if (aCmd == aStdCreate) {
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpolesrvr->lpvtbl->Create,
|
||
|
"Invalid pointer to Create method")
|
||
|
#endif
|
||
|
retval = (*lpolesrvr->lpvtbl->Create) (lpolesrvr, (LHDOC)lpdoc,
|
||
|
lpclassname, lpdocname,
|
||
|
(LPOLESERVERDOC FAR *) &lpoledoc);
|
||
|
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// [StdEditDocument ("docname")]
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
if (aCmd == aStdEdit){
|
||
|
|
||
|
GlobalGetAtomName (lpsrvr->aClass, (LPSTR)buf, MAX_STR);
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpolesrvr->lpvtbl->Edit, "Invalid pointer to Edit method")
|
||
|
#endif
|
||
|
|
||
|
retval = (*lpolesrvr->lpvtbl->Edit) (lpolesrvr, (LHDOC)lpdoc,
|
||
|
(LPSTR)buf, lpdocname,
|
||
|
(LPOLESERVERDOC FAR *) &lpoledoc);
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// [StdNewFormTemplate ("classname", "docname". "templatename)]
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if (aCmd == aStdCreateFromTemplate){
|
||
|
lptemplate = lpnextarg;
|
||
|
if(!(lpnextarg = ScanArg(lpnextarg)))
|
||
|
goto errRtn;
|
||
|
|
||
|
#ifdef FIREWALLS
|
||
|
if (!CheckPointer (lpolesrvr->lpvtbl, WRITE_ACCESS))
|
||
|
ASSERT (0, "Invalid LPOLESERVERVTBL")
|
||
|
else
|
||
|
ASSERT (lpolesrvr->lpvtbl->CreateFromTemplate,
|
||
|
"Invalid pointer to CreateFromTemplate method")
|
||
|
#endif
|
||
|
retval = (*lpolesrvr->lpvtbl->CreateFromTemplate)(lpolesrvr,
|
||
|
(LHDOC)lpdoc, lpclassname, lpdocname, lptemplate,
|
||
|
(LPOLESERVERDOC FAR *) &lpoledoc);
|
||
|
|
||
|
goto end;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
DEBUG_OUT ("Unknown command", 0);
|
||
|
|
||
|
end:
|
||
|
|
||
|
if (retval != OLE_OK)
|
||
|
goto errRtn;
|
||
|
|
||
|
// Successful execute. remember the server app private doc handle here.
|
||
|
|
||
|
lpdoc->lpoledoc = lpoledoc;
|
||
|
|
||
|
end1:
|
||
|
// make sure that the srg string is indeed terminated by
|
||
|
// NULL.
|
||
|
if (*lpnextarg)
|
||
|
retval = OLE_ERROR_SYNTAX;
|
||
|
|
||
|
errRtn:
|
||
|
|
||
|
if ( retval != OLE_OK){
|
||
|
// delete the oledoc structure
|
||
|
if (lpdoc)
|
||
|
OleRevokeServerDoc ((LHDOC)lpdoc);
|
||
|
}
|
||
|
|
||
|
end2:
|
||
|
errRtn1:
|
||
|
|
||
|
if (lpdata)
|
||
|
GlobalUnlock (hdup);
|
||
|
|
||
|
if (hdup)
|
||
|
GlobalFree (hdup);
|
||
|
|
||
|
if (retval == OLE_OK)
|
||
|
lpsrvr->bnoRelease = TRUE;
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
void SendMsgToChildren (
|
||
|
HWND hwnd,
|
||
|
UINT msg,
|
||
|
WPARAM wParam,
|
||
|
LPARAM lParam
|
||
|
){
|
||
|
|
||
|
hwnd = GetWindow(hwnd, GW_CHILD);
|
||
|
while (hwnd) {
|
||
|
SendMessage (hwnd, msg, wParam, lParam);
|
||
|
hwnd = GetWindow (hwnd, GW_HWNDNEXT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
OLESTATUS INTERNAL RequestDataStd (
|
||
|
LPARAM lparam,
|
||
|
LPHANDLE lphdde
|
||
|
){
|
||
|
|
||
|
char buf[MAX_STR];
|
||
|
ATOM item;
|
||
|
HANDLE hnew = NULL;
|
||
|
|
||
|
if (!(item = (ATOM)(HIWORD (lparam))))
|
||
|
goto errRtn;
|
||
|
|
||
|
GlobalGetAtomName (item, (LPSTR)buf, MAX_STR);
|
||
|
|
||
|
if (item == aEditItems){
|
||
|
hnew = MakeGlobal ((LPSTR)"StdHostNames\tStdDocDimensions\tStdTargetDevice");
|
||
|
goto PostData;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (item == aProtocols) {
|
||
|
hnew = MakeGlobal ((LPSTR)"Embedding\tStdFileEditing");
|
||
|
goto PostData;
|
||
|
}
|
||
|
|
||
|
if (item == aTopics) {
|
||
|
hnew = MakeGlobal ((LPSTR)"Doc");
|
||
|
goto PostData;
|
||
|
}
|
||
|
|
||
|
if (item == aFormats) {
|
||
|
hnew = MakeGlobal ((LPSTR)"Picture\tBitmap");
|
||
|
goto PostData;
|
||
|
}
|
||
|
|
||
|
if (item == aStatus) {
|
||
|
hnew = MakeGlobal ((LPSTR)"Ready");
|
||
|
goto PostData;
|
||
|
}
|
||
|
|
||
|
// format we do not understand.
|
||
|
goto errRtn;
|
||
|
|
||
|
PostData:
|
||
|
|
||
|
// Duplicate the DDE data
|
||
|
if (MakeDDEData (hnew, CF_TEXT, lphdde, TRUE)){
|
||
|
// !!! why are we duplicating the atom.
|
||
|
DuplicateAtom ((ATOM)(HIWORD (lparam)));
|
||
|
return OLE_OK;
|
||
|
}
|
||
|
errRtn:
|
||
|
return OLE_ERROR_MEMORY;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL INTERNAL QueryRelease (
|
||
|
LPSRVR lpsrvr
|
||
|
){
|
||
|
|
||
|
HWND hwnd;
|
||
|
LPDOC lpdoc;
|
||
|
|
||
|
|
||
|
// Incase the terminate is called immediately after
|
||
|
// the Std at sys level clear this.
|
||
|
|
||
|
if (lpsrvr->bnoRelease) {
|
||
|
lpsrvr->bnoRelease = FALSE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (lpsrvr->cClients)
|
||
|
return FALSE;
|
||
|
|
||
|
hwnd = GetWindow (lpsrvr->hwnd, GW_CHILD);
|
||
|
|
||
|
// if either the server or the doc has any clients
|
||
|
// return FALSE;
|
||
|
|
||
|
while (hwnd){
|
||
|
lpdoc = (LPDOC)GetWindowLongPtr (hwnd, 0);
|
||
|
if (lpdoc->cClients)
|
||
|
return FALSE;
|
||
|
|
||
|
hwnd = GetWindow (hwnd, GW_HWNDNEXT);
|
||
|
}
|
||
|
return TRUE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//IsSingleServerInstance: returns true if the app is single server app else
|
||
|
//false.
|
||
|
|
||
|
BOOL INTERNAL IsSingleServerInstance ()
|
||
|
{
|
||
|
HWND hwnd;
|
||
|
WORD cnt = 0;
|
||
|
HANDLE hTask;
|
||
|
char buf[MAX_STR];
|
||
|
|
||
|
|
||
|
hwnd = GetWindow (GetDesktopWindow(), GW_CHILD);
|
||
|
#ifdef WIN16
|
||
|
hTask = GetCurrentTask();
|
||
|
#else //NT
|
||
|
hTask = (HANDLE)ULongToPtr(GetCurrentThreadId());
|
||
|
#endif
|
||
|
|
||
|
while (hwnd) {
|
||
|
if (hTask == GetWindowTask (hwnd)) {
|
||
|
GetClassName (hwnd, (LPSTR)buf, MAX_STR);
|
||
|
if (lstrcmp ((LPSTR)buf, SRVR_CLASS) == 0)
|
||
|
cnt++;
|
||
|
}
|
||
|
hwnd = GetWindow (hwnd, GW_HWNDNEXT);
|
||
|
}
|
||
|
#ifdef FIREWALLS
|
||
|
ASSERT (cnt > 0, "srvr window instance count is zero");
|
||
|
#endif
|
||
|
if (cnt == 1)
|
||
|
return TRUE;
|
||
|
else
|
||
|
return FALSE;
|
||
|
|
||
|
}
|