25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Ads   Donate   Humor

DDEML Client/Server Base Classes for Borland OWL

(ddemlapp project)

A class derived from the Borland OWL 2.0 TApplication class, plus a set of several additional utility classes, which use the Windows Dynamic Data Exchange Management Library (DDEML) to provide object-oriented support for DDE to an OWL application. The application can function as client, server, or both.

ddemlapp.h

/*	ddemlapp.h			01/06/02
	Copyright (C)2000-2002, 2006 Steven Whitney.
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 2 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

This entire set of classes in DDEMLAPP.H/.CPP is needed by any application that
uses the Windows Dynamic Data Exchange Management Library (DDEML), providing
object-oriented, (almost) drop-in support for DDE as client, server, or both.
1/24/06: "(almost) drop-in support" looks like a bit of an overstatement. The classes must be fitted
rather carefully into the app, but it's a lot easier than writing it from scratch.

There is some code in ddemlapp.cpp that was specific to my computer system
and needs removal or customization before use.

------
To Do:

Change all const char* uses to const string&, even if it requires addl code inside
fns.  convenience in calling is more important.

This required some changes to work under Win32, which now haven't been tested in Win16.

------
Notes:

--DDERequest() IS a limiting way to get data.  Much more useful would be a reusable pointer
  to an allocated block, support for DDEExecute() that puts any result into the
  block, and a DDERequestable item, "Result", that returns the result data, whatever it is.
  This is essentially what the MSAccess SQL topic and ALL its table-handling topics are.
  It assembles the result, and your requests return the pieces you ask for.
--With modem routines and internet protocols instead of DDE, this probably is a start
  at the functionality you need for an internet browser and server; writing an internet
  browser doesn't look like much fun!

*/
//----------------------------------------------------------------------------
#ifndef __DDEMLAPP_H
#define __DDEMLAPP_H

#include <owl\owlpch.h>
#include <ddeml.h>
#include <classlib\arrays.h>

#include "c:\bcs\my.h"

//////////////////////////////////////////////////////////////////////////////
// globals and global fn prototypes
//----------------------------------------------------------------------------
enum DDESides { CLIENT, SERVER };

string DDEError(DWORD instid, string comment = "");
string StringFromHSZ(DWORD instid, HSZ hsz);
ostream& operator << (ostream& os, const CONVINFO& c);	// diagnostic output of a CONVINFO

//----------------------------------------------------------------------------
// 							end globals
//////////////////////////////////////////////////////////////////////////////
// stores info about known servers
//
class ServerInfo
{
public:
	ServerInfo(string servicename = "", string windowclass = "",
				string windowtitle = "", string exepath = "");
	ServerInfo(const ServerInfo& other) { *this = other; }
	virtual ~ServerInfo();

	ServerInfo& operator = (const ServerInfo& other);
	BOOL operator == (const ServerInfo& other) const;
	BOOL operator != (const ServerInfo& other) const { return(!(*this == other)); }

	friend ostream& operator << (ostream& os, const ServerInfo& s);	// write
	friend istream& operator >> (istream& is, ServerInfo& s);		// read

	// functions

	// variables
	// I no longer use 2 of these, but keep all for reference; it took some research.
	string ServiceName;  	// base service name it registers with DDEML
	string WindowClass;		// as reported by WinSight
	string WindowTitle;  	// bare title, without any open document appended
	string ExePath;         // location of .EXE needed to run it

protected:

};
//----------------------------------------------------------------------------
// 							end class ServerInfo
//////////////////////////////////////////////////////////////////////////////
// A triplet of Service, Topic, and Item for which this SERVER can provide DDE data.
// It also appears to track update intervals and help manage the associated data.
// KEEP ONLY IN A TIARRAY, AND USE ONLY THE EXACT OBJECTS YOU CREATE.
//
// With this organization, I WILL be able to report which items are available for any topic,
// a desirable capability for a server.
//
// It's unclear whether an app can allow > 1 service name per app instance.  If not,
// the Service member may be unnecessary, but don't remove it!  Currently, I just
// supply the 1 service name that I do support.
//
class ServiceTopicItem
{
public:
	ServiceTopicItem(DWORD instid = 0, const char* service = 0, const char* topic = 0,
					const char* item = 0);
	ServiceTopicItem(const ServiceTopicItem& other) { *this = other; }
	virtual ~ServiceTopicItem();

	ServiceTopicItem& operator = (const ServiceTopicItem& other);
	BOOL operator == (const ServiceTopicItem& other) const;
	BOOL operator < (const ServiceTopicItem& other) const;
	BOOL operator != (const ServiceTopicItem& other) const { return(!(*this == other)); }
	operator HSZPAIR() const;

	// functions
	HSZ GetService() { return(Service); }					// for public access
	HSZ GetTopic() { return(Topic); }
	HSZ GetItem() { return(Item); }
	const string& GetServiceText() { return(ServiceText); }
	const string& GetTopicText() { return(TopicText); }
	const string& GetItemText() { return(ItemText); }

	BOOL IsGood() const { return(InstId && Service && Topic && Item); }
	BOOL SameAs(const ServiceTopicItem& other) const;		// alternative to operator ==
	BOOL SameAs(HSZ service, HSZ topic, HSZ item) const;	// another alternative
	BOOL NeedsUpdate();
	BOOL Advise();
	BOOL MatchService(HSZ service);   			// also sets Marked if it matches
	BOOL MatchTopic(HSZ topic);
	BOOL MatchItem(HSZ item);
	BOOL MatchServiceAndTopic(HSZ service, HSZ topic);
	BOOL MatchTopicAndItem(HSZ topic, HSZ item);

	// variables
	BOOL Marked;     		// set by the Match fns; also general purpose for any use
	BOOL IsDirty;			// whether the data associated with this topic has changed
	BOOL AutoAdvise;		// master switch: if true, server sends auto-updates on this item,
							// if true, maybe prevent its deletion from Items[],
							// and thus might also affect CanClose(), i.e. don't close
							// server if another app requires its auto-updates.
	ulong AdviseInterval;	// number of milliseconds between data updates
	ulong LastUpdate;		// time last data update was sent

	static ulong DefaultAdviseInterval;

protected:
	// functions
	BOOL CreateAllStrings(const char* service, const char* topic, const char* item);
	void FreeAllStrings();

	// variables			// it is important that you not change any of these after creation
	DWORD InstId;    		// a copy of the App's DDE instance ID, for quick access
	string ServiceText, TopicText, ItemText;
	HSZ Service, Topic, Item;

};
//----------------------------------------------------------------------------
// 							end class ServiceTopicItem
//////////////////////////////////////////////////////////////////////////////
// SDDEConv provides functions necessary to conduct a DDEML conversation.
// An individual SDDEConv only handles one side of one conversation, and thus
// can only be client-only or server-only.
//
// beware: if you COPY a SDDEConv and delete the original (or it goes out of scope),
// the copy will have no functionality because everything was already freed in the dtor.
// ALWAYS CREATE THEM DYNAMICALLY WITH NEW() AND USE ONLY THE EXACT OBJECT YOU CREATE.
// always use only in indirect containers or direct containers of pointers.
//
class SDDEConv
{
public:
	// For a usable SDDEConv, you MUST supply required parameters.
	//
	// for CLIENT side, you MUST provide: instid, servicename, topic
	// for SERVER side, you MUST provide: instid, ddeside as SERVER, hconv
	// 		(you receive hconv when you're notified by DDEML of the new conv)
	//
	SDDEConv(DWORD instid = 0, string servicename = "", string topic = "",
				DDESides ddeside = CLIENT, HCONV hconv = 0);
	virtual ~SDDEConv();

	// desirable that these 2 fns never be called, which is tested by declaring and not defining them.
	SDDEConv(const SDDEConv& other) : Fields(10,0,10) { *this = other; }
	SDDEConv& operator = (const SDDEConv& other);				// intentionally not defined

	BOOL operator == (const SDDEConv& other) const;
	BOOL operator != (const SDDEConv& other) const { return(!(*this == other)); }
	operator HCONV() const { return(HConv); }
	operator string() const;     		// the last retrieved data, if any, or a status msg

	// (to output on 1 line, use os << (string)c instead of os << c )
	friend ostream& operator << (ostream& os, const SDDEConv&);	// 1 field to a line
	friend istream& operator >> (istream& is, SDDEConv&);

	// CLIENT FUNCTIONS
	BOOL DDERequestAsync(const string& itemname);  // delayed data return (DON'T USE THIS FN)
	BOOL DDERequest(const string& itemname);				// immediate data return
	string DDERequestString(const string& itemname);		// returns string or error msg
	BOOL DDEPoke(const string& itemname, const string& pokestring);	// poke string to server
	BOOL DDEExecute(const string& command);							// send command to server
	BOOL DDEAdvise(const string& itemname, BOOL start);		// start/stop data auto-updates
	BOOL ReceiveData(HDDEDATA);								// retrieve requested data
	const char HUGE* DataPtr() { return(Data); }			// get pointer to Data

	// CLIENT/SERVER FUNCTIONS
	const char HUGE* SetData(const char* text);	// set Data to text (text must be char*)
	BOOL IsGood() { return(HConv != 0); }

	// CLIENT/SERVER VARIABLES
	HCONV HConv;      			// the Windows handle of this conversation
	DDESides DDESide;			// side this SDDEConv handles: CLIENT-ONLY or SERVER-ONLY
	BOOL Disconnecting;			// set TRUE while this SDDEConv is INITIATING a disconnect

	// CLIENT VARIABLES
	ulong DataSize;					// actual length of the data in Data
	BOOL DataReceived;				// whether requested asynch data has been received
	BOOL AutoAdvise;				// whether this CLIENT has REQUESTED auto-updates FROM server
	TArrayAsVector<string> Fields;	// Data fields, broken into strings at tab chars
									// for getting specific fields from database table lines

protected:
	// CLIENT/SERVER VARIABLES
	DWORD InstId;    		// a copy of the App's DDEML instance ID, for quick access

	// CLIENT VARIABLES
	HSZ Service;			// the name of the server in the conversation
	HSZ Topic;				// the topic of this conversation
	DWORD LastTransId;		// in case of multiple pending asynch transactions
	char HUGE* Data;		// to accomodate any size data block
							// requested/retrieved data resides IN the Conv that requested it
							// Data itself must be a valid pointer always, never 0,
							// AND always point to a null-terminated string.
};
//----------------------------------------------------------------------------
// 							end class SDDEConv
//////////////////////////////////////////////////////////////////////////////
// Use SDDEHandler as a base class for your main window (or its client) to provide DDE
// functionality in your app.  It initiates, manages, and terminates DDE conversations,
// and responds to inquiries from the DDEML.
//
// It looks like _OWLCLASS expands to  _OWLFASTTHIS  __import, and
//  _OWLFASTTHIS  expands to __fastthis for Win16 and to a null string in Win32
// (which is always fastthis).  I think import means I intend to import THIS class FROM
// a DLL, which I don't. (But how can you ever import FROM a DLL a class that you are
// only declaring NOW?) Anyway, presumably if it doesn't find it in any DLL, it uses
// what I provide here.  But be aware I may be using this macro wrong,
// and it could relate to the Win32 crash bug.  It would probably be better to
// use exactly what I intend (which will take some experimenting to determine:
// __fastthis may be sufficient, but _FASTTHIS is also available, not documented anywhere!
//
// 1/24/06: I don't remember what the "Win32 crash bug" was.
//
class _OWLCLASS SDDEHandler
{
public:
	// THE DEFAULT VALUES ARE ONLY FOR ARRAY USE.  OTHERWISE YOU MUST SUPPLY VALID VALUES.
	SDDEHandler(DWORD instid = 0, const char* servicename = 0);
	virtual ~SDDEHandler();

