近日,因为工作需要,在公司的一个现有的解决方案里开发一个工具并打包为.msi的安装包(c#),主输出项目为dll。
1、基本痛点
1)现有解决方案为32位工具集
2)自己的工具需要使用一个64位独有的命令工具
2、尝试解决
1)将c++的dll输出平台不变,依旧位win32,其他c#的输出平台全换为X64,失败,会报错,如下
上网搜了下解决方案,说是64位应用程序调用32位dll或32位应用程序调用64位dll出现的问题,推荐解决方案是将文件的输出平台换位X86就可以,但是我要是换成X86那我怎么访问我的64位独有命令?转念一想,我可是有dll源码的啊,我把这些dll也用x64编译一遍不就得了,OK,我将所有的工程输出平台全部换成X64,测试,又失败了,这一步,懵逼。所有的dll我都用x64重新生成了一遍啊。
又一次上网查看又没有类似的情况,有提到dll文件报这种错,不仅仅是dll文件是64位的,可能是因为调用他的应用程序是32位的,而我的.msi安装包是微软自带的安装格式,所以可能是对应的installUtil.exe版本不对,查阅MSDN,如下:
于是我更换了项目输出的程序集为MSIL(也就是目标平台位anyCPU),这次可以用了,但是,附上自己的代码,没反应。
另一方面,我的代码是我自己之前单创的工程跑通了的,所以代码不会有问题,但是以防万一,我依旧是加上了Log代码,存到了文件里,根据log的表现,应该是我使用的命令没有找到。
以上,代码没问题,目标平台没问题,但是在之前目标平台CPU架构设置时,使用X64生成的包无法正常使用,有可能仍然是有依赖的程序集为32位,但是我的解决方案中除了主输出以外所有的dll我都使用X64重新编译了,那么问题就可能出在了系统的库程序集,但是又不知道是哪一项,没辙,只能是用最笨的办法——穷举,根据同事推荐,我首先使用depend工具查看了下我的主输出项目的依赖属性,将所有依赖的dll列举出来,然后使用一个叫CFF-EXplorer的工具,将主输出项目的所有dll一一放进去观察其对应的位数,不出所料,真凶已然显出身影:
这三个程序集是32位下生成的,所以我的主项目只能是32位架构。
综上分析,想要生成64位的项目怕是没戏了,换个思路吧,试试在32位机器中访问64的工具。
查阅MSDN的时候,发现了一个好东西,叫做重定向管理器,
以上摘自MSDN,对应连接:https://docs.microsoft.com/zh-cn/windows/desktop/WinProg64/file-system-redirector
可以通过微软提供的API关闭,Wow64DisableWow64FsRedirection搭配Wow64RevertWow64FsRedirection这两个API分别用来关闭重定向和打开重定向。
so,用呗!
IntPtr oldWOW64State = new IntPtr();
if(Wow64DisableWow64FsRedirection(ref oldWOW64State);)
{
自己的代码;
Wow64RevertWow64FsRedirection(oldWOW64State);
}
现实是,又一次被捶翻了, 依旧访问不到,不信邪的我,改了一下代码
IntPtr oldWOW64State = new IntPtr();
if(Wow64DisableWow64FsRedirection(ref oldWOW64State);)
{
WriteLog("Redirection succeed!")//自己实现的写log函数
if (File.Exists(@"C:\Windows\System32\命令名))
{
WriteLog("find cmdlet succeed!")
自己的代码;
}
Wow64RevertWow64FsRedirection(oldWOW64State);
}
现实又一次将我按在地上,我的log文件中这两个日志都打印出来了,也就是说,文件重定向函数返回值为真,根据MSDN描述,重定向应该是关掉了,但是我的命令就是没执行,我又开始自我怀疑了,代码有问题?于是我打开了powershell,敲了自己的命令,有效果,又把自己的测试工程(64位的)打开,代码拷进去,也成功执行了。看来不是代码的问题,那就只能回过头来,这个重定向API未生效,那么为什么呢?是有什么特殊情况我没有注意到吗?我再次打开了MSDN,看到这个例子里的一行注释。
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0501
#ifdef NTDDI_VERSION
#undef NTDDI_VERSION
#endif
#define NTDDI_VERSION 0x05010000
#include <Windows.h>
void main()
{
HANDLE hFile = INVALID_HANDLE_VALUE;
PVOID OldValue = NULL;
// Disable redirection immediately prior to the native API
// function call.
if( Wow64DisableWow64FsRedirection(&OldValue) )
{
// Any function calls in this block of code should be as concise
// and as simple as possible to avoid unintended results.
hFile = CreateFile(TEXT("C:\\Windows\\System32\\Notepad.exe"),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
// Immediately re-enable redirection. Note that any resources
// associated with OldValue are cleaned up by this call.
if ( FALSE == Wow64RevertWow64FsRedirection(OldValue) )
{
// Failure to re-enable redirection should be considered
// a criticial failure and execution aborted.
return;
}
}
// The handle, if valid, now can be used as usual, and without
// leaving redirection disabled.
if( INVALID_HANDLE_VALUE != hFile )
{
// Use the file handle
}
}
如下图所示,Note that any resoutces associated with OldValue are cleaned up by this call.和OldValue有关的所有资源都会被清理掉,我这一寻思,不对啊,我的命令在执行过程中要持续挺久,要给我直接干掉了,那肯定没效果啊,所以我试着改了改代码:
我将自己之前的ps.Invoke();改成了下面这两行代码,将我的命令执行改成了异步进行,开启重定向后新建一个线程,这个线程里执行我的命令,这样关闭重定向时清理线程资源时也不会影响到我。
IAsyncResult result = ps.BeginInvoke();
ps.EndInvoke(result);
再运行一次,成了!就很舒服。
但是上面的其实是我在写这篇文章从MSDN截图的时候才注意到然后测试成功的解决方案,我自己用的并不是这个,接下来我要将我用的方案。还是MSDN,有这样一句,
所以,我将原先的C:\Windows\System32\命令名改成了C:\Windows\Sysnative\命令名,执行代码,成功!
综上,32位应用程序想要访问System32文件,有两种方案
1、Wow64DisableWow64FsRedirection搭配Wow64RevertWow64FsRedirection这两个API使用,但是需要注意:
1)一定要匹配使用,并且要及时。否则后果,就像野指针一样可怕,甚至犹有过之!
2)如果是一些持续性的进程则需要异步使用,新开启一个线程,使得该进程地资源不会在重定向结束地时候被清理掉。例如如果是用powershell或者cmd命令,就需要BeginInvoke()搭配EndInvoke()使用,如果是process进程,则需要start()搭配WaitForExit()使用。
2、使用Sysnative别名来访问,但是需要注意,64位的应用程序无法使用这个别名