WOW64程序文件重定向无法正常工作

时间:2017-04-20 15:36:50

标签: c# dll wow64

我有一个32位和64位版本的托管应用程序,它们必须能够在同一台机器上运行,并且依赖于具有相同名称的两个位的非托管DLL(即ICU 4.2)。

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

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

已编辑添加:

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

C:\Program Files (x86)\ICUBins
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\0\0" 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
    }
}

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

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;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

我想我找到了问题,问题就在于我。

更确切地说,假设首先有一个Program Files重定向。但the documentation中的任何内容似乎都没有说明情况:WOW64似乎没有重定向程序文件,只是改变%ProgramFiles%指向的内容。并加上侮辱伤害,I can't use %ProgramFiles% in the system PATH来纠正这一点。

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

  

不要这样做,PATH本身已经过分了,在客户端应用程序所在的目录中部署所需的DLL

谢谢,但唉,在我将这个问题简化为一个极小的例子的冲动中,我忽略了提到我有一个真实的&#34;程序和ASP.Net网站(并在应用程序的目录中部署DLL在后一种情况下不起作用)。此外,为了完整起见,&#34;现实世界&#34;代码使用C ++ / CLI包装程序集,它是动态加载的程序集,而不是ICU DLL(&#34;静态&#34;动态链接到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;
}