none
How can I safely load an unmanaged assembly into memory once and then execute it multiple times using unique command line values? RRS feed

  • Question

  • Issue I Need To Solve: 


                    I am writing an automated process in managed code (C#) that needs to make several calls to an unmanaged application, however I need to eliminate the overhead of creating a new process and loading the application into memory for each call. (So using the Process class to run a command line calls does not work for me here).

    Attempted Solution: 

                    I figured the simplest way to execute the application multiple times but still insure the application is only loaded once was to use the current Appdomain object and run the ExecuteAssembly function on the desired  unmanaged application. For managed code this works fine, you just call ExecuteAssembly(exePath, null, argList) where exePath is the path to the application to run and argList is an array of strings containing the argument to pass to the main function of the application. However, for an unmanaged application the main function executes using whatever the original command line string that was used to execute the wrapper application and ignores the values set in argList. I then attempted to change the original command line string stored for the current process. However I could not find any managed methods that could update the value (you can get the value but not set it). I found a way to modify the existing stored command line by p/invoking the Kernel32.dll GetCommandLine() function and overwriting the existing array, but that is not safe and you cannot expand the size of the string without stomping on other process memory.

                    Here is a minimal example of code to wrap a call to an unmanaged exe from a managed application:

    1 using System;  
    2 using System.Collections.Generic;  
    3 using System.Text;  
    4 using System.Runtime.InteropServices;  
    5 using Microsoft.Win32.SafeHandles;  
    6 using System.ServiceProcess;  
    7 namespace TestApp  
    8 {  
    9     class Program  
    10     {  
    11  
    12         [DllImport("kernel32.dll", CharSet = CharSet.Auto)]  
    13         private static extern System.IntPtr GetCommandLine();  
    14  
    15         static void Main(string[] args)  
    16         {  
    17             // Get a pointer to the current command line char pointer  
    18             IntPtr ptr = GetCommandLine();  
    19             // Create a string with the new command line to use  
    20             string cmd = @"c:\my path\my.exe  arg1 arg2";  
    21             // Copy the new command string over the existing command string  
    22             // NOTE: This is not safe and can stomp on existing process memory if the   
    23             //  new char array is longer than the old char array  
    24             Marshal.Copy(cmd.ToCharArray(), 0, ptr, cmd.Length);  
    25             // Execute the unmanaged application, it will now use the value of the cmd string  
    26             // as the command line string and execute as desired  
    27             AppDomain.CurrentDomain.ExecuteAssembly(@"c:\LaunchpadTools\tf.exe");  
    28         }  
    29     }  
    30


    NOTE: I have also tried to load the application into an assembly object and execute it by invoking from the entry point of the assembly. This will execute the application but it has the same problem as the code above. The execution ignores any passed in values for the argument list and instead uses the values parsed from the original command line that called the wrapper application.

    Questions:

                    Is there a way to accomplish my original goal to load an unamanged application into memory once and then call it with multiple times with unique command line values?

                    If not is there a safe way to actually set the current processes CommandLine value (redirect the internal char * to a new char array) so the command line can be reset and resized?
    Wednesday, November 19, 2008 10:26 PM

Answers

  • The exe I am trying to run, definitely does not throw an exception, but it also is definitely not using the string[] array value when it executes. If I run AppDomain.CurrentDomain.ExecuteAssembly(@"c:\LaunchpadTools\tf.exe", null, argList); by itself with out changing the value stored for the command line string, the process ignores the values for arglist and uses the stored value for the command line to generate arguments (as you would expect from a unmanaged application executing main(int argc, char* argv)).

    When I run the same command targeting a simple test application created in C# the process executes as expected and uses the argList values passed into the call to ExectueAssembly.

    I did not write the executable I am calling, so I can't tell you much about it beyond how it responds when I make these calls.

    • Marked as answer by woeful Thursday, November 20, 2008 6:34 PM
    Wednesday, November 19, 2008 11:59 PM

All replies

  • That's a bit weird, but maybe I don't really understand what you're trying to do.  I'm guessing you're trying to run a .exe but without using a process.  As long as the .exe is a managed app, that is really easy to do.  Just use Assembly.Load() to load the .exe assembly, then find the Main() entry point with  Reflection.  You can start it with MethodInfo.Invoke() and pass it any arguments you desire.
    Hans Passant.
    Wednesday, November 19, 2008 11:01 PM
    Moderator
  • The problem is the exe I need to run is not Managed. So loading it as an assembly and calling invoke has the same issue as the code above. The arguments passed in are ignored in favor of the arguments garnered by the command line string stored for the current process.

    Basically I have an automation process that needs to call a pre-existing .exe application many times with different command line setting. Normally I would do this by creating a process, however the application is being called so many times that the overhead for creating the process and loading the application into memory for each call is a problem.

    Executing the application this way is noticeably faster but it is not safe. So I am trying to see if anyone knows a safe way to get the same result.
    Wednesday, November 19, 2008 11:29 PM
  • First of all assemblies are a 'managed' concept, in the native (aka unmanaged) world the concept does not exist, I'm actually surprised that ExecuteAssembly does not throw an exception when you try to feed it unmanaged code. 

    The prefered way to call unmanaged code in your own process is to expose the native functionality inside a dll instead of an exe file and use pinvoke to call it.

    Wednesday, November 19, 2008 11:31 PM
  • Are you sure its an unmanged application? I just tried both

    AppDomain.CurrentDomain.ExecuteAssembly("C:\\windows\\notepad.exe"); 

    and

    Assembly x = Assembly.LoadFile("C:\\windows\\notepad.exe"); 

    And both as expected spit an exception at you when you try.


    Wednesday, November 19, 2008 11:38 PM
  • The exe I am trying to run, definitely does not throw an exception, but it also is definitely not using the string[] array value when it executes. If I run AppDomain.CurrentDomain.ExecuteAssembly(@"c:\LaunchpadTools\tf.exe", null, argList); by itself with out changing the value stored for the command line string, the process ignores the values for arglist and uses the stored value for the command line to generate arguments (as you would expect from a unmanaged application executing main(int argc, char* argv)).

    When I run the same command targeting a simple test application created in C# the process executes as expected and uses the argList values passed into the call to ExectueAssembly.

    I did not write the executable I am calling, so I can't tell you much about it beyond how it responds when I make these calls.

    • Marked as answer by woeful Thursday, November 20, 2008 6:34 PM
    Wednesday, November 19, 2008 11:59 PM
  • Just out of curiosity are you able to open the executable with something like reflector?

    • Proposed as answer by VIncent Hubert Thursday, November 20, 2008 6:39 PM
    Thursday, November 20, 2008 12:39 AM
  • Thanks for pointing me to Reflector, this has already answered a lot of questions for me.
     
    Yes the application can be opened with Reflector and it is implementing Main(string[]) which indicates it is a managed application after all. However I did some investigation and the app is manually parsing the command line string rather than using the values passed in for the args value. So I still have the same problems. I can only execute this application if I can safely change the Command Line string value.

    I am going to open a new thread with that specific question.
    Thursday, November 20, 2008 6:35 PM
  • I don't know if it is feasable, but can you call your Exe "main" function several times using reflection, passing a different string every time?

    Vincent
    Thursday, November 20, 2008 6:41 PM