Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
DDEInitiate()
, DDETerminate()
, DDEAbortTrans()
, DDEPoke()
, DDERequest()
, DDEExecute()
, DDELastError()
, DDESetOption()
, DDEAdvise()
, DDEEnabled()
, DDESetService()
, DDESetTopic()
Despite all the hoopla you’ve heard about the wonders of OLE, COM and ActiveX, Dynamic Data Exchange—one of COM’s forebears—is not yet dead. Getting on in years, yes. Dead, no, not just yet.
In most cases, we prefer to use the COM technologies when developing communications between applications. COM communications are more robust, support richer data types, and are more easily integrated into an OOP environment. But it takes two to tango. If the application on the other end of the phone speaks only DDE, then that is the language FoxPro must speak, too. Visual FoxPro supports the whole complement of DDE commands, providing capability as a client or a server.
In case you’re new to the DDE scene, let’s do a brief overview of the ways in which DDE can be used. DDE is often described as similar to a phone conversation. One party initiates the phone call, the two parties exchange information, and the parties terminate the call.
If VFP is the calling application—if we’re driving the conversation—we first DDEInitiate()
the conversation with the other party. This other application is commonly referred to as the server, confusing the situation with client-server database technology (as well as with Web technology). The client in DDE is the one running the show, running the code, and the server is the application that responds. DDERequest()
and DDEPoke()
provide the verbs to get and set variable values. DDEExecute()
passes a command to the server. DDETerminate()
completes the conversation.
DDELastError()
is the function used to get the details when things go wrong.
It may be that we don’t want to talk with the server as much as register an interest in the values it has in a particular document, and ask to be advised if these change. DDEAdvise()
is the trick for this. Again, you DDEInitiate()
and DDETerminate()
a conversation, with the request for DDEAdvise()
in the middle.
Finally, if your application must play the server and be called upon by other applications, DDESetService()
defines the services available, DDESetTopic()
sets (surprise!) the topics of the conversations, and DDEEnable() allows the server to toggle on and off DDE services while processing.
As we said before, DDE is never our first choice for interprocess communication mechanisms. But for those applications that support only DDE, or in situations of limited resources, Visual FoxPro is up to the task of communicating well using DDE. Visual FoxPro can carry on a variety of conversations, working as a client with cold, warm and hot links, as well as working as a DDE server.
If you’re really interested in the DDE server stuff, a trip down memory lane is required. The FoxPro development environment itself is not a DDE server, but rather the interface is in place for you to hook up your own application as a DDE server. Not much is documented in this version, but Mike Taylor of MicroMega wrote a cool application, FoxData, as a FoxPro DDE server. It was included with the sample code for FoxPro 2.x, and it demonstrated some neat little nooks and crannies in getting FoxPro to work. In addition, several excellent magazine articles and at least one book (listed in the references in the appendices) get into DDE in some depth.
nChannel = DDEInitiate( cAppDDEName, cTopic )
lReturn = DDETerminate( nChannel | cAppDDEName )
Parameter |
Value |
Meaning |
cAppDDEName |
Character |
Also known as a "service name," this is the handle an application uses to identify itself via DDE, like a trucker's CB radio handle. Contrary to the Help file, Visual FoxPro uses "FoxPro." Excel uses "Excel." Word uses "WinWord." Ain't standards wonderful? If cAppDDEName is supplied with DDETerminate, all channels with this server are closed at once. |
cTopic |
Character |
The topic of your conversation. "System" is often a good icebreaker for speaking with an application you're trying to get acquainted with. |
nChannel |
-1 |
Indicates DDEInitiate could not establish a channel. Use DDELastError to determine why. |
Positive Integer |
The channel number that uniquely identifies this conversation for use with the other DDE functions. |
|
lReturn |
.F. |
Indicates DDETerminate could not close a channel. The other application may have already terminated, or an invalid channel number might have been passed. Use DDELastError to determine why. |
.T. |
Successful termination. |
DDEInitiate()
is used to start a conversation between two applications, and DDETerminate()
to finish it. Negative or false returns from these functions (and the ones below) are indicative of errors that should be checked out with DDELastError()
.
DDEInitiate()
attempts to start a conversation with another application by searching the list of DDE servers available in memory. By default, if the server isn’t running, FoxPro offers to start the server by popping a dialog up on the screen. Typically, this is an option you will not want to offer the end user. You probably want to start the application if it isn’t running. Use DDESetOption()
and the RUN command to do this, as shown in the second example below.
Most of the tricks to using DDE involve finding out the names of the services and topics available, and the commands and syntax the application recognizes. Far too often these are poorly documented, if they’re documented at all.
? DDEInitiate("WinWord","Document1")
= DDESetOption("Safety",.F.) && Turn off dialogs
nChannel = -1
nTries = 0
DO WHILE nChannel = -1 and nTries < 3
nChannel = DDEINITIATE("WinWord","System")
IF nChannel = -1 && Command failed
IF DDELASTERROR() = 16 && Connect failure
nTries = nTries + 1
* You'll need to fully qualify the path to
* your server, below, or this will fail.
RUN /N7 WINWORD.EXE
ENDIF
ENDIF
ENDDO
IF nTries = 3 && Three tries and you're out...
WAIT WINDOW "Three strikes, you're out."
ELSE
WAIT WINDOW "Good to go!"
ENDIF
lResult = DDEAbortTrans( nChannel )
uResult = DDEPoke( nChannel, cItemName, cData
[ , cFormat [ , cAsynchUDF ] ] )
uValue = DDERequest( nChannel, cItemName
[ , cFormat [ , cAsynchUDF ] ] )
Parameter |
Value |
Meaning |
nChannel |
Integer |
Identifies the particular DDE conversation, set by DDEInitiate(). |
lResult |
.T. |
The attempt to abort the transaction succeeded. |
.F. |
Unable to abort the transaction. We haven't been able to produce this at will, but we suspect it would occur only when connection with the other party in the DDE conversation was lost. |
|
cItemName |
Character |
The item whose data is either being requested or changed. For Excel, this could be a row/column address such as "R2C3". |
cData |
Character |
The data to be placed in cItemName. All data must be passed as a character string and the receiving application must translate it to numeric, if necessary. |
cFormat |
Character |
Dictates the format the data appears in, most often CF_TEXT, which is tab-separated fields. |
cASynchUDF |
Character |
If an asynchronous connection is desired (usually because the answer might take some time), this parameter supplies the name of the function to run when the data is ready. The six pieces of data this function receives are spelled out in the Help file. |
uResult |
Logical |
Reports whether the data was accepted by the application. Use DDELastError() to get information on the error if lResult is .F. |
Integer |
If an asynchronous transaction is selected, DDEPoke() returns the unique transaction number, which is the last parameter to the cAsynchUDF when the transaction is finished. |
|
uValue |
Character |
The result of querying cItemName. |
Integer |
The transaction number, which is the last parameter to the cAsynchUDF when the transaction is finished. |
DDERequest()
and DDEPoke()
are the read and write equivalents of transactions via DDE. DDERequest()
requests a value from another application and DDEPoke()
places a value in it.
In some cases, poking or requesting a value might cause the server application to have to go off and do some number crunching or other time-consuming processing. In this case, an “asynchronous” connection can be made, whereby the server app “calls back” Visual FoxPro when the deed is done, and passes back results by starting a User-Defined Function (UDF) within FoxPro and passing it several parameters. It is up to your application to monitor an asynchronous transaction, because the DDESetOption(“Timeout”) value does not apply. If an excessively long time has passed or other conditions make the transaction no longer necessary, the transaction can be halted with DDEAbortTrans()
.
* get the value of cell 2,3
nValue = VAL(DDERequest(nExcel,"R2C3"))
lResult = DDEExecute( nChannel, cCommand )
nResult = DDEExecute( nChannel, cCommand, cAsynchUDF )
Parameter |
Value |
Meaning |
nChannel |
Integer |
Identifies the particular DDE conversation, set by DDEInitiate(). |
cCommand |
Character |
The command for the server to execute. Each DDE server is unique in the commands, syntax and format it understands. |
cAsynchUDF |
Character |
If an asynchronous connection is desired (usually because the answer might take some time), this parameter supplies the name of the function to run when the data is ready. The six pieces of data this function receives are spelled out in the Help file. |
lResult |
.T. |
Synchronous command completed successfully. |
.F. |
Synchronous command failed. Test using DDELastError(). |
|
nResult |
-1 |
Asynchronous command failed. Test using DDELastError(). |
Integer |
The transaction number to be returned as the last parameter of the cAsynchUDF specified above. |
DDEExecute()
allows Visual FoxPro to pass commands to the DDE Server, which the server then executes.
* Send the command to WinWord to print the current document
=DDEExecute(nWord,"[FilePrint]")
nValue = DDELastError()
Parameter |
Value |
Meaning |
nValue |
0 |
Last command did not generate an error. |
Integer |
See table of errors in Help. |
Use DDELastError()
to detect problems that DDE is having. Rather than firing the global error handler set in ON ERROR
, this function gives you the option of handling the error locally.
IF DDELastError() = 0 && no error, continue processing
lValue = DDESetOption( "Safety" [, lSafetyOn ] )
lValue | nValue = DDESetOption( "Timeout" [, nTimeout ] )
Parameter |
Value |
Meaning |
lSafetyOn |
Logical |
Determines whether warning dialogs appear (.T.) or not (.F.). See example in DDEInitiate(), above. |
Omitted |
Returns present setting of safety in lValue. |
|
lValue |
.T. |
Function completed successfully. |
.F. |
Function failed. |
|
nTimeout |
Numeric |
Amount of time FoxPro waits for a response from the other end of a DDE conversation, expressed in milliseconds. Default is 2 seconds. Legal values from 1 to 594 billion or so—too long for us to wait! |
Omitted |
Returns current timeout setting in nValue. |
|
nValue |
Numeric |
Current setting for timeout. |
This function is the DDE equivalent of the SET command and matching SET()
function. Without the second parameter, it returns the present setting; with the second parameter, it sets the specified setting to the passed values. With Safety set to .F., our recommended setting, you will need to do more error checking, but your users will not be presented with dialogs allowing them to mangle the DDE portion of your application. As for Timeout, we expect that you might need to raise it for slower systems or systems with a lot going on, but we’ve never had to mess with the settings ourselves.
? DDESetOption("Timeout") && Display the current timeout
lValue = DDEAdvise( nChannel, cItemName, cUDFName, nTypeLink )
Parameter |
Value |
Meaning |
nChannel |
Integer |
Identifies the particular DDE conversation, set by DDEInitiate(). |
cItemName |
Character |
The name of the item the application is to monitor. The server alerts Visual FoxPro if the data changes. For Excel, this might be a cell address. |
cUDFName |
Character |
The name of the routine to run when a change is made to the item identified above. |
nTypeLink |
0 |
Manual or "cold" link. Turns off a warm or hot link. |
1 |
Notify or "warm" link. |
|
2 |
Automatic or "hot" link. |
|
lValue |
.T. |
Function completed successfully. |
.F. |
Function failed. |
This function allows the creation of “warm” and “hot” links between Visual FoxPro and another application. Both cause a Visual FoxPro routine to run when a change occurs in their environment. The difference between the two link types is that the warm link only notifies Visual FoxPro of the change, while the hot link passes the changed data as well.
The routine you indicate receives six items, similar to the AsynchUDF in DDEPoke()
and DDERequest()
above. Your Visual FoxPro application can then determine what action to take based on this new data.
* This command sets up a hot link between an open Excel DDE
* conversation and FoxPro. If the operator or a function
* changes the value of the contents of row 2, column 2, the
* FoxPro routine will run.
= DDEAdvise(nExcel, "R2C2", "MyUDF", 2)
lValue = DDEEnabled( [ nChannel ] [ , lOnOff ] )
Parameter |
Value |
Meaning |
nChannel </td> | Integer |
Identifies the particular DDE conversation, set by DDEInitiate(). |
</tr>
Omitted |
If no channel is specified, the command applies globally to all channels. |
|
lOnOff |
Logical |
Turns on or off either the specified channel or all channels of DDE while processing a time-critical function. |
Omitted |
Reports whether the specified channel or all channels currently have DDE enabled. |
|
lValue |
.T. |
Indicates either that a channel or global DDE processing is enabled, or that the last change to the status was accepted, depending on parameters supplied. |
.F. |
Channel or global DDE processing is disabled, or the function failed to complete successfully. |
Parameter |
Value |
Meaning |
cName |
Character |
Name of the service to maintain. |
cAction |
"Advise" |
Switch to set notification of changes on or off. The server side of warm and hot links, like DDEAdvise(). |
"Define" |
Defines the name of a new service. |
|
"Execute" |
Switch to enable or disable the execution of commands by the service. |
|
"Formats" |
Specifies which formats data may be transferred in. |
|
"Poke" |
Enables or disables a client's ability to poke data to this service. |
|
"Release" |
Releases this service name. |
|
"Request" |
Enables or disables DDERequest() messages. |
|
cFormat |
Character |
Specifies the format in which data can be transferred, using Microsoft shorthand like CF_TEXT (tab-delimited ASCII). |
lSwitch |
Logical |
For Advise, Execute, Poke and Request above, determines whether the feature should be enabled. |
Omitted |
If both cFormat and lSwitch are omitted, returns the current state of the specified action. |
|
lResult |
.T. |
If requesting the status of a feature, it's enabled. If attempting to change an item, the change was successful. |
.F. |
If requesting the status of a feature, it's disabled. If attempting to change an item, the change failed. |
Parameter |
Value |
Meaning |
cService |
Character |
A service name, defined with DDESetService(), above. |
cTopic |
Character |
A name for the individual topic. |
Empty string |
Indicates that the UDF name that follows should be run for all topic names that don't have a UDF defined for them. |
|
cUDFtoRun |
Character |
The name of the routine to be run when this service and topic are accessed. This UDF receives six parameters, as described in the Help, to indicate what it is to do. |
Omitted |
If no UDF is specified, the topic name is released. |
|
lValue |
.T. |
Topic name successfully created or released. |
.F. |
Command failed. Use DDELastError() to determine why. |