	// CLIENT FUNCTIONS
	SDDEConv* DDEInitiate(const string& servicename, const string& topic);
	void DDETerminate(SDDEConv*& channel);
	void DDETerminateAll();

	// SERVER FUNCTIONS
	BOOL RegisterServiceName(HSZ servicename);
	BOOL UnRegisterServiceName(HSZ servicename);

	// SERVER FUNCTIONS THAT MANAGE OR ARE MAINLY CONCERNED WITH THE ITEMS[] ARRAY
	int AddItem(const char* service, const char* topic, const char* item);
	uint CountMarkedItems() const;
	virtual uint CountServiceAndTopic(HSZ service, HSZ topic);	// also marks
	virtual uint CountTopicAndItem(HSZ topic, HSZ item);		// also marks
	ServiceTopicItem* FindServiceTopicItem(HSZ service, HSZ topic, HSZ item) const;
	HDDEDATA WildConnect(HSZ topic, HSZ service, WORD format);
	int UpdateData();

	// CLIENT/SERVER FUNCTIONS
	SDDEConv* FindDDEConv(HCONV hConv, DDESides side);
	void ShutDown(); // empty arrays, close all open conversations, unregister service name

	// not really a callback fn, this is called BY the App's real DdeCallBack function,
	// and does the actual processing work.  This fn is a fully functional placeholder;
	// a derived class can provide an overriding version to ADD to its capabilities.
	// If you provide an overriding version, it will always be called (and this one ignored),
	// even if you call it through a base-class SDDEHandler*, because it's virtual.
	virtual HDDEDATA DDECallBack(WORD type, WORD format, HCONV hConv,
								HSZ topic, HSZ itemorservice,
								HDDEDATA hData, DWORD data1, DWORD data2);

protected:
	// SERVER FUNCTIONS
	// these 2 MUST be virtual because any app-specific behavior must be provided in a
	// derived-class version, which must get called even when the fn is called from HERE.

	virtual HDDEDATA ItemData(HSZ service, HSZ topic, HSZ item, WORD format);
	virtual HDDEDATA DoDDEExecute(HSZ topic, HDDEDATA hData);

	// CLIENT/SERVER VARIABLES
	DDESides DDESide;			// SDDEHandler can handle CLIENT or SERVER(both) sides
	DWORD InstId;   			// a copy of the App's DDE Instance ID

	// SERVER VARIABLES
	HSZ ServiceName;			// a copy of the App's ServiceName, as HSZ

	// items the app can provide data for.  Must be sorted to avoid returning
	// duplicate HSZPAIRs for wildcard matches.
	TISArrayAsVector<ServiceTopicItem> Items;

	// known servers. could maintain list in a .INI file, and update as new ones become known
	static TArrayAsVector<ServerInfo> Servers;

private:

	// CLIENT VARIABLES
	// This array is only so handler can match incoming asynch data with the requests it sent.
	// Maintain your own separate pointer for each channel you create (e.g. Chan1, Chan2...)
	TIArrayAsVector<SDDEConv> Chan;	// currently active DDE conversations (channels)

};
//----------------------------------------------------------------------------
// 							end class SDDEHandler
//////////////////////////////////////////////////////////////////////////////
// Derive your DDEML application object from this instead of from TApplication.
//
class SDDEApplication : public TApplication
{
public:
	// DDESIDE CAN BE CLIENT(ONLY) OR SERVER(SERVER=BOTH)
	SDDEApplication(const char far *title,DDESides ddeside = CLIENT,const char* servicename = 0)
		: TApplication(title)
	{
		InstId = 0;			// it must pass the 0 to Windows, which assigns its real value
		DDESide = ddeside;
		if(servicename && (DDESide != CLIENT))	// client-only can't be a server.
			BaseServiceName = servicename;      // an empty base name is a client-only flag
	}
	static HDDEDATA FAR PASCAL _export
	DDECallBack(WORD type, WORD format, HCONV hConv, HSZ topic, HSZ itemorservice,
					HDDEDATA hData, DWORD data1, DWORD data2);

protected:
	// functions
	virtual void InitMainWindow();          	// in derived, don't call this
	virtual void InitInstance();				// in derived, call this FIRST
	virtual int TermInstance(int status);   	// in derived, call this LAST
	virtual BOOL IdleAction(long idlecount);	// in derived, do call this, anytime

	// variables
	DWORD InstId;			// Windows-supplied instance identifier, for DDEML use
	DDESides DDESide;       // either CLIENT or SERVER(=both)
	string BaseServiceName;	// only if it's a SERVER, the name to register with DDEML

	static TArrayAsVector<SDDEHandler*> SDDEHandlers; // SDDEHandlers of the running instances

DECLARE_RESPONSE_TABLE(SDDEApplication);
};
//----------------------------------------------------------------------------
// 						end class SDDEApplication
//////////////////////////////////////////////////////////////////////////////
#endif			// __DDEMLAPP_H

ddemlapp.cpp

/*  ddemlapp.cpp			03-06-02
	Copyright (C)2000-2002, 2006 Steven Whitney.
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 2 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Code for classes declared in ddemlapp.h.

*/
//----------------------------------------------------------------------------
#include "c:\bcs\library\ddemlapp.h"

