![]() |
|
Published 1998-01-01 Printer-friendly version
In 16 bit Clarion programs, if you run a second copy of a running application then the first copy comes to the front, and the second copy doesn't get started. Although this can be a real pain in some cases it certainly has its uses! Now, unfortunately, (or fortunately, depending on your point of view) 32 bit applications don't behave this way. They are allowed to launch multiple times on the same PC. This article deals with an easy way of stopping this from happening.
Essentially, after thinking for a short time, the problem becomes quite simple. When a 32 bit application is started it needs to:
And while it's running it needs to:
The first part is not too difficult. While there are a number of ways of communicating between two programs in Windows, one of the easiest to implement (from a Clarion perspective) is DDE. Simply put, each program makes itself into a DDE Server. But when it runs, before it makes itself a server, it first tries to connect to itself ( ie it becomes a DDE Client ). If it can't become a client, then another copy of itself isn't already running and it carries on running itself (becoming a server in the process). If it can connect, then another copy of itself is running, so a message is sent to it.
So we're going to create 2 functions here, one for each section:
We're also going to see what bits of code we need to link these functions into our application.
Because I'm strongly in favour of re-using code between applications, I'm also going to demonstrate putting these functions into a template. In a previous article (The Joys of Reusable Code - Volume 1, Issue 1 of Clarion Online) I discussed how to make function libraries. You could, of course, put these functions into a library, and then just have a template to implement them. However, this time I'm going to show you another way of reusing code in the same application.
Lets start with the easy bits.
In the Global Program Setup embed point put the following;
IF Running() ! Returns 0 if not already running RETURN ! Closes the currently running application END
This will run just after the program starts. The Running function, which we're about to write, will check for existing instances and send an instruction to them if they exist. Running will return a zero if it's the first instance of the program, and a positive number otherwise. So if it returns a number, this instance must quietly close - hence the Return command ( Remember we're in the main program bit here and a Return ends the program here. )
Lets look at the Running function a little more closely...
Running FUNCTION
Channel LONG
CODE
Channel = DDECLIENT(UPPER(COMMAND(0)))
IF Channel <> 0
DDEEXECUTE(Channel,'INFRONT')
DDECLOSE(Channel)
END
RETURN(Channel)
Ok, it's quite short and sweet. Command(0) is an internal Clarion function that returns the name of the application currently running. Channel is a local variable that we use to store the result of the attempt to connect to the server. If the connection was successful then Channel will contain a value - if unsuccessful, then a zero. As this is the same as what the Running function is required to return, we can simply return it as-is. If the other instance is running, then we send it a command to come to the front.
Ok, so that's the client side of things out of the way; lets look at the server side. First, we need to add the following code to the After Opening the Window Embed point for the Frame of the app.
Co_Channel = DDESERVER(UPPER(COMMAND(0)))
Co_Channel is a Long and needs to be added to your Frame's Data variables.
That's all we need to do to make the frame of the app a DDE Server which will respond to calls from the later instances (the Clients). And all that's left to do is add the following also to the Frame, but this time in the Other Window Event Handling embed point.
Listen()
The Listen Procedure is defined as follows...
Listen PROCEDURE
CODE
IF EVENT() = Event:ddeExecute
IF DDEVALUE() = 'INFRONT'
Target{prop:iconize} = 0
BringWindowToTop(TARGET{prop:handle})
END
END
Nothing too special here, if the event is a ddeExecute event (which means some other application has sent us a ddeExecute command) and the ddeValue is INFRONT (i.e., the command that was sent) then we restore the window (in case it's minimised) and bring it to the front. The BringWindowToTop function is a Windows API call, and it takes the handle of a window. I'm using the Clarion built in variable Target and the property {prop:handle} which always points to the current window.
What's left to do? Well we still need to prototype the DDE functions and Windows API functions in the global map. So in the global embed pointInside the Global Map put the following:
INCLUDE('DDE.CLW')
COMPILE('***',_WIDTH32_ = 1)
MODULE('Windows')
BringWindowToTop(ULONG),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
END
***
COMPILE('***',_WIDTH32_ = 0)
MODULE('Windows')
BringWindowToTop(USHORT),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
END
***
Wow, that looks like a mouthful. This is because in 16 bit mode then BringWindowToTop function takes a Ushort, and in 32 bit mode it takes a Ulong. So we make use of the Compile compiler directive to cater for both cases. From the point of view of this article we don't really need to do this (I'll leave you to figure out why) but it's best to do it right in case you use the function elsewhere. The function returns a byte and, as is usual for Windows API prototypes, we also use the Pascal and Raw attributes.
Ok so now we have all the bits in place, how do we make this truly reusable? Well there are a number of options:
I'm going to use the second method this time for two reasons. First, I've already described the first method and second, because we're all in the process of moving from Clarion 2 to Clarion 4. This method is more easily portable as the template should work as-is in both environments. (I say "should", because I haven't yet received my copy of C4 Gold so I can't test it. However if there are any issues involved then I'll post any updates to our web site at www.capesoft.com)
I'm going to describe the template in some detail to shed some light on this under-used area of Clarion.
First, select what kind of template it's going to be. Clarion offers 3 kinds of templates - a procedure template, a code template and an extension template. A code template is associated with some kind of window control, which we don't need in this case; a procedure template is used to make one procedure. We have more than one procedure to do, so I'm going to use an extension template, which EXTENDS applications or procedures, and that IS what we're doing here.
I'm going to call this template OnlyOne.Tpl and it needs to be stored in your \cw20\template directory for Clarion 2 users and in \Clarion4\template for Clarion 4 users.
Now remember, the goal here is to do it with a single extension, so the next decision to make is where will this extension go. It could be global or it could be attached to the frame. As we're going to modify the frame, and we can more easily modify the global from the frame than vice versa, let's attach it to the frame. First things first - let's start the template.
#TEMPLATE(OnlyOne,'Only 1 instance of 32 bit apps') #EXTENSION(OnlyOne,'Make this app unique in 32 bit instances' )
Not so bad so far, nothing to be scared of ..... The #Template command is always there at the start of a new template class (A group of templates is called a class - we're only doing one template in this class, but we could have more if we wanted to.) And as we mentioned earlier, we're going to write an Extension template, hence the #Extension command. The rest of the template takes place in embed points. To do that we surround the code with #AT and #ENDAT commands. Let's do the global ones first...
#!-----------------------------------------------------------------
#AT(%CustomGlobalDeclarations)
#PROJECT('OnlyOne.Clw')
#ENDAT
This adds the OnlyOne.Clw module to your application as an external source module. We'll create the OnlyOne.Clw file after this template.
#!-----------------------------------------------------------------
#AT(%ProgramSetup)
If Running() ! Returns 0 if not already running
Return ! Closes the currently running application
End
#ENDAT
#!----------------------------------------------------------------
#AT(%GlobalMap)
INCLUDE('DDE.CLW')
MODULE('OnlyOne.Clw ')
RUNNING(),LONG
LISTEN()
END
COMPILE('***',_WIDTH32_ = 1)
MODULE('Windows')
BringWindowToTop(ULONG),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
END
***
COMPILE('***',_WIDTH32_ = 0)
MODULE('Windows')
BringWindowToTop(USHORT),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
END
***
#ENDAT
#!----------------------------------------------------------------
I've already explained these two. Now for the embeds that affect the frame...
#!---------------------------------------------------------------- #LOCALDATA Co_Channel LONG #ENDLOCALDATA
That adds the Co_Channel to the local data.
#!---------------------------------------------------------------- #AT(%AfterWindowOpening) Co_Channel = DDESERVER(UPPER(COMMAND())) #ENDAT #!---------------------------------------------------------------- #AT(%WindowOtherEventHandling) Listen() #ENDAT #!----------------------------------------------------------------
And again, we've been through these bits already...
Last, we create the OnlyOne.Clw file which we'll put in the \cw20\libsrc directory (or \clarion4\libsrc directory if you're using Clarion 4)
MEMBER('')
! -------------------------------------------
! Used by the OnlyOne.Tpl file and included
! Into apps where only one instance is wanted
! Even in 32 bits....
! -------------------------------------------
MAP
INCLUDE('DDE.CLW')
MODULE('OnlyOne.Clw ')
Running(),LONG
Listen()
END
COMPILE('***',_WIDTH32_ = 1)
MODULE('Windows')
BringWindowToTop(ULONG),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
END
***
COMPILE('***',_WIDTH32_ = 0)
MODULE('Windows')
BringWindowToTop(USHORT),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
END
***
! -------------------------------------------
Running FUNCTION
Channel LONG
CODE
Channel = DDECLIENT(UPPER(COMMAND(0)))
IF Channel <> 0
DDEEXECUTE(Channel,'INFRONT')
DDECLOSE(Channel)
END
RETURN(Channel)
! -------------------------------------------
Listen PROCEDURE
x LONG(0)
CODE
IF EVENT() = Event:ddeExecute
If DDEVALUE() = 'INFRONT'
x{prop:iconize} = 0
BringWindowToTop(TARGET{prop:handle})
END
END
! -------------------------------------------
One interesting point about this member module is the fact that it's generic. In other words, because no program name is filled in the Member statement, it can be compiled in any program. However, because it's generic, all the functions it calls must be prototyped inside the module - hence the repeated map. Although we need to keep theInclude ('DDE.CLW') statement in the Global Map (because we're calling the Server part directly in the frame) I have also decided to keep the BringWindowToTop prototype there, as well. This means that the function will become available to your whole application in case you want to use it somewhere else.
And that's the whole shebang! Don't forget to register the template before using it! Enjoy...
As always I'm happy to answer questions about this - just email me at Bruce@capesoft.com.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239