c# visual studio build exe with target anycpu but that determines its platform(x86/x64) on the calling process platform(x86/x64)
Asked Answered
K

2

8

We have a software in 32 and 64 bits that calls our exe and passes events to it(like a plugin).

The thing is that our exe has to execute in the same bitness(x86/x64) as the calling software (if the software is run in 32 bits version our exe must run in 32 bits, if the software is run in 64bits version our exe must run in 64 bits). The windows version is 64bits but the client can run the software in 32bits version or 64bits version.

In visual studio(2015) the Target AnyCPU option only depends of the windows version(+ Prefer 32-bit checkbox) but we need to be dependent on the calling software Process.

Is there any option or solution we can implement instead of compiling to each platform (x86 and x64)?

Kimberly answered 26/1, 2017 at 11:7 Comment(4)
"Calls our exe" makes little sense. A plugin is invariably loaded as a DLL, that the file has the .exe filename extension does not matter in .NET. Any DLL needs to conform to the bitness selected by the startup executable. The host process in a plugin scenario. Very easily done in C# by selecting "AnyCPU" as the target and not checking the "Prefer 32-bit" checkbox. Have you actually tried this?Unbalanced
Sorry if i explained it wrong. In this case the software explicitly runs ourapp.exe (is its own process not as a plugin) so there is then two independent processes: software.exe and ourapp.exe Yes, we tried AnyCPU before asking the question and in those cases our process based its bitness on the windows it was running (logically as documented) but we want some option that base its bitness on the process calling our exe (bootstrap style?)Kimberly
Do you have any control over software.exe? Can you create a 32bitWrapper and a 64BitWrapper of ourapp.exe and let software.exe start whichever it wants?Philbrook
From my experience, "prefer 32 bit" actually means "force 32 bit". I have never had a prefer 32 bit app run in 64 bit mode. Perhaps it does but not in my experience.Reactant
P
4

There is no Windows operating system requirement that two separate processes need to be the same bitness to communicate with each other-- so I'm assuming you have some internal requirement that both processes be the same bitness. If you absolutely must achieve dynamically running the same EXE in either 32-bit or 64-bit and make that decision at runtime, you can modify your AnyCPU compiled app at run time using corflags.exe utility that ships with Visual Studio.

You can launch corflags.exe like this (using System.Diagnostic.Process):

corflags.exe "ourapp.exe" /32BIT+

To force it to run 32bit, and

corflags.exe "ourapp.exe" /32BIT-

To go back to AnyCPU (which will be 64-bit on a 64-bit OS)

The solution would be that you would deploy a copy of corflags.exe in your runtime environment. Then, your host application can detect it's current bitness by checking sizeof(IntPtr) to see if it is 8 or 4; and spawn a copy of corflags.exe accordingly (using System.Diagnostics.Process), prior to launching the ourapp.exe.

This is very HACKY. Be careful. Obviously you will have lots of trouble if you need to run two copies of your ourapp.exe at the same time on the same machine. It would be ideal to create a unique copy of ourapp.exe to a local user folder before modifying it and launch it from there to avoid race conditions from multiple instances and UAC prompts depending on your target environment.

Pocketknife answered 3/2, 2017 at 18:2 Comment(0)
P
0

Is it an absolute requirement that the process must appear as itself in the Processes tab of Task Manager? Or can it run contained inside another host process?

If the latter, you could create a stub project containing the following Main method and then compile two versions of the executable: one as 32-bit (LaunchX86.exe) and the other 64-bit (LaunchX64.exe).

static int Main(string[] args)
{
    try
    {
        foreach (var t in Assembly.LoadFile(args[0]).GetTypes())
        {
            var methodInfo = t.GetMethod(
                "Main",
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

            if (methodInfo != null)
            {
                int? retVal = 0;

                // See https://mcmap.net/q/371330/-how-to-run-something-in-the-sta-thread#2378077
                var thread = new Thread(() =>
                {
                    try
                    {
                        // Main() methods may have zero or one parameters
                        var methodParams = methodInfo.GetParameters().Length == 0
                            ? null
                            : new object[] {args.Skip(1).ToArray()};

                        retVal = methodInfo.Invoke(t, methodParams) as int?;
                    }
                    catch (Exception e)
                    {
                        retVal = 99; // Error 99: The executable itself threw an exception
                    }
                });

                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                thread.Join();

                return retVal ?? 0;
            }
        }

        return 98; // Error 98: unable to launch exe because no method "Main" found
    }
    catch (Exception e)
    {
        // Can identify exception type here and return appropriate result, e.g.:
        // ArgumentNullException - no EXE name provided in first param
        // BadImageFormatException - specified file was not a suitable EXE

        return 97; // Error 97: unable to load executable for analysis
    }
}

Note: to keep things simple, above has very coarse catching of exceptions and maps those to a few fixed return values.

Also, note that in the Invoke() method, we are not passing any parameters to the Main() method for WPF applications, because the WPF application automatically receives the same parameters as those of the launcher. This is not ideal but various workarounds are possible, such as setting an environment variable using Environment.SetEnvironmentVariable() before launching, checking for that variable within the target application using Environment.GetEnvironmentVariable() and (if present) using its contents in place of the normal method.

Then from your primary application you launch the relevant host executable depending on your current process's bitness:

new Process
{
    StartInfo = new ProcessStartInfo(
        Environment.Is64BitProcess ? "LaunchX64.exe" : "LaunchX86.exe",
        "application_to_be_hosted.exe param1 param2 etc."
    )
}.Start();
Postorbital answered 8/2, 2017 at 23:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.