//////////////////////////////////////////////////////////////////////////////
// global fns. (don't move to mylib.cpp; their only usefulness is here)
//----------------------------------------------------------------------------
// handles and logs DDE errors.  keep as global fn.
string DDEError(DWORD instid, string comment)
{
UINT error = DdeGetLastError(instid);  		// this also resets last error to NO_ERROR
ostrstream os;
os << hex << error << ends;

string s = string("DDE error #") + tostring(error) + " (0x" + os.str() + "), " + comment;
logerror(s,"error.log",TRUE);
// ::MessageBox(GetFocus(), s.c_str(), "DDE Error Logged", MB_ICONSTOP);

delete[] os.str();
return(s);
}		  					//DDEError
//----------------------------------------------------------------------------
// returns a string created from the given HSZ.  keep as global fn.
string StringFromHSZ(DWORD instid, HSZ hsz)
{
string s;
if(!instid || !hsz)
	return(s);
uint bufsize = (uint)DdeQueryString(instid,hsz,0,0,CP_WINANSI);	// returns DWORD (ulong)
if(bufsize)
{
	char* buf = new char[bufsize + 1];
	buf[0] = 0;							// in case query doesn't set buf when it fails
	if(DdeQueryString(instid, hsz, buf, bufsize + 1, CP_WINANSI))
		s = buf;
	else
		DDEError(instid,"global StringFromHSZ");
	delete[] buf;
}
else
	DDEError(instid,"global StringFromHSZ");
return(s);
}    						//StringFromHSZ
//----------------------------------------------------------------------------
// diagnostic output of a CONVINFO's members in text format.
// #ERROR NEED AN INSTID FOR ALL THE STRINGFROMHSZ CALLS.  THIS << FN MAY BE OVERKILL.
// keep until sure it's useless.
#pragma argsused
ostream& operator << (ostream& os, const CONVINFO& c)
{
#if 0

// Win16

os << "SEE HELP|CONVINFO TO DECODE BITMAPPED MEMBERS." << endl;
os << "Length of the structure, in bytes:  " << c.cb << endl;
os << "Identifier of application-defined data:  " << c.hUser << endl;
os << "Partner application in the DDE conversation:  " << "c.hConvPartner" << endl;
os << "Service name of the partner application:  " << StringFromHSZ(c.hszSvcPartner) << endl;
os << "Service name of the server application that was requested for connection:  " <<
		StringFromHSZ(c.hszServiceReq) << endl;
os << "Name of the requested topic:  " << StringFromHSZ(c.hszTopic) << endl;
os << "Name of the requested item:  " << StringFromHSZ(c.hszItem) << endl;
os << "Format of the data being exchanged:  " << c.wFmt << endl;
os << "Type of the current transaction:  " << c.wType << endl;
os << "Status of the current conversation:  " << c.wStatus << endl;
os << "Conversation state:  " << c.wConvst << endl;
os << "Error value associated with the last transaction:  " << c.wLastError << endl;
os << "If in a conversation list, identifies the list:  " << c.hConvList << endl;
os << "Conversation context not shown:  " << "c.ConvCtxt" << endl;
os << endl;

#endif // 0

return(os);
}               	//operator << (ostream& os, const CONVINFO& c)
//----------------------------------------------------------------------------
// 							end global functions
//////////////////////////////////////////////////////////////////////////////
// class ServerInfo code
//----------------------------------------------------------------------------
// constructor
ServerInfo::ServerInfo(string servicename, string windowclass, string windowtitle,
						string exepath)
{
ServiceName = servicename;
WindowClass = windowclass;
WindowTitle = windowtitle;
ExePath = exepath;
}                       	//constructor
//----------------------------------------------------------------------------
// destructor
ServerInfo::~ServerInfo()
{
}                       	//destructor
//----------------------------------------------------------------------------
ServerInfo& ServerInfo::operator = (const ServerInfo& other)
{
if(this == &other)
	return(*this);

ServiceName = other.ServiceName;
WindowClass = other.WindowClass;
WindowTitle = other.WindowTitle;
ExePath = other.ExePath;

return(*this);
}                    		//assignment operator
//----------------------------------------------------------------------------
// compares only the servicename, for use by Find(), to look up an existing
// server using only the servicename as the search string.
BOOL ServerInfo::operator == (const ServerInfo& other) const
{
	return(ServiceName == other.ServiceName);
}                        	//operator ==
//----------------------------------------------------------------------------
// output to a stream
ostream& operator << (ostream& os, ServerInfo& )
{
// you usually let the caller add the endl
os << "";
return(os);
}                 			//operator <<
//----------------------------------------------------------------------------
// read from a stream
// be sure to set ALL members, even those not read from the stream
istream& operator >> (istream& is, ServerInfo& )
{
return(is);
}                  			//operator >>
//----------------------------------------------------------------------------
// 						end class ServerInfo
//////////////////////////////////////////////////////////////////////////////
// class ServiceTopicItem code
//----------------------------------------------------------------------------
// static members
ulong ServiceTopicItem::DefaultAdviseInterval = 60000L;	// 1 minute default interval
//----------------------------------------------------------------------------
// constructor
ServiceTopicItem::ServiceTopicItem(DWORD instid, const char* service, const char* topic,
									const char* item)
{
Marked = FALSE;
AutoAdvise = FALSE;
AdviseInterval = DefaultAdviseInterval;
LastUpdate = 0;
IsDirty = FALSE;
InstId = instid;
Service = Topic = Item = 0;

CreateAllStrings(service, topic, item);
}                       	//constructor
//----------------------------------------------------------------------------
// destructor
ServiceTopicItem::~ServiceTopicItem()
{
FreeAllStrings();
}                       	//destructor
//----------------------------------------------------------------------------
ServiceTopicItem& ServiceTopicItem::operator = (const ServiceTopicItem& other)
{
if(this == &other)
	return(*this);

// if these have existing nonzero values, you must DdeFreeStringHandles first
// to ensure they are freed as many times as they are created.
FreeAllStrings();						// also zeroes them

// now initialize to match other
InstId = other.InstId;
CreateAllStrings(other.ServiceText.c_str(), other.TopicText.c_str(), other.ItemText.c_str());

return(*this);
}                    		//assignment operator
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::operator == (const ServiceTopicItem& other) const
{
	return(&other == this);	// for use in TIArray
}                        	//operator ==
//----------------------------------------------------------------------------
// alphabetic sort by service,topic,item
BOOL ServiceTopicItem::operator < (const ServiceTopicItem& other) const
{
if(ServiceText < other.ServiceText) return(TRUE);
if(ServiceText > other.ServiceText) return(FALSE);
if(TopicText < other.TopicText)     return(TRUE);
if(TopicText > other.TopicText)     return(FALSE);
return(ItemText < other.ItemText);
}                        	//operator <
//----------------------------------------------------------------------------
ServiceTopicItem::operator HSZPAIR() const
{
	HSZPAIR h = { Service, Topic };
	return(h);
}               			//operator HSZPAIR()
//----------------------------------------------------------------------------
// alternative to operator ==
BOOL ServiceTopicItem::SameAs(const ServiceTopicItem& other) const
{
if(&other == this) return(TRUE);
return(SameAs(other.Service, other.Topic, other.Item));
}             				//SameAs
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::SameAs(HSZ service, HSZ topic, HSZ item) const
{
return(	(DdeCmpStringHandles(Service, service) == 0) &&
		(DdeCmpStringHandles(Topic, topic) == 0) &&
		(DdeCmpStringHandles(Item, item) == 0) );
}             				//SameAs
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::CreateAllStrings(const char* service,const char* topic,const char* item)
{
if(InstId && service && topic && item)
{
	ServiceText = service; TopicText = topic; ItemText = item;

	// Win32 requires nonconst char*
	char* s = strnewdup(service);
	char* t = strnewdup(topic);
	char* i = strnewdup(item);

	Service = DdeCreateStringHandle(InstId, s, CP_WINANSI);
	Topic = DdeCreateStringHandle(InstId, t, CP_WINANSI);
	Item = DdeCreateStringHandle(InstId, i, CP_WINANSI);

	delete[] s;
	delete[] t;
	delete[] i;

	if(Service && Topic && Item)
		return(TRUE);
	DDEError(InstId,"CreateAllStrings");
}
return(FALSE);
}         					//CreateAllStrings
//----------------------------------------------------------------------------
void ServiceTopicItem::FreeAllStrings()
{
if(InstId)
{
	BOOL ok = TRUE;
	if(Service && !DdeFreeStringHandle(InstId, Service))	ok = FALSE;
	if(Topic &&   !DdeFreeStringHandle(InstId, Topic))		ok = FALSE;
	if(Item &&    !DdeFreeStringHandle(InstId, Item))		ok = FALSE;
	if(!ok)		  DDEError(InstId,"FreeAllStrings");
}
Service = Topic = Item = 0;						// they're invalid now, anyway
}          					//FreeAllStrings
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::NeedsUpdate()
{
if(AutoAdvise)
	if(IsDirty || ((GetTickCount() - LastUpdate) >= AdviseInterval))
		return(TRUE);
return(FALSE);
}							//NeedsUpdate
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::Advise()
{
// Note that DdePostAdvise doesn't receive the service name.  It does receive the instance.
// This implies that Topic/Item must be unique within an instance, and thus that
// each instance can only support ONE service name.  Not sure whether that would
// be the base name or the Win-assigned instance-specific name.  In either case,
// your app can't arbitrarily register a bunch of synonyms for itself(?).
// If true, it means the Service member of ServiceTopicItem is unnecessary, since
// each instance has its own Items[] array, anyway.
// However, DON'T delete Service; if I'm wrong, it might later be useful.

// When processing the XTYP_ADVREQ message in the callback, we effectively know
// the instance (and thus service name) because the App's static callback fn has already
// routed the message TO the instance that owns the conversation.  So you're
// already IN the correct instance, and can be concerned with only topic and item.

BOOL result = (DdePostAdvise(InstId,Topic,Item) != 0); 	// nonzero = success
if(!result)
	DDEError(InstId,"Advise");
LastUpdate = GetTickCount();	// technically not updated just yet, but will be very soon
return(result);
}								//Advise
//----------------------------------------------------------------------------
// these functions are used for marking subsets of an array's elements, and support
// wildcard comparisons.  An HSZ arg passed as 0 will match anything.
// Each fn resets Marked, overwriting any previous setting, so you can use in the same way
// both the simple (1 arg) OR compound (2 args) fns.  Each call to a simple fn
// sets Marked, but in a compound fn, the final result overrides the setting
// from both of the simple fns, so Marked is always left with the final result.
// You can use one of the SDDEHandler::Count... fns to mark and get the number of
// matches, then go through the array retrieving the marked elements.
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::MatchService(HSZ service)
{
Marked = ((service == 0) || (DdeCmpStringHandles(Service, service) == 0));
return(Marked);
}
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::MatchTopic(HSZ topic)
{
Marked = ((topic == 0) || (DdeCmpStringHandles(Topic, topic) == 0));
return(Marked);
}
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::MatchItem(HSZ item)
{
Marked = ((item == 0) || (DdeCmpStringHandles(Item, item) == 0));
return(Marked);
}
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::MatchServiceAndTopic(HSZ service, HSZ topic)
{
// if service or topic is 0, its corresponding Match fn will return TRUE
Marked = (MatchService(service) && MatchTopic(topic));
return(Marked);
}
//----------------------------------------------------------------------------
BOOL ServiceTopicItem::MatchTopicAndItem(HSZ topic, HSZ item)
{
Marked = (MatchTopic(topic) && MatchItem(item));
return(Marked);
}
//----------------------------------------------------------------------------
// 						end class ServiceTopicItem
//////////////////////////////////////////////////////////////////////////////
// class SDDEConv code
//----------------------------------------------------------------------------
// constructor
SDDEConv::SDDEConv(DWORD instid,string servicename,string topic,DDESides ddeside,HCONV hconv)
	: Fields(10,0,10)
{
Data = new HUGE char[128];	// Data will always have a value and contain most recent data
Data[0] = 0;
DataReceived = TRUE;
DDESide = ddeside;
HConv = 0;
if(DDESide == SERVER)				// CLIENT hconv MUST be 0
{
	HConv = hconv;
	SetData("Error: This is server side. Data contains only this text.");
}
else
	SetData("DDE Channel Not Valid");	// default initial message
InstId = instid;
LastTransId = 0;
AutoAdvise = FALSE;
Disconnecting = FALSE;

Service = 0;	// do initialize these in case their real initializations are bypassed
Topic = 0;

// if either is the default 0, this object will be nonfunctional.  you must be careful here
// not to attempt any Windows initialization that you know in advance will fail anyway.
// (when used as a default ctor for array use, this block will be skipped)
if(InstId && servicename.length() && topic.length())
{
	// Service and Topic will be set, if provided, for SERVER (can be useful for reference),
	// but only CLIENT can connect.

	// Win32 requires nonconst char*
	char* s = strnewdup(servicename.c_str());
	char* t = strnewdup(topic.c_str());

	Service = DdeCreateStringHandle(InstId, s, CP_WINANSI);
	Topic = DdeCreateStringHandle(InstId, t, CP_WINANSI);

	delete[] s;
	delete[] t;

	if(!Service || !Topic)
		DDEError(InstId,"Error in SDDEConv::SDDEConv constructor");
	if(Service && Topic && (DDESide == CLIENT))
	{
		// if this fails, HConv is 0, and this Conversation is no good.
		// Service can be base name or instance-specific, if you know it.
		HConv = DdeConnect(InstId, Service, Topic, NULL);
	}
}
if(IsGood())
	SetData("No Data Received Yet From Server");
else
	// having to auto-launch a server generates 1 to 2 of these, as error 0x400A,
	// even if it succeeds.  It's not an error you normally need to track down.
	// DDEError(InstId,"SDDEConv::SDDEConv error");
	DdeGetLastError(instid);  		// reset last error to NO_ERROR, without logging it

}                       		//constructor
//----------------------------------------------------------------------------
// destructor
SDDEConv::~SDDEConv()
{
// only the initiating partner disconnects, and only if valid HConv
// So you can prevent disconnect by zeroing HConv (see DDETerminate)
if(HConv && Disconnecting)
	if(!DdeDisconnect(HConv))
		DDEError(InstId,"~SDDEConv DdeDisconnect error");
if(InstId)
{
	BOOL ok = TRUE;
	if(Service && !DdeFreeStringHandle(InstId, Service)) ok = FALSE;
	if(Topic &&   !DdeFreeStringHandle(InstId, Topic))	 ok = FALSE;
	if(!ok)		  DDEError(InstId,"~SDDEConv DdeFreeStringHandle");
	InstId = 0;
}
delete[] Data;
// this seemingly unnecessary cleanup tries to ensure that if user is maintaining a separate
// pointer to this object and accidentally tries to re-delete it through that pointer,
// OR tries to reuse this object via that now-invalid pointer, this object will at least
// recognize itself as invalid and nonfunctional and do as little damage as possible.
Data = 0;
DataSize = 0;
Service = Topic = 0;
HConv = 0;
AutoAdvise = FALSE;
}                       		//destructor
//----------------------------------------------------------------------------
BOOL SDDEConv::operator == (const SDDEConv& other) const
{
	return(&other == this); // objects are unique, for TIArray use
}                        	//operator ==
//----------------------------------------------------------------------------
// SDDEConv& SDDEConv::operator = (const SDDEConv& other)
// {
// if(this == &other)
// 	return(*this);
// write code for this only if you MUST, and even then try to avoid it!
// return(*this);
// }                    		//assignment operator
//----------------------------------------------------------------------------
SDDEConv::operator string() const
{
string s("Data Too Long For String");
if(!DataReceived)
	s = "Requested Async Data Not Yet Received From Server";
else
	if(DataSize < 65000L)						// platform doesn't change string size limit
		s = string((const char far*)Data);
return(s);
}                        		//operator string()
//----------------------------------------------------------------------------
ostream& operator << (ostream& os, const SDDEConv& c)
{
for(int i = 0 ; i < c.Fields.GetItemsInContainer() ; i++)
	os << c.Fields[i] << endl;					// 1 field to a line
return(os);
}                 				//operator <<
//----------------------------------------------------------------------------
istream& operator >> (istream& is, SDDEConv&)
{
return(is);
}                  				//operator >>
//----------------------------------------------------------------------------
// SDDECONV CLIENT/SERVER FUNCTIONS
//----------------------------------------------------------------------------
// set the text in Data, and adjust DataSize to match.  Setting to "" null string is ok.
// use SetData(0); to fill Fields[] array with current contents of Data.
const char HUGE* SDDEConv::SetData(const char* text)
{
// if text is nonzero, copy it into data.
if(text && ((const char HUGE*)text != Data))	// prevent disaster
{
	DataSize = strlen(text) + 1;
	delete[] Data;
	Data = new HUGE char[DataSize];
	strcpy((char far*)Data,text);
}
// regardless of whether text was provided, use existing Data to fill Fields[]
Fields.Flush();					// empty array
string s;
istrstream is((char far*)Data);
while(is)
{
	// #error test this carefully and document in help.
	// both getline and read_to_delim can return stream failure even if s received data
	// also see note at ReceiveData().
	// note that ANY embedded tabs (including within strings) in Data
	// will make this malfunction.
	// my string operator >> might be useful, but it can't read up to next TAB char.

	s.remove(0);
	getline(is,s,'\t');			// maybe preferable over read_to_delim

	// s.length() can be zero either because the stream failed OR you got
	// two consecutive tabs for a field whose contents are null.  These interior
	// blank fields must retain their proper position in the field list.  Therefore,
	// you Add() if s received ANY data OR if it received the data for a blank
	// field (nothing), indicated by the stream still being good.  You are only sure
	// you're done when s has nothing AND the stream is failed.

	if(s.length() || is.good())
		Fields.Add(s);
}
return(Data);
}            					//SetData
//----------------------------------------------------------------------------
// SDDECONV CLIENT FUNCTIONS
//----------------------------------------------------------------------------
// Request a data item. ReceiveData will be called asynchronously by the callback.
// Be sure your SDDEConv object exists until you are sure you have received the result.
// returns TRUE if transaction was successful, else FALSE.
// THERE IS *RARELY* ANY REASON TO USE THIS FUNCTION.  After creating and testing it for the
// demo app, I've spent no time giving it additional capabilities it needs.
// It works IF you know your request will succeed, but it can't report why if a request
// fails, and can't tell the difference between a failed request and a null string result.
// Also, since you don't get your data immediately, there is no error handling or reporting,
// such as to distinguish between MSAccess "null data" and "end of table" error.
BOOL SDDEConv::DDERequestAsync(const string& itemname)
{
if(!IsGood() || (DDESide == SERVER))
	return(FALSE);

// Win32 requires nonconst char*
char* i = strnewdup(itemname.c_str());
HSZ Item = DdeCreateStringHandle(InstId, i, CP_WINANSI);
delete[] i;

if(!Item)
{
	DDEError(InstId,"DDERequestAsync");
	return(FALSE);
}
int result = FALSE;
if(DdeClientTransaction(0,0,HConv,Item,CF_TEXT, XTYP_REQUEST, TIMEOUT_ASYNC, &LastTransId))
{
	result = TRUE;
	DataReceived = FALSE;
}
else
	DDEError(InstId,"DDERequestAsync");
DdeFreeStringHandle(InstId, Item);
return(result);
}                     			//DDERequestAsync
//----------------------------------------------------------------------------
// Request and receive a data item (SYNCHRONOUSLY -- immediate return).
// Returns a string containing the data, or error string "Data Request Failed".
// (the error string is set in ReceiveData())
//
// When processing an MSAccess table or query, you can use:
// (any received data is IN the Conv's Data itself, AS WELL AS IN the returned string)
// while(Chan1->DDERequestString("NextRow") != "Data Request Failed")
// 	ProcessRow();
//
string SDDEConv::DDERequestString(const string& itemname)
{
DDERequest(itemname);
return((string)(*this));
}       						//DDERequestString
//----------------------------------------------------------------------------
// Request and receive a data item (SYNCHRONOUSLY -- immediate return).
// returns TRUE if the server returned data, FALSE if it refused the request.
// (in MSAccess, the refusal means the end of a table has been reached.)
//
// ALWAYS USE THIS FUNCTION (or DDERequestString) FOR DDE REQUESTS.
//
// When processing an MSAccess table/query, you can use: (any received data is IN the Conv)
// while(Chan1->DDERequest("NextRow")) 	// while() or if()
// 	s = (string)(*Chan1);
//
BOOL SDDEConv::DDERequest(const string& itemname)
{
SetData("");	// prevent mistaking previous data as the new result

if(!IsGood() || (DDESide == SERVER))
	return(FALSE);

// Win32 requires nonconst char*
char* i = strnewdup(itemname.c_str());
HSZ Item = DdeCreateStringHandle(InstId, i, CP_WINANSI);
delete[] i;

if(!Item)
{
	DDEError(InstId,"DDERequest");
	return(FALSE);
}
HDDEDATA hData = DdeClientTransaction(0, 0, HConv, Item, CF_TEXT, XTYP_REQUEST, 60000L, 0);
DdeFreeStringHandle(InstId, Item);
ReceiveData(hData);							// do call this, even if handle is null
if(hData)
{
	if(!DdeFreeDataHandle(hData))			// for synch, you must free it HERE
		DDEError(InstId,"DDERequest");
	return(TRUE);
}
return(FALSE);
}       						//DDERequest
//----------------------------------------------------------------------------
// this function receives data that was requested, for both sync AND async.
// DO NOT call DdeFreeDataHandle() here, because you don't call it for asynch data at all.
//
// returns TRUE if the server sent any data, FALSE if it denied our request (NULL hData).
//
// #error
// To make DDERequestAsynch fully functional, you need a SDDEConv member(?) that records
// if hData was NULL here, for reference.  Might be able to use LastTransId somehow?
// Also, getting the data INTO the SDDEConv is only half the job: presumably, you wanted
// to use it for something.  With sync, you do so immediately.  With async, you must
// remember what you needed it for.
//
// A convention of MSAccess (and maybe other MSOffice apps) seems to be that MULTI-line
// text is sent with literal quotes around it, single-line text isn't.
// Multiple lines are received from MSAccess with /r/n at line ends (except last line).
// Other things might cause quotation: embedded tabs, etc., but I haven't tested.
// Suspect that whatever rules apply to WordBasic, etc, quoting also apply for DDE.
//
// Unsure if this is too basic a level to strip quotes as needed.
// I can't think of any situation where I would want them left in.
//
BOOL SDDEConv::ReceiveData(HDDEDATA hData)
{
LastTransId = 0;		// transaction "completed" even if it receives nothing
DataReceived = TRUE;	// transaction "completed" even if it receives nothing

// this phrase IN Data is the failure return value of DDERequestString,
// and may later be the failure flag for DDERequestAsync.
SetData("Data Request Failed");

if(DDESide == SERVER)	// should never happen.
	return(FALSE);
if(!hData)      		// server did not provide data; ALSO MSAccess END-OF-TABLE flag
	return(FALSE);
											// server DID provide data.
DataSize = DdeGetData(hData, 0, 0, 0); 		// get size of the buffer we need
// I think I've had this situation, with no error msg logged; so size includes 0 terminator(?)
if(!DataSize)
	DDEError(InstId,"ReceiveData: size of returned data is 0");
delete[] Data;								// unconditionally delete any previous data
Data = new HUGE char[DataSize + 2];			// create oversize buffer for new data
Data[0] = 0;								// and preinitialize to null, just in case.

if(!DdeGetData(hData, (uchar HUGE*)Data, DataSize + 1, 0))
	DDEError(InstId,"ReceiveData: DdeGetData error");

SetData(0);                             	// fill Fields array with the received data
return(TRUE);
}                   			//ReceiveData
//----------------------------------------------------------------------------
// Poke a string over to the server
BOOL SDDEConv::DDEPoke(const string& itemname, const string& pokestring)
{
if(!IsGood() || (DDESide == SERVER))
	return(FALSE);

// Win32 requires nonconst char*
char* i = strnewdup(itemname.c_str());
HSZ Item = DdeCreateStringHandle(InstId, i, CP_WINANSI);
delete[] i;

if(!Item)
{
	DDEError(InstId,"DDEPoke loc 1");
	return(FALSE);
}
char* p = strnewdup(pokestring.c_str());
HDDEDATA hData =
	DdeClientTransaction((uchar*)p,strlen(p)+1,HConv,Item,CF_TEXT,XTYP_POKE,60000L,0);
delete[] p;
DdeFreeStringHandle(InstId, Item);
if(hData)
	return(TRUE);
DDEError(InstId,"DDEPoke loc 2");
return(FALSE);
}                         		//DDEPoke
//----------------------------------------------------------------------------
// Send a COMMAND string to the server.
BOOL SDDEConv::DDEExecute(const string& command)
{
if(!IsGood() || (DDESide == SERVER))
	return(FALSE);
char* p = strnewdup(command.c_str());
HDDEDATA hData =
	DdeClientTransaction((uchar*)p,strlen(p)+1,HConv,NULL,CF_TEXT,XTYP_EXECUTE,60000L,NULL);
delete[] p;
if(hData)
	return(TRUE);

// This isn't really a DDE error: it can mean merely that the server rejected the transaction,
// which you handle in caller when this fn returns FALSE.
// DDEError(InstId,"DDEExecute");
DdeGetLastError(InstId);  		// reset last error to NO_ERROR, without logging it
return(FALSE);
}                         		//DDEExecute
//----------------------------------------------------------------------------
// Start or stop a continuous advise loop.
BOOL SDDEConv::DDEAdvise(const string& itemname, BOOL start)
{
if(!IsGood() || (DDESide == SERVER))
	return(FALSE);

// Win32 requires nonconst char*
char* i = strnewdup(itemname.c_str());
HSZ Item = DdeCreateStringHandle(InstId, i, CP_WINANSI);
delete[] i;

if(!Item)
{
	DDEError(InstId,"DDEAdvise");
	return(FALSE);
}
UINT flags;
if(start) flags = XTYP_ADVSTART | XTYPF_ACKREQ;	// start, and make server wait for our ACK
else      flags = XTYP_ADVSTOP;					// stop

HDDEDATA hData = DdeClientTransaction(0, 0, HConv, Item, CF_TEXT, flags, 60000L, NULL);
DdeFreeStringHandle(InstId, Item);
if(hData)
	return(TRUE);
DDEError(InstId,"DDEAdvise");
return(FALSE);
}                 				//DDEAdvise
//----------------------------------------------------------------------------
// 						end class SDDEConv
//////////////////////////////////////////////////////////////////////////////
// class SDDEHandler code
//----------------------------------------------------------------------------
// initialize static members
TArrayAsVector<ServerInfo> SDDEHandler::Servers(10,0,2);

