WOW64 程序文件重定向不起作用

WOW64 Program Files redirection not working

我有一个 32 位和 64 位版本的托管应用程序,它们都必须能够 运行 在同一台机器上,依赖于具有相同名称的两个位数的非托管 DLL(即,重症监护病房 4.2).

所以,我决定将两个版本的 ICU 放在它们匹配的程序文件中,依靠 WOW64 文件系统重定向来完成繁重的工作:C:\Program Files\ICUBins 中的 64 位版本和 32 位版本在 C:\Program Files (x86)\ICUBins 中;前者被添加到 PATH 变量中。

除了...它不起作用。我的程序的两个版本最终都使用了 64 位版本的非托管 DLL,我不明白为什么 32 位进程使用 C:\Program Files\ICUBins 的尝试没有重定向到 C:\Program Files (x86)\ICUBins .

编辑添加:

这里是重现问题的最小代码示例(但由于包装代码很大,它没有加载 DLL,只是检查其位数)。 请注意,它使用过时的函数来检查 WOW64,因为它最初是为 .Net 2.0 创建的:

using System;

namespace SearchICU
{
    /// <summary>
    /// Methods for getting the version and date of the assembly that calls them.
    /// </summary>
    public static class BitnessHelper
    {
        public enum Machine : short
        {
            x86=0x14C,
            Alpha=0x184,
            ARM=0x1C0,
            MIPS16R3000=0x162,
            MIPS16R4000=0x166,
            MIPS16R10000=0x168,
            PowerPCLE=0x1F0,
            PowerPCBE=0x1F2,
            Itanium=0x200,
            MIPS16=0x266,
            Alpha64=0x284,
            MIPSFPU=0x366,
            MIPSFPU16=0x466,
            x64=unchecked((short)0x8664),
        }

        public static Machine RetrieveMachine(string filePath)
        {
            if(string.IsNullOrEmpty(filePath)) { throw new ArgumentNullException("filePath"); }

            const int c_PeHeaderOffsetOffset = 60; //Within whole file/IMAGE_DOS_HEADER structure. Equal to 0x003C
            const int c_MachineOffset = 4; //Within IMAGE_NT_HEADERS

            //Just read "enough" of the file: In modern PE files, the IMAGE_NT_HEADERS structure should never start past 2KiB anyway.
            byte[] b = new byte[2048];
            System.IO.Stream s = null;

            try
            {
                s = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                s.Read(b, 0, 2048);
            }
            finally
            {
                if(s != null)
                {
                    s.Close();
                }
            }

            //First check the MZ header (IMAGE_DOS_HEADER)'s magic.
            short mzMagic = ReadInt16LE(b, 0);
            if(mzMagic != 0x5A4D) //"MZ" in little-endian
                throw new BadImageFormatException("File does not start with MZ header.");

            //Get its "next header offset" value and check the PE header (IMAGE_NT_HEADERS)'s magic.
            int peHeaderOffset = ReadInt32LE(b, c_PeHeaderOffsetOffset);
            int peMagic = ReadInt32LE(b, peHeaderOffset);
            if(peMagic != 0x00004550) //"PE[=10=][=10=]" in little-endian
                throw new BadImageFormatException("Header pointed by MZ header is not PE.");

            //Read the machine from the PE header (IMAGE_NT_HEADERS).
            //We're still in the bitness-agnostic part (the IMAGE_FILE_HEADER structure).
            short machine = ReadInt16LE(b, peHeaderOffset + c_MachineOffset);
            return (Machine)machine;
        }

        /// <summary>Reads a 16-bit integer as little-endian from a byte array.</summary>
        /// <remarks>Because BitConverter depends on the platform's endianness, and here I need an "always little-endian" code.
        /// Made public because some other code has a need for this.</remarks>
        public static short ReadInt16LE(byte[] bytes, int offset)
        {
            if(bytes==null) { throw new ArgumentNullException("bytes"); }

            ushort ret = 0;
            for(int i=1 ; i>=0 ; i--) { ret <<= 8; ret |= bytes[offset+i]; }
            return unchecked((short)ret);
        }
        /// <summary>Reads a 32-bit integer as little-endian from a byte array.</summary>
        /// <remarks>Because BitConverter depends on the platform's endianness, and here I need an "always little-endian" code.
        /// Made public because some other code has a need for this.</remarks>
        public static int ReadInt32LE(byte[] bytes, int offset)
        {
            if(bytes==null) { throw new ArgumentNullException("bytes"); }

            uint ret = 0;
            for(int i=3 ; i>=0 ; i--) { ret <<= 8; ret |= bytes[offset+i]; }
            return unchecked((int)ret);
        }

