|
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 Payments Humor Music |
Borland C++ ObjectWindows client / server base class for application DDEML supportOn this page is a class derived from the Borland C++ 4.0 ObjectWindows Library (OWL) 2.0 TApplication class and additional utility classes that use the Windows Dynamic Data Exchange Management Library (DDEML) to provide object-oriented support for DDE to an OWL application. To use, derive your application object from SDDEApplication instead of from the usual TApplication. Your application can function as client, server, or both. |
/* ddemlapp.h 01/06/02
Copyright (C)2000-2002, 2006 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Version 3 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.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Version 3 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
//////////////////////////////////////////////////////////////////////////////
|
|
|