4.8 Closing a Server Application

The server DLL calls the Exit function in the OLESERVERVTBL structure when the server must quit. The DLL calls the OLESERVERVTBL Release function to inform the server application that it is safe to quit; the server does not necessarily stop when the Release function is called.

Note The server must exit when it is invisible and the server DLL calls Release. The only exception is when an application supports multiple servers; in this case, an invisible server is sometimes not revocable when the DLL calls Release. If the server application has no open documents and it was started with the /Embedding option (indicating that it was started by a client application) the server should exit when the server DLL calls the Release function. If the user explicitly loads a document into a single-instance (MDI) server, however, the server should not exit when the DLL calls Release.

The following code examples shows the OLESERVERVTBL Release callback function, which supports the closing of an invisible server application:

/*

* SrvrRelease SERVER "Release" METHOD

*

* This library calls the SrvrRelease method when it is safe

* to quit the application. Note that the server application

* is not required to quit.

*

* srvrMain.lhsrvr != NULL indicates that SrvrRelease has

* been called because the client is no longer connected,

* not because the server called OleRevokeServer.

* Therefore, only start the revoking process if the

* document is of type doctypeEmbedded or if the server was

* opened for an invisible update.

*

* srvrmain.lhsrvr == NULL indicates that OleRevokeServer

* has already been called (by the server application), and

* srvrMain is a lame duck. It is safe to quit now because

* SrvrRelease has just been called.

*

* Note that this method may be called twice: when

* OleRevokeServer is called in StartRevokingServer,

* SrvrRelease is called again. Therefore we need to be

* reentrant.

*

* LPOLESERVER lpolesrvr - The server structure to release

*

* RETURNS: OLE_OK

*

*/

OLESTATUS FAR PASCAL SrvrRelease (LPOLESERVER lpolesrvr)

{

if (srvrMain.lhsrvr)

{

if (fRevokeSrvrOnSrvrRelease && (docMain.doctype ==

doctypeEmbedded || !IsWindowVisible (hwndMain)))

StartRevokingServer();

}

else

{

fWaitingForSrvrRelease = FALSE;

// Here you should free any memory that had been

// allocated for the server.

PostQuitMessage (0);

}

return OLE_OK;

}

When a user closes a server application containing an embedded object without updating changes to the client application, the server should display a message box asking whether to save the changes. If the user chooses to save the changes, the server should send the OLE_CLOSED notification and call the OleRevokeServerDoc function. (Because sending OLE_CLOSED prompts the server library to send data to the client library, it is not necessary to send OLE_CHANGED or OLE_SAVED). If the user chooses not to save the changes, the server should simply call the OleRevokeServerDoc function (without sending OLE_CLOSED).

A server can use the OleRevokeObject function to revoke a client's access to an object—for example, if the user destroys the object. Similarly, the OleRevokeServerDoc function revokes a client's access to a document. (Because OleRevokeServerDoc revokes a client's access to all objects in a document, an application that uses OleRevokeServerDoc does not need to call the OleRevokeObject function for objects in that document.) To terminate all conversations with client applications, the server application can call the OleRevokeServer function. These functions inform the server DLL that the specified items are no longer available.

A server application can receive OLE_WAIT_FOR_RELEASE—for example, the OleRevokeServerDoc function can return this value. Although a server application can enter a message-dispatch loop and wait for the server DLL to call the server's Release function, server applications should never enter message-dispatch loops inside any of the server callback functions that are called by the server DLL.

Note The client application will not instruct the server application to close the document or exit when the server is editing a linked object (unless the server application is updating the link without displaying the object to the user). Because a linked object exists independently of the client application, the user controls saving and closing the document by using the server application.

If a server application owns the clipboard when it terminates, it should make sure that the data on the clipboard is complete and in the correct order. For example, any Native data should also be accompanied by OwnerLink format.

SRVRDEMO.EXE handles application shutdown by generating a WM_SYSCOMMAND that is handled by the DefWindowProc. DefWindowProc processes the message, translating the SC_CLOSE message into a WM_CLOSE command:

case IDM_EXIT:

SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);