        #region Win64/WOW64 methods
        /// <summary>
        /// Win32 function <c>IsWow64Process</c>: Determines whether the specified process is running under WOW64.
        /// </summary>
        /// <param name="hProcess">[in] Process handle with enough access rights.</param>
        /// <param name="Wow64Process">[out] set to <c>true</c> if running under WOW64, <c>false</c> for Win32 and Win64.</param>
        /// <returns><c>true</c> if succeeded.</returns>
        [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
        extern static bool IsWow64Process(IntPtr hProcess, ref bool Wow64Process);

        /// <summary>
        /// Wrapper for <c>IsWow64Process</c>, so the calling function may throw <c>SecurityException</c> when calling it
        /// rather than be completely prevented from running by <c>LinkDemand</c>.
        /// </summary>
        static bool CallIsWow64Process(IntPtr hProcess, ref bool Wow64Process)
        {
            //P/Invoke has a LinkDemand for full trust, so this function won't even start
            //if partially trusted.
            return IsWow64Process(hProcess, ref Wow64Process);
        }

        /// <summary>
        /// Wrapper for <c>Process.GetCurrentProcess</c>, so the calling function may throw <c>SecurityException</c> when calling it
        /// rather than be completely prevented from running by <c>LinkDemand</c>.
        /// </summary>
        static IntPtr GetProcessHandle()
        {
            //GetCurrentProcess() has a LinkDemand for full trust, so this function won't even start
            //if partially trusted.
            return System.Diagnostics.Process.GetCurrentProcess().Handle;
        }

        /// <summary>
        /// Wrapper for <c>Marshal.GetLastWin32Error</c>, so the calling function may throw <c>SecurityException</c> when calling it
        /// rather than be completely prevented from running by <c>LinkDemand</c>.
        /// </summary>
        static int CallGetLastWin32Error()
        {
            //GetLastWin32Error() has a LinkDemand for UnmanagedCode, so this function won't even start
            //if partially trusted.
            return System.Runtime.InteropServices.Marshal.GetLastWin32Error();
        }

        /// <summary>
        /// Test whether the current process is running under Win32, Win64 or WOW64.
        /// </summary>
        /// <param name="message">[out] Human-readable message describing the situation.</param>
        /// <returns><c>true</c> if succeeded, <c>false</c> if couldn't determine (security or IsWow64Process failure).</returns>
        /// <exception cref="Exception">For any other error with the P/Invoke call.</exception>
        public static bool TestWow64(out string message)
        {
            //Note on exceptions: Apparently, on a full .Net Framework P/Invoke can throw EntryPointNotFoundException,
            //ExecutionEngineException (if incorrectly declared) or DllNotFoundException.
            //(the former two replaced with MissingMethodException and NotSupportedException on the Compact Framework).
            //Since we're hitting Kernel32.dll, using the correct declaration, and not planning for an embedded version,
            //only EntryPointNotFoundException will be handled here.
            try
            {
                bool isWow64 = false;
                //Call wrapper functions to avoid security exceptions being thrown before the try block.
                if(CallIsWow64Process(GetProcessHandle(), ref isWow64))
                {
                    if(isWow64)
                        message = "Running as a 32-bit process on a Win64 machine.";
                    else if(IntPtr.Size==4)
                        message = "Running on Win32.";
                    else if(IntPtr.Size==8)
                        message = "Running on Win64.";
                    else
                        message = string.Format("Something weird: Not WOW64, but pointer size is {0}.", IntPtr.Size);
                    return true;
                }
                else
                {
                    message = string.Format("IsWow64Process was correctly called, but failed with error {0}", CallGetLastWin32Error());
                    return false;
                }
            }
            catch(EntryPointNotFoundException)
            {
                message = "Running on Win32, WOW64 not supported.";
                return true;
            }
            catch(System.Security.SecurityException)
            {
                message = "Running in a sandbox, process information inaccessible.";
                return false;
            }
            //catch(Exception e)
            //{
            //    log.Warn("IsWow64Process call failed:", e); //test
            //}
        }

        /// <summary>
        /// Wrapper method for determining whether the current process is 64-bit.
        /// Useful for determining which version of a library to load.
        /// </summary>
        public static bool IsWin64
        {
            get { return IntPtr.Size==8; } //In V10, use Environment.Is64BitProcess
        }
        #endregion
    }
}

using System;
using System.IO;

namespace SearchICU
{
    class Program
    {
        static void Main(string[] args)
        {
            string bitness;
            if(BitnessHelper.TestWow64(out bitness)) { Console.WriteLine(bitness); }
            string icuDir = FindDirInPath(new string[] { "icudt42.dll", "icuuc42.dll", "icuin42.dll" });
            if(icuDir==null)
            {
                Console.WriteLine("ICU DLLs not found in PATH.");
                return;
            }

            Console.WriteLine("ICU DLLs found in PATH:{1}\t{0}", icuDir, Environment.NewLine);

            string dllPath = Path.Combine(icuDir, "icuin42.dll");
            BitnessHelper.Machine machine = BitnessHelper.RetrieveMachine(dllPath);
            switch(machine)
            {
            case BitnessHelper.Machine.x86:
                Console.WriteLine("DLL in path is 32-bit DLL.");
                break;
            case BitnessHelper.Machine.x64:
                Console.WriteLine("DLL in path is 64-bit DLL.");
                break;
            default:
                Console.WriteLine("DLL in path is unknown (machine={0}).", machine);
                break;
            }
        }