//----------------------------------------------------------------------------
// constructor
SDDEHandler::SDDEHandler(DWORD instid, const char* servicename)
	: Chan(2,0,2), Items(10,0,20)
{
// THE FIRST SDDEHANDLER CREATED CAUSES THE STATIC ARRAY TO LOAD
// 1/24/06. This sets up servers that were on my system during development.
//
if(!Servers.GetItemsInContainer())
{
	Servers.Add(ServerInfo("MSAccess","OMain","Microsoft Access",
							"C:\\MSOFFICE\\ACCESS\\MSACCESS.EXE"));

	// switches: /without Tip of the Day, and /no empty document (Document 1)
	Servers.Add(ServerInfo("WinWord","OpusApp","Microsoft Word",
							"C:\\MSOFFICE\\WINWORD\\WINWORD.EXE /w /n"));

	// switch is /e (no empty workbook)
	Servers.Add(ServerInfo("Excel","XLMAIN","Microsoft Excel",
							"C:\\MSOFFICE\\EXCEL\\EXCEL.EXE /e"));

	// this is my program WTALK.EXE, which has rudimentary server capabilities for parsing text.
	Servers.Add(ServerInfo("WTALK","OwlWindow","WTalk","C:\\AI\\TALK\\WTALK.EXE"));

	// these are the original Borland server example and my own demo program, in that order.
	// #error adding them to the array should probably be deleted here
	// and moved to the programs where they're used.
	Servers.Add(ServerInfo("TDMLSR_Server","OwlWindow","DDESVR (A DDE Server)",
							"C:\\BC4\\EXAMPLES\\OWL\\WINAPI\\DDEML\\DDESRV.EXE"));
	Servers.Add(ServerInfo("MYCLISRV","OwlWindow","DDEML Demo","C:\\BCS\\DDE\\MYCLISRV.EXE"));

	// I think I added the following only because I'd figured out the window classes, etc.
	// and wanted to preserve the hunted-down info.
	// I think neither provides any useful DDE capabilities.  I also searched for
	// other programs that do, and found none, except maybe CorelDraw.  The rest, even if
	// they support DDE, do nothing except Open and Print, for FileManager's use.
	// Servers.Add(ServerInfo("Progman","Progman","ProgramManager","PROGMAN.EXE"));
	// Servers.Add(ServerInfo("WinFile","WFS_Frame","FileManager","WINFILE.EXE"));
}
InstId = instid;
ServiceName = 0;
DDESide = (servicename ? SERVER : CLIENT);
if(InstId && (DDESide == SERVER))
{
	// Win32 requires nonconst char*
	char* s = strnewdup(servicename);
	ServiceName = DdeCreateStringHandle(InstId, s, CP_WINANSI);
	delete[] s;

	RegisterServiceName(ServiceName);
}
}                       	//constructor
//----------------------------------------------------------------------------
// destructor.  All DDE resource cleanup must be done in ShutDown(), not here.
SDDEHandler::~SDDEHandler()
{
}                       	//destructor
//----------------------------------------------------------------------------
// all this termination code came from the dtor, where it caused errors in the SDDEConv dtor.
// By the time the destructor is called, this instance has already Unitialized with the
// DDEML, and thus no DDE calls can succeed.  So this fn is called by TermInstance().
// Don't call from CanClose(), because this fn makes the Handler unrecoverably useless.
void SDDEHandler::ShutDown()
{
DDETerminateAll();
if(DDESide == SERVER)
	UnRegisterServiceName(ServiceName);
Items.Flush(TShouldDelete::Delete);		// it should be already empty (see UnRegister)
if(InstId && ServiceName && !DdeFreeStringHandle(InstId, ServiceName))
	DDEError(InstId,"ShutDown");
}                       	//ShutDown
//----------------------------------------------------------------------------
// SDDEHANDLER CLIENT FUNCTIONS
//----------------------------------------------------------------------------
// open a DDE channel and return ptr to it, or 0 if the connect fails.
// the object will be deleted automatically when the channel is closed;
// ALWAYS USE DDETERMINATE() TO CLOSE THE CHANNEL.  DO NOT DELETE IT YOURSELF.
// servicename can be base name, or instance-specif if you know it.
SDDEConv* SDDEHandler::DDEInitiate(const string& servicename, const string& topic)
{
if(!servicename.length() || !topic.length())
	return(0);

// FIRST, TRY TO OPEN THE CHANNEL USING EXACTLY WHAT WE WERE GIVEN.  THIS WILL ALLOW QUICK
// SUCCESS IF APP IS RUNNING AND DOC (IF ANY) IS ALREADY OPEN, AND WILL ALLOW SUCCESS,
// OTHERWISE IMPOSSIBLE BELOW, WITH SERVERS THAT DON'T SUPPORT THE SYSTEM TOPIC.

SDDEConv* channel = new SDDEConv(InstId, servicename, topic);
if(channel->IsGood())			// failed channels not allowed into the array
{
	Chan.Add(channel);
	return(channel);
}
delete channel; 				// first try failed, but we don't know why.
channel = 0;					// re-init for 2nd try

// TRY TO INITIATE A TEMPORARY LOCAL CONV WITH THE SERVER ON THE SYSTEM TOPIC.
// FOR SERVERS THAT SUPPORT THE SYSTEM TOPIC, IF THIS FAILS, WE KNOW FOR SURE
// THAT THE PROBLEM IS THAT IT'S NOT RUNNING, AND SHOULD TRY TO AUTO-LAUNCH.

channel = new SDDEConv(InstId, servicename, "SYSTEM");
BOOL ServerIsRunning = channel->IsGood();
delete channel;					// system topic was ONLY to determine if it's running
channel = 0;
if(!ServerIsRunning)			// SERVER WASN'T RUNNING. TRY AUTO-LAUNCH.
{
	ServerInfo s(servicename.c_str());	// see if requested server is one we have info about
	int i = Servers.Find(s);
	if(i == INT_MAX)
	{
		// (could try appending .EXE to servicename, and try to launch that)
		logerror("Can't auto-launch unknown server.");
		return(channel);
	}
	s = Servers[i]; 						// now s will have ALL the members
	HWND w = ::GetFocus();					// save this app's window

	// DO NOT use ShellExecute on the assumption that the requested topic is a document.
	// This is almost never true for MSAccess, which I expect to use most.  A database
	// topic typically looks like "C:\PATH\X.MDB;TABLE Y", which ShellExecute can't use.

	// This useful note from previous version isn't that important here:
	// it appears that if this launch results in multiple instances of the server,
	// we'll get connected to this, most-recently-launched, which is what we want
	// (a fresh copy that we can tell to open the desired file).

	// #error experiment with SW_SHOWNOACTIVATE or others?
	WinExec(s.ExePath.c_str(), SW_SHOW);
	if(w)
		::SetFocus(w);						// restore focus to this app
}
// SPECIAL HANDLING FOR VARIOUS MSOFFICE APPS, PROVIDING A 2ND CHANCE OF SUCCESS.
if(((servicename == "MSACCESS") || (servicename == "WINWORD")) && (topic != "SYSTEM"))
{
	channel = new SDDEConv(InstId, servicename, "SYSTEM");
	if(!channel->IsGood()) 		// if SYSTEM fails, something unfixable is wrong
	{
		delete channel;
		channel = 0;
		return(channel);
	}
	// SPECIAL HANDLING FOR MSACCESS.  EVEN ONCE IT'S RUNNING, IT WON'T AUTO-OPEN
	// THE DATABASE (IF ANY) REQUIRED FOR YOUR TOPIC, SO TRY TO OPEN IT HERE.
	string t;   						// for building a command in
	if(servicename == "MSACCESS")
	{
		// all legal MSACCESS topics except SYSTEM are, or begin with, a database name.
		// if it contains no ;, then t is already just a database name.
		t = topic;						// copy for manipulation and parsing
		size_t u = t.find(";");			// else remove anything after ";"
		if(u != NPOS)
			t.remove(u);
		t.prepend("[OPENDATABASE ");
		t += "]";
	}
	// SPECIAL HANDLING FOR WINWORD 6.0. ASSUME TOPIC IS A DOCUMENT NAME, AND TRY TO OPEN IT.
	if(servicename == "WINWORD")
	{
		t = "[FileOpen .Name = \"%s\", .ConfirmConversions = 0, .ReadOnly = 0, "
			".AddToMru = 1, .PasswordDoc = \"\", .PasswordDot = \"\", .Revert = 0, "
			".WritePasswordDoc = \"\", .WritePasswordDot = \"\"]";
		t.substring("%s") = topic;
	}
	channel->DDEExecute(t);
	delete channel;
	channel = 0;
}
// NOW RETRY THE CONNECTION WITH THE ORIGINALLY REQUESTED TOPIC.
// IT *SEEMS* THAT MSOFFICE APPS WILL SOMETIMES! OPEN THE REQUIRED FILE IF IT ISN'T
// ALREADY OPEN, SO YOU'LL GET THE IMMEDIATE RESULT YOU WANT.  HOWEVER, THE VARIOUS
// APPS HAVE DIFFERENT WAYS OF HANDLING CONFLICTS, SO
// YOU MAY GET SOME WEIRD PROBLEMS, SUCH AS RE-OPENING AN ALREADY-OPEN
// FILE, OR CLOSING AN OPEN DATABASE JUST SO YOURS CAN BE OPENED, ETC., BUT FOR NOW
// I JUST HAVE TO WAIT FOR THOSE PROBLEMS TO ARISE.  MSWORD LOOKS MOST RELIABLE (sometimes):
// OPENS FILE IF NECESSARY, OTHERWISE CHANGES FOCUS TO THE DESIRED FILE.  MSEXCEL:
// MAY PAUSE WITH A DIALOG ASKING YOU IF YOU WANT TO RE-OPEN.  MSACCESS: WILL
// PROBABLY CLOSE ANY OPEN FILE (EVEN IF IT'S IN USE), AND OPEN YOURS.
// I originally wanted to use the server's topic lists, connect to a specific server
// instance to get to an already-open file, or connect with a fresh instance to open a
// new file, etc., to avoid disrupting any running apps, but it's way too complicated.

channel = new SDDEConv(InstId, servicename, topic);
if(channel->IsGood())
	Chan.Add(channel);
else
{
	delete channel;
	channel = 0;
}
return(channel);
}              				//DDEInitiate
//----------------------------------------------------------------------------
// note channel is a ptr-reference, which is auto-zeroed here.
void SDDEHandler::DDETerminate(SDDEConv*& channel)
{
if(!channel)
	return;

// identify THIS as the object initiating the disconnect, so that if the partner conv is
// also in our array, we can prevent it calling DdeDisconnect(), too.
channel->Disconnecting = TRUE;
Chan.Destroy(channel);
channel = 0;				// zero the ptr: it is invalid now anyway
}                      		//DDETerminate
//----------------------------------------------------------------------------
void SDDEHandler::DDETerminateAll()
{
// don't use Flush().  Each channel is initiating a disconnect.
SDDEConv* c;
while(Chan.GetItemsInContainer())
{
	c = Chan[0];    	// to shield Chan[0] itself from being zeroed if passed as a *&
	if(c)               // just in case a zero-pointer gets in
		DDETerminate(c);
	else
		Chan.Destroy((int)0);
}
}                       	//DDETerminateAll
//----------------------------------------------------------------------------
// SDDEHANDLER SERVER FUNCTIONS
//----------------------------------------------------------------------------
// register the given servicename, and create standard System topic items in Items[]
BOOL SDDEHandler::RegisterServiceName(HSZ service)
{
if(!InstId || !service)
	return(FALSE);

if(DdeNameService(InstId, service, 0, DNS_REGISTER))
{
	string s = StringFromHSZ(InstId,service);
	AddItem(s.c_str(), SZDDESYS_TOPIC, SZDDESYS_ITEM_SYSITEMS);
	AddItem(s.c_str(), SZDDESYS_TOPIC, SZDDESYS_ITEM_FORMATS);
	AddItem(s.c_str(), SZDDESYS_TOPIC, SZDDESYS_ITEM_STATUS);
	AddItem(s.c_str(), SZDDESYS_TOPIC, SZDDESYS_ITEM_TOPICS);
	AddItem(s.c_str(), SZDDESYS_TOPIC, SZDDESYS_ITEM_HELP);
	return(TRUE);
}
DDEError(InstId,"RegisterServiceName");
return(FALSE);
}                 			//RegisterServiceName
//----------------------------------------------------------------------------
// unregister the given servicename, and delete all Items[] lines that use it.
BOOL SDDEHandler::UnRegisterServiceName(HSZ service)
{
if(!InstId || !service)
	return(FALSE);

// delete the items for this service only
CountServiceAndTopic(service, 0);    				// also marks
for(uint i = 0 ; i < Items.GetItemsInContainer() ; )
	if(Items[i]->Marked)
		Items.Destroy(i);
	else
		i++;

if(DdeNameService(InstId, service, 0, DNS_UNREGISTER))
	return(TRUE);
DDEError(InstId,"UnRegisterServiceName");
return(FALSE);
}                         	//UnRegisterServiceName
//----------------------------------------------------------------------------
// THIS BASE VERSION handles standard SYSTEM TOPIC items,
HDDEDATA SDDEHandler::ItemData(HSZ service, HSZ topic, HSZ item, WORD format)
{
if(!InstId || !service || !topic || !item)
	return((HDDEDATA)NULL);

string Service = StringFromHSZ(InstId,service);
string Topic = StringFromHSZ(InstId,topic);
string Item = StringFromHSZ(InstId,item);
string s;                 			// for building the reply in
if(Service == "MYCLISRV")			// test in case you support multiple service names
{
	//----------------------------------------------------------------------------
	if(Topic == SZDDESYS_TOPIC)
	{
		if(Item == SZDDESYS_ITEM_SYSITEMS)
		{
			// ASSEMBLE ALL LINE ITEMS FROM ITEMS[] WITH TOPIC == SYSTEM
			// we can use the HSZ topic we received
			CountServiceAndTopic(0, topic);    			// also marks
			for(uint i = 0 ; i < Items.GetItemsInContainer() ; i++)
				if(Items[i]->Marked)
				{
					if(format == CF_TEXT)
					{
						if(s.length())
							s += "\t";
						s += Items[i]->GetTopicText();
					}
					// other formats would be handled here
				}
		}
		if(Item == SZDDESYS_ITEM_FORMATS)
		{
			if(format == CF_TEXT)
				s = "TEXT";
		}
		if(Item == SZDDESYS_ITEM_STATUS)
		{
			if(format == CF_TEXT)
				s = "READY";
		}
		if(Item == SZDDESYS_ITEM_TOPICS)
		{
			// ASSEMBLE ALL UNIQUE TOPICS FOR THIS SERVICE
			if(ServiceName)
			{
				string previous;
				CountServiceAndTopic(ServiceName, 0);    			// also marks
				for(uint i = 0 ; i < Items.GetItemsInContainer() ; i++)
					if(Items[i]->Marked && (Items[i]->GetTopicText() != previous))
					{
						if(format == CF_TEXT)
						{
							if(s.length())
								s += "\t";
							s += Items[i]->GetTopicText();
							previous = Items[i]->GetTopicText();
						}
						// other formats would be handled here
					}
			}
		}
		if(Item == SZDDESYS_ITEM_HELP)
		{
			if(format == CF_TEXT)
				s = "THIS HELP TOPIC AS YOU CAN SEE IS CURRENTLY VERY LITTLE HELP";
		}
	}
	if(s.length())
	{
		char* p = strnewdup(s.c_str());
		HDDEDATA r = DdeCreateDataHandle(InstId,(uchar*)p,strlen(p)+1,0,item,format,0);
		delete[] p;
		return(r);
	}
	//----------------------------------------------------------------------------
}								// end if(Service == "MYCLISRV")
return((HDDEDATA)NULL);
}								//ItemData
//----------------------------------------------------------------------------
// This BASE CLASS VERSION only rejects the transaction and cleans up.
// -->DERIVED VERSION MUST DO THE EQUIVALENT CLEANUP.
// topic is passed here because it's available in callback, but it's seldom used for anything;
// besides, the topic was already fixed when the conv was established.
#pragma argsused
HDDEDATA SDDEHandler::DoDDEExecute(HSZ topic, HDDEDATA hData)
{
if(!DdeFreeDataHandle(hData))      			// cleanup
	DDEError(InstId,"DoDDEExecute");
return((HDDEDATA)DDE_FNOTPROCESSED);
}               			//DoDDEExecute
//----------------------------------------------------------------------------
// only counts already-marked.  does not remark any items.
uint SDDEHandler::CountMarkedItems() const
{
uint count = 0;
for(uint i = 0 ; i < Items.GetItemsInContainer() ; i++)
	if(Items[i]->Marked)
		count++;
return(count);
}							// CountMarkedItems
//----------------------------------------------------------------------------
uint SDDEHandler::CountServiceAndTopic(HSZ service, HSZ topic)
{
uint count = 0;
for(int i = 0 ; i < Items.GetItemsInContainer() ; i++)
	if(Items[i]->MatchServiceAndTopic(service,topic))
		count++;
return(count);
}                   		//CountServiceAndTopic
//----------------------------------------------------------------------------
uint SDDEHandler::CountTopicAndItem(HSZ topic, HSZ item)
{
uint count = 0;
for(int i = 0 ; i < Items.GetItemsInContainer() ; i++)
	if(Items[i]->MatchTopicAndItem(topic,item))
		count++;
return(count);
}                     		//CountTopicAndItem
//----------------------------------------------------------------------------
ServiceTopicItem* SDDEHandler::FindServiceTopicItem(HSZ service, HSZ topic, HSZ item) const
{
for(int i = 0 ; i < Items.GetItemsInContainer() ; i++)
	if(Items[i]->SameAs(service,topic,item))
		return(Items[i]);
return(0);
}         					//FindServiceTopicItem
//----------------------------------------------------------------------------
int SDDEHandler::AddItem(const char* service, const char* topic, const char* item)
{
if(!service || !topic || !item)		// an item must have all its members
	return(0);

ServiceTopicItem* sti = new ServiceTopicItem(InstId, service, topic, item);
BOOL isok = sti->IsGood();   						// all strings valid?
if(isok)
{
	for(int i = 0 ; i < Items.GetItemsInContainer() ; i++)
		if(Items[i]->SameAs(*sti))   				// prevent duplicate table entries
		{
			isok = FALSE;
			break;
		}
	if(isok)
		return(Items.Add(sti));
}
delete sti;
return(0);
}                       	//AddItem
//----------------------------------------------------------------------------
// responds to 'system wide' polling of any available Services with specific Topics,
// any Topics with specific Services or any Services with any Topics.
HDDEDATA SDDEHandler::WildConnect(HSZ service, HSZ topic, WORD format)
{
// this 1 call should correctly mark all potential line items for any of the 4
// possible combinations of topic and service (both null, either null, both non-null).
// a null is a wildcard, matching anything.
if(!CountServiceAndTopic(service,topic))	// if 0, nothing matched; quit
	return((HDDEDATA)0);

//----------
// now that all possible line items are marked, unmark any duplicate service/topic pairs.
//
// this block could go into a separate fn CullItemsToUnique(BOOL service, BOOL topic)
// possibly useful elsewhere. args = leave only unique services, unique topics, or both.
// just unmark items as necessary to leave only unique lineitems marked.
HSZ prevs = 0, prevt = 0;
for(int i = 0 ; i < Items.GetItemsInContainer() ; i++)
{
	ServiceTopicItem* a = Items[i];
	if(!a->Marked)								// skip if not marked
		continue;
	// nulls won't match because null values aren't allowed in Items[]
	// if(a->MatchServiceAndTopic(prevs,prevt)) // don't use this. it matches nulls.
	if(	(DdeCmpStringHandles(a->GetService(), prevs) == 0) &&
		(DdeCmpStringHandles(a->GetTopic(), prevt) == 0))
	{
		a->Marked = FALSE;
		continue;
	}
	// only if this item passed all tests, it becomes the "previous" service/topic pair
	// that you don't want to allow duplicates of.
	prevs = a->GetService(); prevt = a->GetTopic();
}
//----------
// create an array of the needed size, copy our marked items into it,
// and terminate it with a null pair entry.
int count = CountMarkedItems();					// if we got this far, min possible is 1
HSZPAIR* array = new HSZPAIR[count + 1];
int index = 0;
for(i = 0 ; i < Items.GetItemsInContainer() ; i++)
{
	ServiceTopicItem* a = Items[i];
	if(!a->Marked)
		continue;
	HSZPAIR h = { a->GetService(), a->GetTopic() };	// note HSZPAIR (for C) has no ctor
	array[index++] = h; 							// but has implicit copy ctor? (hope so!)
	if(index > count) 								// just in case; shouldn't happen.
		index = count;
}
HSZPAIR h = { 0, 0 };						// null pair terminator
array[index] = h;							// index must == count here: last array entry
HDDEDATA data = DdeCreateDataHandle(InstId, (uchar*)array, (count + 1) * sizeof(HSZPAIR),
									0L, 0, format, 0);
if(!data)
	DDEError(InstId,"WildConnect");
return(data);
}							//WildConnect
//----------------------------------------------------------------------------
// called by the app's IdleAction.
int SDDEHandler::UpdateData()
{
int count = 0;
for(int i = 0 ; i < Items.GetItemsInContainer() ; i++)
	if(Items[i]->NeedsUpdate())
	{
		Items[i]->Advise();		// if(Items[i]->Advise())?
		count++;
	}
return(count);
}							//UpdateData
//----------------------------------------------------------------------------
// SDDEHANDLER CLIENT/SERVER FUNCTIONS
//----------------------------------------------------------------------------
// returns ptr to the SDDEConv that matches the HCONV, or 0 if not found.
// side is the side you are looking for, CLIENT or SERVER, in case connection is to itself.
SDDEConv* SDDEHandler::FindDDEConv(HCONV hConv, DDESides side)
{
for(int i = 0 ; i < Chan.GetItemsInContainer() ; i++)
	if( (Chan[i]->DDESide == side) && ((HCONV)(*Chan[i]) == hConv) )
		return(Chan[i]);
return(0);
}                    		//FindDDEConv
//----------------------------------------------------------------------------
// THIS SDDEHANDLER BASE CALLBACK can handle all transaction notifications it could receive,
// but may not necessarily do everything you want done for every case.
// To add specific behavior, create a derived version that provides code for cases you
// want to handle specially, then call this for default handling of all other cases.
// Use this as a template for the derived version.
//
#pragma argsused
HDDEDATA SDDEHandler::DDECallBack(WORD type, WORD format, HCONV hConv,
								HSZ topic, HSZ itemorservice,
								HDDEDATA hData, DWORD data1, DWORD data2)
{
SDDEConv* c;			// use this when you must determine which conv the data is for
ServiceTopicItem* sti;  // for finding a particular supported line item
string s;				// misc.
switch(type)
{
	//----------------------------------------------------------------------------
	// MESSAGES SENT TO CLIENT
	//----------------------------------------------------------------------------
	case XTYP_ADVDATA:        			// auto-advised that new data is ready for pickup
	case XTYP_XACT_COMPLETE:    		// asynch transaction: your data is ready for pickup
		if((c = FindDDEConv(hConv, CLIENT)) == 0)	// if THIS handler doesn't own the conv,
			return(DDE_FNOTPROCESSED);				// =0, reject the transaction
		c->ReceiveData(hData);			// else tell the Conv to retrieve its data
		return((HDDEDATA)DDE_FACK);		// ack/accept: causes DDEML to auto-free hData

	//----------------------------------------------------------------------------
	// MESSAGES SENT TO SERVER
	//----------------------------------------------------------------------------
	case XTYP_CONNECT:				// client wants to connect: allow this connection?
		if(CountServiceAndTopic(itemorservice,topic))
			return((HDDEDATA)(TRUE));
		return((HDDEDATA)(FALSE));	// return TRUE/FALSE

	case XTYP_WILDCONNECT:
		return(WildConnect(itemorservice, topic, format));

	case XTYP_EXECUTE:
		return(DoDDEExecute(topic, hData));

	case XTYP_POKE:
	{
		// only the derived version can do any real handling, so do something informative
		// this section caused a lot of trouble Win16 vs. Win32.
		// don't know why.  The IDE itself seemed confused, sometimes even compiling comments.
		// but the error msg "illegal structure operation" was consistent, as was the loc,
		// more or less.
		char* p = new char[256];
		p[0] = 0;
		DdeGetData(hData, (uchar*)p, 256, 0);
		s = "The MYCLISRV server received your string:\n";
		s += p;
		delete[] p;
		::MessageBox(GetFocus(), s.c_str(), "XTYP_POKE Report", MB_ICONINFORMATION);
		return((HDDEDATA)DDE_FACK);
	}

	case XTYP_ADVSTART:  				// client wants to start an advise loop
		if((sti = FindServiceTopicItem(ServiceName, topic, itemorservice)) != 0)
			if(!sti->AutoAdvise)		// if already going, keep loop's current settings
				if(format == CF_TEXT)	// the only format I support
				{
					sti->AutoAdvise = TRUE;
					return((HDDEDATA)TRUE);
				}
		return((HDDEDATA)FALSE);		// return TRUE/FALSE

	case XTYP_ADVREQ:
		// ItemData() determines whether it can process it, and returns appropriate value
		return(ItemData(ServiceName, topic, itemorservice, format));

	case XTYP_REQUEST:
		// ItemData() determines whether it can process it, and returns appropriate value
		// you don't have to know who is receiving it: DDEML handles delivery.
		return(ItemData(ServiceName, topic, itemorservice, format));

	// NOTIFICATIONS: OUR RETURN VALUE IS IGNORED
	case XTYP_ADVSTOP:
		if((sti = FindServiceTopicItem(ServiceName, topic, itemorservice)) != 0)
			if(sti->AutoAdvise)
// 				if(format == CF_TEXT)			// why bother testing format?
					sti->AutoAdvise = FALSE;
		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_CONNECT_CONFIRM:  	// purpose: DDEML provides the HCONV for ref.
		// hconv	Identifies the new conversation.
		// dwData2	BOOL whether client and server are in the same application instance
		// re-check: if this pair not found, this couldn't be the target destination.
		if(CountServiceAndTopic(itemorservice,topic))
		{
			Chan.Add(new SDDEConv(InstId, StringFromHSZ(InstId,itemorservice).c_str(),
									StringFromHSZ(InstId,topic).c_str(), SERVER, hConv));
			return((HDDEDATA)DDE_FACK);			// tell app's callback we accept it
		}
		return((HDDEDATA)DDE_FNOTPROCESSED); 	// refuse the connection

	//----------------------------------------------------------------------------
	// MESSAGES SENT TO EITHER CLIENT OR SERVER
	// ALL ARE NOTIFICATIONS: OUR RETURN VALUE IS IGNORED
	//----------------------------------------------------------------------------
	case XTYP_DISCONNECT:	// our notification that the OTHER party is disconnecting
		// if both convs are in the same app instance, you must not invalidate the wrong one
		c = FindDDEConv(hConv, SERVER);
		if(!c || (c && c->Disconnecting))
			c = FindDDEConv(hConv, CLIENT);
		if(c && !c->Disconnecting)
		{
			Chan.Destroy(c);    		// don't call DDETerminate because c didn't initiate it
			return((HDDEDATA)DDE_FACK);	// inform static callback that we processed it
		}
		return((HDDEDATA)DDE_FNOTPROCESSED);	// the conv didn't belong to this instance

	case XTYP_ERROR:                        	// low memory
		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_REGISTER:
		// only these args are valid:
		// hsz1	Identifies the base service name being registered.
		// hsz2	Identifies the instance-specific service name being registered.
		// The instance-specific name looks like:  "Basename:(02a0)"
		// An application should use the hsz1 parameter to add the service name to the
		// list of servers available to the user. An application should use the hsz2 parameter
		// to identify which application instance has started.

		// IF ONE OF OUR SDDEHANDLERS LAUNCHED THE SERVER, IT might LIKE TO KNOW ITS FULL NAME.
		// BUT CURRENTLY WE HAVE NO WAY TO DISCOVER WHICH SDDEHANDLER JUST LAUNCHED A SERVER.
		// if this instance is notified of its own registration, we can make better use of
		// the info (discovering our own instance-specific name?, and maybe even waiting
		// until now to create the system topics for Items[]).
		// Being able to connect to the correct instance of a server could be important:
		// getting the wrong one and auto-running a macro could be disastrous.
		// study on how to connect to a specific instance.    see DdeConnect.

		// temporary diagnostic code.  multiple instances will log more than once.
// 		s = "A New Server Registered:";
// 		s += "\n\tBase: " + StringFromHSZ(InstId,topic) +
// 			  ", Instance-specif: " + StringFromHSZ(InstId,itemorservice);
// 		logerror(s);

		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_UNREGISTER:
		// temporary diagnostic code.  multiple instances will log more than once.
// 		s = "A Server Is Terminating:";
// 		s += "\n\tBase: " + StringFromHSZ(InstId,topic) +
// 			  ", Instance-specif: " + StringFromHSZ(InstId,itemorservice);
// 		logerror(s);

		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_MONITOR:
		return((HDDEDATA)DDE_FACK);				// our return value is ignored(?)

	default:
		return((HDDEDATA)DDE_FNOTPROCESSED);	// =0, rejects transaction, prevents retry

}								//end switch(type)
}								//DDECallBack
//----------------------------------------------------------------------------
// 						end class SDDEHandler
//////////////////////////////////////////////////////////////////////////////
// class SDDEApplication code
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SDDEApplication,TApplication)
// 	EV_WM_DROPFILES,
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// initialize static members
TArrayAsVector<SDDEHandler*> SDDEApplication::SDDEHandlers(2,0,2);
//----------------------------------------------------------------------------
// WHEN YOU OVERRIDE, YOU SHOULD (AS ALWAYS) *NOT* CALL THIS USELESS BASE VERSION AT ALL.
// HOWEVER, SEE NOTES BELOW FOR THINGS YOU MUST DO.
//
void SDDEApplication::InitMainWindow()
{
// BaseServiceName is the name to register with DDEML as a SERVER.
// BE SURE TO GIVE THE CLIENT CONSTRUCTOR A 0 POINTER IF THIS IS CLIENT-ONLY,
// BECAUSE IF IT GETS A VALID POINTER, IT *WILL* REGISTER IT.
// const char* p =
// 			((DDESide == SERVER) && BaseServiceName.length()) ? BaseServiceName.c_str() : 0;

// YOUR MAIN WINDOW MUST HAVE A SDDEHANDLER-DERIVED CLIENT, AND NOTE THE EXTRA ARGUMENTS
// THAT YOU MUST PASS TO ITS CONSTRUCTOR TO PROPERLY INITIALIZE ITS SDDEHANDLER MEMBER.
// example:
// SDDEDemoWindow* client = new SDDEDemoWindow(0, 0, InstId, p);

TWindow* client = new TWindow(0,0,0);		// uselesss, but allows compilation

TFrameWindow* frame = new TFrameWindow(0, GetName(), client);
// frame->AssignMenu(TDMLClWnd_MENU);

SetMainWindow(frame);
}                			//InitMainWindow
//----------------------------------------------------------------------------
// IF YOU OVERRIDE INITINSTANCE(), CALL THIS BASE CLASS VERSION *FIRST*.
// THE ORDER OF THE STEPS HERE IS IMPORTANT.
//
void SDDEApplication::InitInstance()
{
// 1. INSTALL THE STATIC CALLBACK FN FOR USE BY THE DDEML, AND RECEIVE OUR INSTANCE ID.
DWORD flags = ((DDESide == CLIENT) ? APPCMD_CLIENTONLY : APPCLASS_STANDARD);
if(DdeInitialize(&InstId, (PFNCALLBACK)(FARPROC)DDECallBack, flags, 0) != DMLERR_NO_ERROR)
{
	::MessageBox(0,"DDEML Initialization failed",GetName(), MB_ICONSTOP|MB_TASKMODAL);
	PostQuitMessage(0);
}
// Note that the rest here is executed even if quit message was posted.
// Unsure of effect, but running briefly with a failed DDEML registration seems low-risk,
// while omitting this normal startup code seems like it would invite disaster.

// 2. CALL TAPPLICATION::INITINSTANCE(), WHICH CALLS INITMAINWINDOW(), WHICH REQUIRES THE
//    INSTANCE ID WE JUST RECEIVED, AND TRIGGERS FRAME'S SETUPWINDOW.
TApplication::InitInstance();

// 3. ADD THE SDDEHANDLER OF THE NEW INSTANCE TO THE HANDLER LIST.
SDDEHandler* handler = dynamic_cast<SDDEHandler*>(GetMainWindow()->GetClientWindow());
if(handler)
	SDDEHandlers.Add(handler);	// add to list of DDE handlers
else
{
	// #error I haven't checked carefully enough whether this SHOULD be a fatal error,
	// but suspect it doesn't need to be.
	string s = "Client window not SDDEHandler-derived.\nDDEML functions will not be available.";
	::MessageBox(0,s.c_str(),GetName(),MB_ICONSTOP|MB_TASKMODAL);
// 	PostQuitMessage(0);
}
}                  			//InitInstance
//----------------------------------------------------------------------------
// IF YOU OVERRIDE TERMINSTANCE(), CALL THIS BASE CLASS VERSION *LAST*.
//
int SDDEApplication::TermInstance(int status)
{
// FIND THIS INSTANCE'S SDDEHANDLER, EMPTY ARRAYS, TERMINATE OPEN CONVS, UNREGISTER SERVNAME
SDDEHandler* handler = dynamic_cast<SDDEHandler*>(GetMainWindow()->GetClientWindow());
if(handler)
	handler->ShutDown();
else
	::MessageBox(0,"SDDEHandler not found in TermInstance",GetName(),MB_ICONINFORMATION);

// this loc seems to satisfy requirement that "An application should wait until its windows
// are no longer visible and its message loop has terminated before calling this function",
// but I'm not sure!  At any rate, this is the last possible loc earlier than in OwlMain.
if(InstId)
	if(!DdeUninitialize(InstId))	// ensure this app instance won't get a DDE message
		DDEError(InstId,"DdeUninitialize");

// NOW THAT IT CANNOT RECEIVE ANY DDEML MESSAGES, REMOVE THE HANDLER POINTER FROM THE LIST
if(handler)
	SDDEHandlers.Destroy(handler);

return(TApplication::TermInstance(status));	// for completeness, but does nothing
}                 			//TermInstance
//----------------------------------------------------------------------------
// IF YOU OVERRIDE IDLEACTION(), DO CALL THIS BASE CLASS VERSION; IT DOESN'T MATTER WHEN.
// remember that this is the app's IdleAction, not a window's.
//
BOOL SDDEApplication::IdleAction(long /* idlecount */)
{
// added 11/29/01 and untested.
// Its previous omission was a mistake.  Don't know why it didn't cause problems.
TApplication::IdleAction(0);

// tell each handler to check its item list and update all items that need it
for(int i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
	SDDEHandlers[i]->UpdateData();

return TRUE;
}               			//IdleAction
//----------------------------------------------------------------------------
// THIS STATIC FUNCTION SERVICES ALL INSTANCES OF THIS APPLICATION.  However, it has
// no knowledge of any specific App instance or of the associated main windows, so
// the application class keeps a static array of ptrs to the running DDE-handlers,
// which we can search to determine the HConv's owner.  Once that is done,
// it merely calls the actual DDE handler of the window that owns the conversation.
// Thus, this function serves only as a router for the messages, and this
// "generic" version of it can be used for any DDE application.
//
// MY DIAGNOSTIC AND LOGERROR ROUTINES GO HERE BECAUSE SOME DDEML MESSAGES SENT HERE
// HAVE NO ASSOCIATED HCONV, AND SO ARE UNROUTABLE.
// AFTER YOU'VE CREATED THE DIAGNOSTIC ROUTINES, YOU'LL KNOW WHAT INFO IS AVAILABLE FOR
// EACH CASE, AND THUS HOW TO OBTAIN AND USE IT IN A DERIVED CALLBACK.
//
// Declaring it _export here makes listing it as exported in .DEF unnecessary.
HDDEDATA FAR PASCAL _export
SDDEApplication::DDECallBack(WORD type, WORD format, HCONV hConv, HSZ topic, HSZ itemorservice,
						HDDEDATA hData, DWORD data1, DWORD data2)
{
SDDEHandler* w = 0;
// SDDEConv* c;			// use this when you must determine which conv the data is for
int i;
string s;				// misc. uses
switch(type)
{
	// Determine which SDDEHandler owns the conversation this information relates to.
	// For client and server messages, this is easy because you know which side to search for.
	//----------------------------------------------------------------------------
	// MESSAGES SENT TO CLIENT
	//----------------------------------------------------------------------------
	// Route these messages to ALL handlers, until one retrieves the data and acknowledges.
	// (This may be a desirable method for additional cases below where it's not yet used.)

	case XTYP_ADVDATA:
	case XTYP_XACT_COMPLETE:
		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
			if(SDDEHandlers[i]->DDECallBack(type,format,hConv,topic,itemorservice,
											hData,data1,data2) == (HDDEDATA)DDE_FACK)
				return((HDDEDATA)DDE_FACK);	// ack/accept: causes DDEML to auto-free hData
// 		return(DDE_FNOTPROCESSED);			// =0, rejects transaction
		return((HDDEDATA)DDE_FACK);	// or just pretend we did handle it? DDEML doesn't care?
									// return DDE_FACK,DDE_FBUSY,DDE_FNOTPROCESSED
	//----------------------------------------------------------------------------
	// MESSAGES SENT TO SERVER
	//----------------------------------------------------------------------------
	// These SERVER cases do NOT identify an existing HCONV, so you CANNOT search for it.
	// The APP gets the message, but the client wants to connect to one of its instances.

	case XTYP_CONNECT:
	case XTYP_WILDCONNECT:
		// Search all handlers for the first that allows the requested connection.
		// This may be the first place where my failure to use MAKEPROCINSTANCE causes
		// trouble.  Since this static callback services ALL instances, DDEML can't
		// distinguish, and can't assign an instance-specific service name.
		// However, since the eventual goal is Win32, forget the problem for now.

		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
			if(SDDEHandlers[i]->DDECallBack(type,format,hConv,topic,itemorservice,
											hData,data1,data2) == (HDDEDATA)TRUE)
				return((HDDEDATA)TRUE);
		return((HDDEDATA)(FALSE));		// return TRUE/FALSE

	// THIS SERVER CASE IS SPECIAL: IT'S THE FIRST YOU LEARN OF A NEW HCONV'S EXISTENCE.
	// You should also somehow remember which instance allowed the connection.
	// Until I figure out a way, just search handlers until one accepts it.
	// Thus, the first that originally allowed it will also be the first to accept the confirm.
	case XTYP_CONNECT_CONFIRM:
		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
			if(SDDEHandlers[i]->DDECallBack(type,format,hConv,topic,itemorservice,
											hData,data1,data2) == (HDDEDATA)DDE_FACK)
				return((HDDEDATA)DDE_FACK);
		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	//----------------------------------------------------------------------------
	// The following SERVER cases identify an existing HCONV, so you should search for it.
	// i.e. these are all currently ok in falling through to the final return;

	case XTYP_EXECUTE:
		// hconv	Identifies the conversation.
		// hsz1	Identifies the topic name.
		// hdata	Identifies the command string.

	case XTYP_POKE:
		// uFmt	Specifies the format of the data sent from the server.
		// hconv	Identifies the conversation.
		// hsz1	Identifies the topic name.
		// hsz2	Identifies the item name.
		// hdata	Identifies the data that the client is sending to the server.

	case XTYP_ADVSTART:
		// uFmt	Specifies the data format requested by the client.
		// hconv	Identifies the conversation.
		// hsz1	Identifies the topic name.
		// hsz2	Identifies the item name.

	case XTYP_ADVREQ:
		// uFmt	Specifies the format in which the data should be submitted to the client.
		// hconv	Identifies the conversation.
		// hsz1	Identifies the topic name.
		// hsz2	Identifies the item name that has changed.
		// dwData1	Specifies the count, in the low-order word, of XTYP_ADVREQ
		// transactions that remain to be processed, on the same topic, item, and format
		// name set, within the context of the current call to the DdePostAdvise
		// function. The count is zero if the current XTYP_ADVREQ transaction is the last one.
		// A server can use this count to determine whether to create an HDATA_APPOWNED
		// data handle for the advise data.
		// The low-order word is set to CADV_LATEACK if the
		// (DDEML) issued the XTYP_ADVREQ transaction because of a late-arriving DDE_ACK
		// message from a client being outrun by the server.  	The high-order word is not used.

	case XTYP_REQUEST:
		// uFmt	Specifies the format in which the server should submit data to the client.
		// hconv	Identifies the conversation.
		// hsz1	Identifies the topic name.
		// hsz2	Identifies the item name.

	case XTYP_ADVSTOP:
		// uFmt	Specifies the data format associated with the advise loop being ended.
		// hconv	Identifies the conversation.
		// hsz1	Identifies the topic name.
		// hsz2	Identifies the item name.

		// For now, doing the handler search here is ok.  Most of these cases are way ahead
		// of anything I'm currently prepared to handle.
		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
		{
			w = SDDEHandlers[i];
			if(w->FindDDEConv(hConv, SERVER))
				return(w->DDECallBack(type,format,hConv,topic,itemorservice,hData,data1,data2));
		}
		return(DDE_FNOTPROCESSED);			// =0, rejects transaction

	//----------------------------------------------------------------------------
	// MESSAGES SENT TO EITHER CLIENT OR SERVER
	// This is more complicated because the message may be being sent to us in our capacity
	// as client OR server, and one or more of our windows might own one or both sides
	// of the conversation.
	//----------------------------------------------------------------------------
	case XTYP_DISCONNECT:
		// hconv	Identifies the conversation that was terminated.
		// dwData2	BOOL whether the partners in the conv are in the same app instance

		// route the message to all handlers until one says it processed it.
		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
			if(SDDEHandlers[i]->DDECallBack(type,format,hConv,topic,itemorservice,
											hData,data1,data2) == (HDDEDATA)DDE_FACK)
				return((HDDEDATA)DDE_FACK);
		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_ERROR:
		// hconv	Identifies the conversation associated with the error. This parameter
		// is NULL if the error is not associated with a conversation.
		// dwData1	Specifies the error code in the low-order word.

		logerror("XTYP_ERROR Report: Critical DDE error: Low Memory");
		::MessageBox(GetFocus(),"Critical DDE error.","XTYP_ERROR Report",MB_ICONINFORMATION);

		// this code probably unnecessary.  keep until sure it's useless.
		// SDDEHandler* w2 = 0;	// a second one, for when you must search for clients AND servers
// 		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
// 		{
// 			if(!w && SDDEHandlers[i]->FindDDEConv(hConv, CLIENT)) w = SDDEHandlers[i];
// 			if(!w2 && SDDEHandlers[i]->FindDDEConv(hConv, SERVER)) w2 = SDDEHandlers[i];
// 		}
// 		if(w && !w2)
// 			return(w->DDECallBack(type,format,hConv,topic,itemorservice,hData,data1,data2));
// 		if(w2 && !w)
// 			return(w2->DDECallBack(type,format,hConv,topic,itemorservice,hData,data1,data2));
// 		// #ERROR UNFINISHED: IF BOTH WERE FOUND, YOU MUST DETERMINE WHICH IS WANTED

		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_REGISTER:			// a server app is registering its service name.
	case XTYP_UNREGISTER:      	// a server app is unregistering its service name.
		// route both messages to ALL instances, so they can update lists, or whatever.
		for(i = 0 ; i < SDDEHandlers.GetItemsInContainer() ; i++)
			SDDEHandlers[i]->DDECallBack(type,format,hConv,topic,itemorservice,
															hData,data1,data2);
		return((HDDEDATA)DDE_FACK);				// our return value is ignored

	case XTYP_MONITOR:
		// powerful debugging ability, but only for tracking down serious problems.
// 		return(DDE_FNOTPROCESSED);	// =0, Help says to return 0 if you DO process MONITORs.
		return((HDDEDATA)DDE_FACK);	// but our return value is ignored anyway(?)

	default:
		return(DDE_FNOTPROCESSED);			// =0, rejects transaction
}							//switch(type)
}					//STATIC SDDEApplication::DDECallBack
//----------------------------------------------------------------------------
// 						end class SDDEApplication
//////////////////////////////////////////////////////////////////////////////

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2007 Steven Whitney. Last modified 09/25/2007.