break;/*

.

.

.

case WM_CLOSE:

{

BOOL fUpdateLater;

if (SaveChangesOption(&fUpdateLater) != IDCANCEL)

ExitApplication(fUpdateLater);

break;

/*

* SaveChangesOption

*

* Give the user the opportunity to save changes to the

* current document before continuing.

*

* BOOL *pfUpdateLater - Will be set to TRUE if the client

* does not accept the update and

* needs to be updated when the

* document is closed. In that case,

* OLE_CLOSED will be sent.

*

* RETURNS: IDYES, IDNO, or IDCANCEL

*/

int SaveChangesOption (BOOL *pfUpdateLater)

{

int nReply;

char szBuf[cchFilenameMax];

*pfUpdateLater = FALSE;

if (fDocChanged)

{

char szTmp[cchFilenameMax];

if (docMain.aName)

GlobalGetAtomName (docMain.aName, szTmp, cchFilenameMax);

else

szTmp[0] = NULL;

if (docMain.doctype == doctypeEmbedded)

wsprintf (szBuf, "The object has been changed. \n\nUpdate %s

before closing the object?", Abbrev (szTmp));

else

lstrcpy (szBuf, (LPSTR) "Save changes?");

nReply = MessageBox (hwndMain, szBuf, szAppName,

MB_ICONEXCLAMATION | MB_YESNOCANCEL);

switch (nReply)

{

case IDYES:

if (docMain.doctype != doctypeEmbedded)

SaveDoc();

else

switch (OleSavedServerDoc (docMain.lhdoc))

{

case OLE_ERROR_CANT_UPDATE_CLIENT:

*pfUpdateLater = TRUE;

break;

case OLE_OK:

break;

default:

ErrorBox ("Fatal Error: Cannot update.");

}

return IDYES;

case IDNO:

return IDNO;

case IDCANCEL:

return IDCANCEL;

}

}

return TRUE;

}

* ExitApplication

*

* Handles the WM_CLOSE and WM_COMMAND/IDM_EXIT messages.

*

* RETURNS: TRUE if application should really terminate

* FALSE if not

*

*/

static BOOL ExitApplication (BOOL fUpdateLater)

{

if (fUpdateLater)

{

// The non-standard OLE client did not accept the

// update when we requested it, so we are sending the

// client OLE_CLOSED now that we are closing the

// document.

SendDocMsg (OLE_CLOSED);

}

if (StartRevokingServer() == OLE_WAIT_FOR_RELEASE)

Wait (&fWaitingForSrvrRelease);

// SrvrRelease will not necessarily post a WM_QUIT

// message. If the document is not embedded, SrvrRelease

// by itself does not cause the application to terminate.

// But now we want it to.

if (docMain.doctype != doctypeEmbedded)

PostQuitMessage(0);

SaveDimensions();

return TRUE;

}

/*

* StartRevokingServer

*

* Hide the window, and start to revoke the server.

* Revoking the server will let the library close any registered

* documents.

*

* OleRevokeServer may return OLE_WAIT_FOR_RELEASE.

* Calling StartRevokingServer starts a chain of events that will

* eventually lead to the application being terminated.

*

* RETURNS: The return value from OleRevokeServer

*

*/

OLESTATUS StartRevokingServer (void)

{

OLESTATUS olestatus;

if (srvrMain.lhsrvr)

{

LHSERVER lhserver;

// Hide the window so user can do nothing while we are waiting.

ShowWindow (hwndMain, SW_HIDE);

lhserver = srvrMain.lhsrvr;

// Set lhsrvr to NULL to indicate that srvrMain is a lame duck

// and that if SrvrRelease is called, then it is ok to quit the

// application.

srvrMain.lhsrvr = NULL;

olestatus = OleRevokeServer (lhserver);

}

else

// The programmer should ensure that this never happens.

ErrorBox ("Fatal Error: StartRevokingServer called on NULL

server.");

return olestatus;

}