views:

614

answers:

5

I am trying to extend a 3rd party application so that it can be invoked via command line in addition to using the windows form GUI (mixed mode is desired). It's a fairly simple program which basically loads a file and then you click a button it starts sending UDP network packets.

I need to invoke the application from another and would like to pass in an argument and need to be able to return the ExitCode to the calling app. From what i've read, in order to do so you need to add the compiler directive {APPTYPE CONSOLE}.

I did this and my application worked as I wanted it to except sending the network packets slowed down to a crawl. I found that whenever I moved my mouse around on the form. That the network transfer rate increased significantly. I suspect there is some type of Windows Message queue problem and moving mouse is causing interrupts which in turn is causing the message queue to be processed?

I have googled around and tried calling Application.ProcessMessages and PeekMessages in a Timer with a 1ms interval and that didn't help at all. I found in this user manual for some other application it says that Indy 10 is supported in both APPTYPE CONSOLE and GUI types. Quite frankly this just confuses me as I would have assumed that all network library would work in both modes... but like I said I'm not familiar with Delphi.

I am positive that the issue is isolated to a single line in my application and that is whether or not {APPTYPE CONSOLE} is included or not.

Anyone have any ideas?

Version Info:
Delphi 7 Personal (Build 4.453)
Indy 9.0.4

+2  A: 

If you want an application to return an "error" code there is no need to make it a console application. You only need to set the ExitCode, e.g.

ExitCode := 10;

in a batch file

@Echo off
project1
echo %errorlevel%

Will display the application, then display 10 when.

Note: It is also possible to create a console window dynamically from the windows API using AllocConsole or to attach using AttachConsole.

I created an object wrapper for this once, but no longer have the code available. From memory it didn't support redirection (because I didn't need it).

Gerry
I tried that. It will echo 0. When calling the application from command line when the application doesn't include the {APPTYPE CONSOLE} line it returns immediately. However... If what might work is "start /wait <appname>". I'll give that a try tomorrow.
blak3r
@Gerry I gave you +1 for being the first to suggest the ExitCode approach. I thought that mghie's answer was a little more complete so i choose his as the accepted solution. Thanks for contributing!
blak3r
A: 

A timer with 1ms will only fire about every 40 ms (due to Windows limitations), so it won't help. I have seen effects like you describe with mixed console and GUI apps, another is that they don't minimize properly.

Instead of enabling the console in the project, you could probably use the CreateConsole API call (Not sure whether the name is correct) to create one after the programm was started. I have seen no adverse effects in the one (!) program I have done this.

But this is only necessary if you want to write to the console. If you only want to process command line parameters and return an exit code, you do not need a console. Just evaluate the ParamCount/ParamStr functions for the parameters and set ExitCode for the return value.

dummzeuch
@dummzeuch thanks for the suggestion. See the comments I just added to Gerry's post. I was hoping to use the real console window so the calling application could capture it's standard output. As for the timer, I think even running at 40ms it would have improved the time slightly... but it didn't. Each sample took 14s. If it had worked I probably would have added 20-40 Timers as a workaround haha. But, since it didn't -- I think that's a clue that Application.ProcessMessages isn't the problem to begin with...
blak3r
And a timer functions because of a windows message so it is funny to try to drive a message pump by reacting to messages from that same message pump.
Lars Truijens
@Lars Truijens - +1 when put that way it's absurd.
blak3r
+4  A: 

If you add {APPTYPE CONSOLE} to your application even though you desire mixed mode execution, then you will have to live with a console even when the application is in GUI mode. You can of course close the console, but this will cause some flicker and feels a bit hackish to me.

You should be able to do what you want without a console program. A small test program proves that the exit code can be read from a GUI program:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Close;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ExitCode := 42;
  Timer1.Interval := 1000;
  Timer1.Enabled := TRUE;
end;

If this is executed with the following cmd file:

@echo off
start /WAIT project1.exe
echo %ERRORLEVEL%

the program shows its main form for 1 second, closes, and the script prints 42 to the console window.

Now for capturing the output - doing this from a GUI program is actually easier than doing it from a console program, if you allow for the use of a temporary file. You need to start the program with a command line parameter anyway, so why not give it the name of a temporary file, wait for the application to finish, read in the file and delete it afterwards?

mghie
@mghie Thanks for the completeness of this post. I originally thought that ExitCode didn't work when using {APPTYPE CONSOLE} but it turned out to be a bug in my code.
blak3r
A: 

If some threads in your console application call Synchronize (and I guess the Indy stuff is actually doing that), you have to make some preparations:

Assign a method to the WakeMainThread variable. This method must have the signature of TNotifyEvent.

Inside this method call CheckSynchronize.

For additional information see the Delphi help for these two items.

Uwe Raabe
@Uwe Raabe - I ended up implementing it in an easier fashion. Someone else might benefit from this so thanks for contributing. I did a very cursory search for how to do this and didn't come across anything so if you do... might be a good idea to posted it for someone else.
blak3r
A: 

If I understand you correctly, then you want your app to have two modes:

  1. If no argument is passed, run in GUI mode
  2. Run in non-GUI mode otherwise

The easiest is if you can centralize your logic so it can be called from one method (CoreLogic in my example).

The below app then should work fine.

Two tricks:

  1. Application.ShowMainForm := False; that will not make the MainForm show at all.
  2. ExitCode := 327; which will set your return code (like mghie and Gerry already mentioned).

A few notes:

  • because the CoreLogic does not process any windows messages, anything in your application that depends on Windows messages being processed will stall.
  • if you need windows message processing, then just all Application.ProcessMessages() inside your CoreLogic
  • if you need your form to be visible, then you change the logic inside your MainForm to test for the commandline parameters, and exit when it's work as been done (by calling Application.Terminate()). The best place to put that logic in is the event method for the MainForm.OnShow event.

Hope this helps :-)

program VCLAppThatDoesNotShowMainForm;

uses
  Forms,
  MainFormUnit in 'MainFormUnit.pas' {MainForm},
  Windows;

{$R *.res}

procedure CoreLogic;
begin
  Sleep(1000);
  ExitCode := 327;
end;

procedure TestParams;
begin
  if ParamCount > 0 then
  begin
    MessageBox(0, CmdLine, PChar(Application.Title), MB_ICONINFORMATION or MB_OK);
    CoreLogic();
    Application.ShowMainForm := False;
  end;
end;

begin
  Application.Initialize();
  Application.MainFormOnTaskbar := True;
  TestParams();
  Application.CreateForm(TMainForm, MainForm);
  Application.Run();
end.
Jeroen Pluimers