我有一个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;
}
}
}
答案 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;
}