        public static string FindDirInPath(string[] filesToFind)
        {
            if(filesToFind==null || filesToFind.Length==0)
                throw new ArgumentException("filesToFind must be a non-empty array of file names.", "filesToFind");

            string pathEnvVariable = Environment.GetEnvironmentVariable("PATH");
            if(!string.IsNullOrEmpty(pathEnvVariable))
            {
                foreach(string pathDirectory in pathEnvVariable.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
                {
                    bool allFound = true;
                    foreach(string fileName in filesToFind)
                    {
                        string filePath = Path.Combine(pathDirectory, fileName);
                        if(!File.Exists(filePath))
                        {
                            allFound = false;
                            break;
                        }
                    }
                    if(allFound)
                        return pathDirectory;
                }
            }
            return null;
        }
    }
}

当我运行这个程序的32位版本时,输出是这样的:

Running as a 32-bit process on a Win64 machine.
ICU DLLs found in PATH:
        C:\Program Files\ICUBins
DLL in path is 64-bit DLL.

我想我找到问题了,问题出在我身上

更准确地说,假设首先存在程序文件重定向。但是 the documentation seems to say this is actually the case: It appears WOW64 doesn't redirect Program Files, but merely changes what %ProgramFiles% points to. And to add insult to injury, I can't use %ProgramFiles% in the system PATH 中没有任何内容可以更正此问题。

因此,要么我必须为每个用户设置环境变量(对于网站,则为每个应用程序池),或者我必须修改程序本身以在尝试访问之前正确修改 PATH DLL.

Don't do this, PATH is already excessively fugly by itself, deploy the DLLs you need in the same directory as the client app

谢谢,但是,唉,在我急于将问题减少到最小的例子时,我忽略了提到我有一个 "real" 程序和一个 ASP.Net 网站(并在应用程序的目录在后一种情况下不起作用)。此外,为了完整起见,"real world" 代码使用 C++/CLI 包装器程序集,并且是动态加载的程序集,而不是 ICU DLL("statically" 动态链接到 C++/CLI 包装器) .

编辑: 最后,我将此添加到我的代码中:

/// <summary>
/// Enumerates the paths in the <c>PATH</c> environment variable, looking for <paramref name="filesToFind"/>,
/// then checks their bitness and if it's a conflicting bitness in one of the Program Files directories,
/// looks into the other Program File directory and updates <c>PATH</c> in consequence when successful.
/// </summary>
/// <param name="filesToFind">[in] File names.</param>
/// <param name="oldPath">[out] Old value of <c>PATH</c>. This parameter is always set.</param>
/// <param name="foundPath">[out] Directory in which the files were found, or <c>null</c> if they weren't.</param>
/// <returns><c>true</c> if the <c>PATH</c> environment variable was modified.</returns>
public static bool FindDirInPathAndUpdatePathWithBitness(string[] filesToFind, out string oldPath, out string foundPath)
{
    if(filesToFind==null || filesToFind.Length==0)
        throw new ArgumentException("filesToFind must be a non-empty array of file names.", "filesToFind");

    string pathEnvVariable = Environment.GetEnvironmentVariable("PATH");
    oldPath = pathEnvVariable;
    foundPath = null;

    if(!string.IsNullOrEmpty(pathEnvVariable))
    {
        string[] pathArray = pathEnvVariable.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
        for(int iPath=0 ; iPath<pathArray.Length ; iPath++)
        {
            string pathDirectory = pathArray[iPath];
            bool allFound = true;
            foreach(string fileName in filesToFind)
            {
                string filePath = Path.Combine(pathDirectory, fileName);
                if(!File.Exists(filePath))
                {
                    allFound = false;
                    break;
                }
            }

            //If all files were found in this directory
            if(allFound)
            {
                //Now the fun begins
                try
                {
                    string firstFilePath = Path.Combine(pathDirectory, filesToFind[0]);
                    bool runningWin64 = BitnessHelper.IsWin64;
                    var fileBitness = BitnessHelper.RetrieveMachine(firstFilePath);
                    if(runningWin64 != (fileBitness==BitnessHelper.Machine.x64))
                    {
                        //Bitness conflict detected. Is this directory in %ProgramFiles%?
                        bool bHandled = HandleBitnessConflict(ref pathDirectory, filesToFind[0], runningWin64, fileBitness);
                        if(bHandled)
                        {
                            pathArray[iPath] = pathDirectory;
                            string newPath = string.Join(";", pathArray);
                            Environment.SetEnvironmentVariable("PATH", newPath);
                            return true;
                        }
                        //Otherwise, several possible scenarios:
                        //Remove the path from PATH and keep searching (which requires some bookkeeping),
                        //or just return foundPath as if the check hadn't happened, letting subsequent code throw a BadImageFormatException.
                        //We'll just do the latter, at least for now.
                    }
                }
                catch { }

                foundPath = pathArray[iPath];
                return false;
            }
        }
    }
    return false;
}

private static bool HandleBitnessConflict(ref string pathDirectory, string firstFileName, bool runningWin64, BitnessHelper.Machine fileBitness)
{
    //Bitness conflict detected. Is this directory in %ProgramFiles%?

    //Bitness-dependent Program Files
    string programFiles = Environment.GetEnvironmentVariable("ProgramFiles");
    //Always points to 32-bit version, if a 64-bit Windows.
    string programFilesX86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
    //Always points to 64-bit version, if a 64-bit Windows 7 or greater.
    string programW6432 = Environment.GetEnvironmentVariable("ProgramW6432");
    char[] directoryChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };

