views:

184

answers:

3

Hello all,

I'm trying the achieve the following (using Delphi7): After logging in to my program, the user gains control, but in the background a separate thread downloads a file from the Internet to check if the current license key is blacklisted. If it is, the user receives a prompt and the program terminates.

So I've created a separate TThread class which downloads the blacklist from the Net using InternetOpenURL/InternetReadFile.

My problem is the following:

If the user quits my program before the downloading in the background finishes, the license manager thread should be terminated by the main thread.

If the thread has done its job, it should terminate automatically.

If I use FreeOnTerminate := true I can't terminate the thread from the Main thread. But otherwise, how can I make the thread free its resources after it has done its job?

My other question is:

If the license key is blacklisted, I use Synchronize to do something with certain resources of the Application's main form.

But how do I know if the user has already closed the app and the program is in FormDestroy of the main form, for example? If I Synchronize in the wrong time, it could lead to access violations...

Thanks!

A: 

For the second question, don't bother updating the UI from the thread. Just set a global flag. i.e. IsBlackListed := true; Now you can use that global flag as the basis for nagging the user, in response to some user-initiated action. Such as OnFileSave... ShowMessage('I would love to save that file for you, but unfortunately, you are using a blacklisted key.');

IsBlackListed Redux....

// globally...
var
  IsBlackListed  : integer;

...
initialization
  IsBlackListed  := 0;

...
// in your thread:
if OhMyGodThisIsABlackListedKey then
  IsBlackListed := 1;

... Back in the main code, maybe in the OnSaveMyData event:

if IsBlackListed then
begin
  IsBlackListed := -1; // so we don't repeat ourselves
  MessageBox('Hey, you naughty pirate!');
  MyDBConnection.Whatever.DoSQL('insert into licensetable (name,key) values(pirate,blacklisted);
end;
Chris Thornton
Hello! If the key is blacklisted, I need to update a record in the program's database, and the DB components are on the main form - thus it should be updated before FormDestroy. How do I go about that?
Steve
Skip the DB components, and "insert into blacklist (username, key, blah blah) values(yadda yadda);"
Chris Thornton
I'm already calling it like:MainForm.IBCQuery.SQL.Text := 'INSERT INTO ...';I can't create the IBCQuery component dynamically, as I don't want to make multiple connections to the database. (and IBCConnection is also on the MainForm)
Steve
Then just skip it, and do it next time when they're not in the middle of a shutdown. I'll add some code to my answer to deal with that....
Chris Thornton
Thanks for the code, but the problem is that as soon as a blacklisted key is detected, it should INSTANTLY trigger a showmessage, save something in the db and then shut down the app. For this I can use Synchronize from my thread.The problem is that if the application is shutting down and the main thread is in FormDestroy, doing a Synchronize will lead to access violations... So how do I know from my thread that it's safe to do a Synchronize?
Steve
@Steve - I'm sure you have your reasons, but I would propose that you are going about it all wrong. You apparently don't agree, I understand. BTW, and FWIW, I've made many thousands of $$$ by detecting blacklisted keys, taking a somewhat "softer" approach, and converting pirates into sales. But I'm sure your way is good too.
Chris Thornton
Chris, this is not shareware software. This is corporate software. They are paying on a yearly basis, so I issue them yearly licenses. But if a user is not paying in time or not paying at all, I need a way to disable their access within the one year interval. I don't want to nag them, just disable access to the whole program.
Steve
A: 

First, in your check thread object, create a "completed" flag. You can check this is true to determine if all is well. As Chris T suggests, have the thread set a global to indicate things are good/bad, so that the main thread can use something like a timer to check that and that all is well or take appropriate action.

Then, if your app wants to quit early, call

  MyThread.Terminate;
  MyThread.WaitFor;

And in the thread, check for Terminated being set at appropriate points. This way you can close down nicely.

mj2008
I know about WaitFor, but if the thread finishes its job before the program is closed, how can it free its resources (strings it uses, for example) without using the FreeOnTerminate flag?
Steve
A: 

I think this is what you're trying to do.... Assign an OnTerminate() event to the TThread, which gets excuted as part of your main thread when the worker thread is terminated. You can do all of your UI updates there (the black listing). Make a property of your worker thread such as 'DownloadComplete', and only do the blacklisting/validating if thats set to true in the OnTerminated event. That should allow the thread to free itself regardless of the state of the download when the program runs, as long as you .watifor() it before the program exits.

GrandmasterB
But this way OnTerminate won't be called before MyThread.Execute finishes, right? Because in MyThread.Execute I'm downloading the file - and what happens if the downloading gets stuck due to an unresponsive server? MyThread.WaitFor will keep waiting till the download is finished and the application hangs... (I don't want that)
Steve
How are you doing the download? You should be able to cancel it at any point by checking the terminated property of the thread. A good download routine should have either a timeout or a callback allowing you to bail out of the download if the thread is marked as terminated.
GrandmasterB
I'm using code like this:http://delphi.about.com/od/internetintranet/a/get_file_net.htm(InternetOpenURL, InternetReadFile)I think these functions have a time-out value, but you can't cancel them...
Steve
I've never used that, but if it doesnt have it, I'd switch to something that allows for a timeout should the network connection hang. Otherwise, threads or not, your process will hang until physically terminated. Really, based on the code you are using, doing the download using socket reads isnt that much more complicated. Its basically the same approach - open the connection, send an http GET, and start reading the response. You can use something like the synapse library (http://www.ararat.cz/synapse/doku.php/start) for that.
GrandmasterB
Thanks for the suggestion. My problem with Synapse is that it seems to be outdated (no Delphi2010 version, Delphi2009 only experimental) - I'd prefer not to rely on any third party components for such simple thing. Any other suggestions?
Steve
Delphi has its own socket class (I think its called TClientSocket or TClientWinSocket or somesuch) that can be used for it if you want to avoid a component. Or you can use the windows socket api functions directly.
GrandmasterB