views:

248

answers:

2

Hi,

I have a .NET application that is launched via a Delphi program using ShellExecute. Unfortunately when launched in this manner, the application does not seem to be reading its app.config file correctly, as if the file did not exist.

I have tried testing the application in other scenarios, e.g. calling from a shortcut with the the working directory set to a different folder and it runs fine.

[Edit]The Environment.CurrentDirectory property returns the directory of the Delphi program.

Any ideas would be really appreciated.

Cheers,

James

+1  A: 

Apparently the process you spawn cannot handle the fact that the working directory is not it's own.

You could open the file using CreateProcess(). I have a small example with waiting for (but you can clip that out):

procedure ExecuteAndWaitFor(CommandLine, CurrentDirectory: string; Environment: TStrings);
var
  List: TList;
  ActiveWin: HWnd;
  i: Integer;
  Ret: Longword;
  SI: TStartupInfo;
  PI: TProcessInformation;
  MadeForeground: Boolean;
  AssociatedCommandLine: string;
begin
  // find the association ot use
  AssociatedCommandLine := GetAssociatedCommandLine(CommandLine);
  // first we create a list of windows which we need to block...
  List := TList.Create;
  try
    ActiveWin := Windows.GetForegroundWindow;
    // get the list of all visible and active top windows...
    if not Windows.EnumThreadWindows(GetCurrentThreadId,@InternallyThreadWindowCallback,Integer(List)) then RaiseLastOSError;
    // disable all those windows...
    for i := 0 to List.Count - 1 do Windows.EnableWindow(HWnd(List[i]),False);
    try
      // create the process
      System.FillChar(SI,sizeof(SI),0);
      SI.cb := sizeof(SI.cb);
      // todo: environment
      if not Windows.CreateProcess(nil,PChar(AssociatedCommandLine),nil,nil,False,NORMAL_PRIORITY_CLASS,nil,PChar(CurrentDirectory),SI,PI) then RaiseLastOSError;
      // wait until the process is finished...
      MadeForeGround := False;
      repeat
        // process any pending messages in the thread's message queue
        Application.ProcessMessages;
        if not MadeForeground then begin
          Windows.EnumThreadWindows(PI.dwThreadId,@InternallyTopWindowToForeMost,Integer(@MadeForeGround));
        end;
        // wait for a message or the process to be finished
        Ret := Windows.MsgWaitForMultipleObjects(1, PI.hProcess, False, INFINITE, QS_ALLINPUT);
        if Ret = $FFFFFFFF then RaiseLastOSError;
      until Ret = 0;
      // free the process handle
      Windows.CloseHandle(PI.hProcess);
      WIndows.CloseHandle(PI.hThread);
    finally
      // enable all those windows
      for i := 0 to List.Count - 1 do Windows.EnableWindow(HWnd(List[i]), True);
    end;
    Windows.SetForegroundWindow(ActiveWin);
  finally
    List.Free;
  end;
end;

Added some missing utility functions:

uses
  SysUtils, Registry;

function GetAssociatedFile(const Extension: string; const RemoveParameters: Boolean = False): string;
var
  FileClass: string;
  Reg: TRegistry;
  Position: Integer;
begin
  // initialize
  Result := '';
  // create registry entry
  Reg := TRegistry.Create(KEY_EXECUTE);
  try
    // find the given extension
    Reg.RootKey := HKEY_CLASSES_ROOT;
    FileClass := '';
    if Reg.OpenKeyReadOnly(ExtractFileExt(Extension)) then begin
      FileClass := Reg.ReadString('');
      Reg.CloseKey;
    end;
    if FileClass <> '' then begin
      if Reg.OpenKeyReadOnly(FileClass + '\Shell\Open\Command') then begin
        Result := Reg.ReadString('');
        Reg.CloseKey;
      end;
    end;
  finally
    Reg.Free;
  end;
  // remove the additional parameters
  Position := Pos('"%1"', Result);
  if RemoveParameters and (Position > 0) then
    Result := Trim(Copy(Result, 1, Position - 1))
  else
    Result := Trim(Result);
end;

function GetAssociatedCommandLine(const CommandLine: string): string;
begin
  // build the command line with the associated file in front of it
  Result := Trim(GetAssociatedFile(CommandLine, True) + ' ') + '"' + CommandLine + '"';
end;

function InternallyThreadWindowCallback(Window: HWnd; Data: Longint): Bool; stdcall;
var
  List: TList;
begin
  Result := True;
  if (not IsWindowVisible(Window)) or (not IsWindowEnabled(Window)) then Exit;
  List := TList(Data);
  List.Add(Pointer(Window));
end;

function InternallyTopWindowToForeMost(Window: HWnd; Data: LongInt): Bool; stdcall;
begin
  Result := True;
  if (not IsWindowVisible(Window)) or (not IsWindowEnabled(Window)) then Exit;
  SetForegroundWindow(Window);
  PBoolean(Data)^ := True;
end;
Ritsaert Hornstra
Thanks,Unfortunately I am unlikely to be able to change the Delphi program so if possible I am going to change the .NET program.
James
In that case, try to make sure that you read the settings file from a fullpath, for example, get the local appdata directory and use a fixed subdirectory from there.
Ritsaert Hornstra
I probably didn't make it clear in my original post, but I needed the app.config file to be used automatically anywhere where the ConfigurationManager class was being used. My program uses some third party libraries that automatically load their settings from the app.config file and they were not working correctly.
James
@James: So: you had a Delphi application that you can't change on one hand, and your own application with some 3rd party dlls that you cannot change in the other. The problem was that the current working directory was incorrect at startup. -> you shoudl clarify that in your question. Perhaps you can set the current directory as the first thing you do in your application or are the DLL's loaded directkly at startup. In that case you indeed need some launcher application that launches your app with the CWD set correctly to what the DLL's expect.
Ritsaert Hornstra
Hi Ritsaert,My question was posted rather hurriedly as I had a deadline but I did mention in my original post that I had tested the effects of changing the working directory when launching the program normally and it still worked.I did try changing the working directory early on in the Main method but it didn't solve the problem.I don't think the problem is down to the working directory but some side effect of how the process is being created and how .NET works.Anyway, I have solved the problem and I have marked your answer as useful so please let's leave it at that.
James
+1  A: 

Well I researched it a bit and there does not appear to be a solution that is both simple and elegant.

The easiest way round it seems to be to call an intermediate .NET program that then runs the target application via Process.Start and passes the parameters on. Not ideal but it's simpler than the other solutions I have found so far.

James