    if(string.IsNullOrEmpty(programFilesX86))
        return false; //Not Win64, so there won't be two Program Files directories anyway.
    if(string.IsNullOrEmpty(programW6432))
    {
        //Pre-7 Windows version: Try an heuristic.
        int ix = programFilesX86.IndexOf(" (x86)", StringComparison.OrdinalIgnoreCase);
        if(ix < 0) { return false; } //Heuristic failed.
        string exactSubstring = programFilesX86.Substring(ix, " (x86)".Length);
        programW6432 = programFilesX86.Replace(exactSubstring, "");
        if(!Directory.Exists(programW6432)) { return false; } //Heuristic failed.
    }

    if(pathDirectory.StartsWith(programFilesX86) && fileBitness==BitnessHelper.Machine.x86)
    {
        //The file is a 32-bit file in the 32-bit directory in the path;
        //Since there's a conflict, this must mean the current process is 64-bit
        if(!runningWin64) { return false; } //No conflict, no handling.

        string directory64 = Path.Combine(programW6432, pathDirectory.Substring(programFilesX86.Length).TrimStart(directoryChars));
        string filePath64 = Path.Combine(directory64, firstFileName);
        if(Directory.Exists(directory64) && File.Exists(filePath64))
        {
            if(BitnessHelper.RetrieveMachine(filePath64) == BitnessHelper.Machine.x64)
            {
                pathDirectory = directory64;
                return true;
            }
        }
    }
    else if(pathDirectory.StartsWith(programW6432) && fileBitness==BitnessHelper.Machine.x64)
    {
        //The file is a 64-bit file in the 64-bit directory in the path;
        //Since there's a conflict, this must mean the current process is 32-bit
        if(runningWin64) { return false; } //No conflict, no handling.

        string directory32 = Path.Combine(programFilesX86, pathDirectory.Substring(programW6432.Length).TrimStart(directoryChars));
        string filePath32 = Path.Combine(directory32, firstFileName);
        if(Directory.Exists(directory32) && File.Exists(filePath32))
        {
            if(BitnessHelper.RetrieveMachine(filePath32) == BitnessHelper.Machine.x86)
            {
                pathDirectory = directory32;
                return true;
            }
        }
    }

    return false;
}