|
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 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 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
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|