wine运行软件的排错和常见问题

背景

wine的安装并不是这篇文章的主题,所以这里我们借用docker-wine来构建运行环境,避免啰哩啰嗦的事情

这个镜像包括了多种运行方式,我根据需要以daemon进行持久化运行

# tty是为了保持运行不退出
# 其他参数是为了通过x11来进行显示,以及软件目录
docker run -d \
           --name wine \
           --tty=true  \
           --hostname="$(hostname)"   \
           --env="DISPLAY"   \
           --volume="${XAUTHORITY:-${HOME}/.Xauthority}:/root/.Xauthority:ro"   \
           --volume="/tmp/.X11-unix:/tmp/.X11-unix:ro"   \
           -v /media/minecraft/Data/software/HALo/:/root/HALo \
           scottyhardy/docker-wine \
           bash
# 进入到容器内部进行操作
docker exec -it wine bash

程序排错过程

首先配置一下wine

初始化运行wine需要进行一些初步的配置

# 这个参数代表了wine内部系统的根目录,更换之后相当于重新装了个新的wine的环境,默认是这个
export WINEPREFIX=$HOME/.wine
# 64位还是32位,默认64
export WINEARCH=win64
# 配置wine的版本,建议设置
winecfg -v win10

排错开始,一些基本信息

# 我们直接运行程序,并根据程序的报错来确定问题所在
wine xxxx.exe

在我的运行过程中发现的是如下的log,其中会出现一大堆fixme相关的log,这个实际上是wine对于windows的日志系统的操作,这里大部分并没有进行实现(毕竟貌似没啥必要),所以会提示一些日志的方法没有实现,多数情况下可以忽略掉

wine: created the configuration directory '/root/.wine'
002c:fixme:actctx:parse_depend_manifests Could not find dependent assembly L"Microsoft.Windows.Common-Controls" (6.0.0.0)

... # 此处省路一大堆fixme

0078:fixme:nsi:ipv6_forward_enumerate_all not implemented
0078:fixme:nsi:ipv6_forward_enumerate_all not implemented
0078:fixme:nsi:ipv6_forward_enumerate_all not implemented
0078:fixme:nsi:ipv6_forward_enumerate_all not implemented
[ERROR] FATAL UNHANDLED EXCEPTION: System.Runtime.InteropServices.COMException (0x80004005)
  at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR (System.Int32 errorCode) [0x0000a] in <e70d6e9587d64cb3abb4b3f99bbf5a0d>:0 
  at (wrapper cominterop) IWshRuntimeLibrary.IWshShortcut.set_IconLocation(string)
  at (wrapper cominterop-invoke) IWshRuntimeLibrary.IWshShortcut.set_IconLocation(string)
  at Mediinfo.WinForm.HIS.Main.Program.CreateShortcutOnDesktop () [0x00441] in <792f8817992a498aac36d9ea5e2f5fbc>

WINEDEBUG变量

这里开始引入我们第一个非常重要的环境变量WINEDEBUG,该变量可以对wine的输出内容进行调整,例如这里我们不想显示fixme相关的内容,就可以设置一下,具体更详细的内容可以参考man wine中这一部分

export WINEDEBUG=fixme-all
wine xxxx.exe
# 或者这个方法临时进行设置环境变量
WINEDEBUG=fixme-all wine xxxx.exe

常用的几个参数是:

  • +relay,会把每一个函数的调用打印出来,会产生巨大的log同时极大的减慢程序的运行速度
  • +module,通常建议设置这个,用来寻找dll的问题很方便
# 根据以上建议,我们在调试的时候建议
export WINEDEBUG=fixme-all,+module
# 平时运行建议
export WINEDEBUG=fixme-all

缺失的函数dll

好了,现在回到我们的出问题的部分,可以看到log最后面是打印出来了

[ERROR] FATAL UNHANDLED EXCEPTION: System.Runtime.InteropServices.COMException (0x80004005)
  at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR (System.Int32 errorCode) [0x0000a] in <e70d6e9587d64cb3abb4b3f99bbf5a0d>:0 
  at (wrapper cominterop) IWshRuntimeLibrary.IWshShortcut.set_IconLocation(string)
  at (wrapper cominterop-invoke) IWshRuntimeLibrary.IWshShortcut.set_IconLocation(string)
  at Mediinfo.WinForm.HIS.Main.Program.CreateShortcutOnDesktop () [0x00441] in <792f8817992a498aac36d9ea5e2f5fbc>

除开我们的程序本身自己的函数来讲,可以看到涉及到系统的函数是IWshRuntimeLibrary,网上进行寻找发现是属于wshom.ocx这个dll(?)的,当然另一种更靠谱的方法是github的镜像wine-mirror处进行寻找,函数具体实现可能和原dll中函数名称不一致,查找的时候要注意。

举例我这个函数是这个路径下找到的,可以看到具体的实现,这里提示是实现了程序所需要的函数,但是实际是运行不了的,不在这里继续探究了

接下来我们就要想办法解决这个缺失的dll了

用win自身的dll来替换

wine实现了大量的dll,但仍然有很多函数没有被实现,这个时候就可以考虑用非free的方式来让程序跑起来

通过winetricks这个工具可以很好的来进行安装各种非free的依赖

既然我们已经知道这个函数是属于wshom.ocx的,同时wsh是Windows Script Host,那我们就进行搜索和安装

winetricks dlls list | grep "wsh \| Script \| Host"
#msscript                 MS Windows Script Control (Microsoft, 2004) [downloadable]
#wsh57                    MS Windows Script Host 5.7 (Microsoft, 2007) [downloadable]
winetricks wsh57
# 加上-q安安静静的安装,别烦我
winetricks -q wsh57

这里我们要告诉wine,用winetricks的dll来替换wine自己的dll,方式有两种:

  1. 通过winecfg的库tab,输入wshom.ocx来进行添加设置native(windows)优先
  2. 通过命令的方式设置,本文章的方法
export WINEDLLOVERRIDES="wshom.ocx=n,b;这里继续写其他要替换的dll"
# 有些dll需要注册一下才行
regsvr32 $WINEPREFIX/drive_c/windows/system32/wshom.ocx

更换32位

再次运行发现如果+module的话log太多,保存到文件里去寻找

export WINEDEBUG=fixme-all,+module
wine xxxx.exe &> dbg.log

接下来的报错很复杂,最终我将其归结于64位的问题(毕竟System.Int32),之后整体切换到32位

[ERROR] FATAL UNHANDLED EXCEPTION: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException
  at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR (System.Int32 errorCode) [0x0000a] in <e70d6e9587d64cb3abb4b3f99bbf5a0d>:0 

  ...

  at Mediinfo.WinForm.HIS.Main.Program.CreateShortcutOnDesktop () [0x002ea] in <792f8817992a498aac36d9ea5e2f5fbc>:0

切换32位很简单,只需要重新设置两个环境变量即可,之后就进入一个新的环境了,之后前面该做的事情再重复一遍即可

export WINEPREFIX=/root/.wine32
export WINEARCH=win32
winetricks -q wsh57
export WINEDLLOVERRIDES="wshom.ocx=n;"
regsvr32 $WINEPREFIX/drive_c/windows/system32/wshom.ocx

中文问题

继续运行发现log中有?在,确认发现是中文问题,这个docker-wine并没有考虑中文问题,所以进行一下设置

[ERROR] FATAL UNHANDLED EXCEPTION: System.Runtime.InteropServices.COMException (0x8007007B): Unable to save shortcut "C:\users\root\Desktop\??HALO.lnk".
  at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR (System.Int32 errorCode) [0x0000a] in <e70d6e9587d64cb3abb4b3f99bbf5a0d>:0 
  at (wrapper cominterop) IWshRuntimeLibrary.IWshShortcut.Save()
  at (wrapper cominterop-invoke) IWshRuntimeLibrary.IWshShortcut.Save()
  at Mediinfo.WinForm.HIS.Main.Program.CreateShortcutOnDesktop () [0x00454] in <792f8817992a498aac36d9ea5e2f5fbc>:
echo "zh_CN GB2312
zh_CN.GB18030 GB18030
zh_CN.GBK GBK
zh_CN.UTF-8 UTF-8" >> /etc/locale.gen

locale-gen
# 设置语言和编码,必须设置
export LC_ALL=zh_CN.utf8

.NET(dotnet)

这里的问题我触发出来一次窗口提示,找不到怎么再次弄出来了,但总之是有线索可寻的

    [ERROR] FATAL UNHANDLED EXCEPTION: System.TypeInitializationException: The type initializer for 'DevExpress.Utils.Text.FontGuard' threw an exception. ---> System.NullReferenceException: Object reference not set to an instance of an object
  at System.Reflection.Emit.ILGenerator.Emit (System.Reflection.Emit.OpCode opcode, System.Reflection.FieldInfo field) [0x0001c] in <e70d6e9587d64cb3abb4b3f99bbf5a0d>:0 
  at DevExpress.Utils.Text.FontGuard.EmitNativeFontAccessor () [0x00066] in <6dabd2d242dd455a9176a4be43804016>:0 
  at DevExpress.Utils.Text.FontGuard..cctor () [0x00000] in <6dabd2d242dd455a9176a4be43804016>:0 

...

  at (wrapper remoting-invoke-with-check) Mediinfo.WinForm.HIS.Main.DengLu..ctor()
  at Mediinfo.WinForm.HIS.Main.Program.FormLoginFunc (System.String[] args, System.Boolean switchSystem, System.Boolean againSystem, System.Boolean PICException) [0x00001] in <792f8817992a498aac36d9ea5e2f5fbc>

安装就很简单啦

winetricks -q dotnet40

字体问题

程序启动之后是方块乱码,字体问题有两种方式:

  1. winetricks可以进行解决,具体可以参考winetricks fonts list
  2. 直接拷贝windows下C:/Windows/Fonts/$WINEPREFIX/drive_c/windows/,我用的是这种比较暴力的方式

.NET4.0的bug

这里的报错是.NET4.0自身的一个错误,需要安装kb2468871补丁

System.TypeInitializationException: The type initializer for 'Mediinfo.ServiceProxy.Core.TokenLocator' threw an exception. ---> System.IO.FileLoadException: Could not load file or assembly 'System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)
   at Autofac.Builder.RegistrationData..ctor(Service defaultService)
   at Autofac.Builder.RegistrationBuilder`3..ctor(Service defaultService, TActivatorData activatorData, TRegistrationStyle style)
   at Autofac.Builder.RegistrationBuilder.ForType(Type implementationType)
   at Autofac.RegistrationExtensions.RegisterType(ContainerBuilder builder, Type implementationType)
   at Mediinfo.ServiceProxy.Core.TokenLocator..ctor() in C:\images\workspace\git4-pipeline-halo-jcjg\04.ServiceProxy\Mediinfo.ServiceProxy.Core\TokenLocator.cs:line 64
   at Mediinfo.ServiceProxy.Core.TokenLocator..cctor() in C:\images\workspace\git4-pipeline-halo-jcjg\04.ServiceProxy\Mediinfo.ServiceProxy.Core\TokenLocator.cs:line 23
   --- End of inner exception stack trace ---
   at Mediinfo.ServiceProxy.Core.TokenLocator.get_Instance()
   at Mediinfo.ServiceProxy.Core.ServiceClient.Invoke[T](String controller, String method, ServiceParm[] parms) in C:\images\workspace\git4-pipeline-halo-jcjg\04.ServiceProxy\Mediinfo.ServiceProxy.Core\ServiceClient.cs:line 320

很简单

winetricks -q dotnet40_kb2468871

wine自身的问题

运行后的程序有个问题,画面会进入一个卡死的状态,但实际程序是正常运行的(通过任务切换界面的任务缩略图),此时是能够进行点击交互的。

这个问题是wine的窗口管理问题导致的,可以通过开启虚拟桌面来避开,一般有两种方法:

  1. winecfg界面在显示tab中开启虚拟桌面
  2. wine explorer /desktop=name,1800x900 xxxx.exe命令的方法

该程序的总结

echo "zh_CN GB2312
zh_CN.GB18030 GB18030
zh_CN.GBK GBK
zh_CN.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
export LC_ALL=zh_CN.utf8
export WINEPREFIX=/root/.wine32
export WINEARCH=win32
export WINEDEBUG=fixme-all
winecfg -v win10
winetricks -q dotnet40 dotnet40_kb2468871
winetricks -q wsh57
export WINEDLLOVERRIDES="wshom.ocx=n;"
regsvr32 $WINEPREFIX/drive_c/windows/system32/wshom.ocx
export WINEESYNC=1
export WINE_EXPLORER_FULLSCREEN=1
# export LIBGL_ALWAYS_SOFTWARE=1
export LIBGL_ALWAYS_INDIRECT=1

cd ~/HALo
cp -r Fonts $WINEPREFIX/drive_c/windows/
# LC_ALL=zh_CN.GBK wine Mediinfo.WinForm.HIS.Starter.exe
# winecfg # set virtual desktop
wine explorer /desktop=name,1800x900 Mediinfo.WinForm.HIS.Update.exe

Dockerfile

FROM scottyhardy/docker-wine
RUN echo "zh_CN GB2312\n\
    zh_CN.GB18030 GB18030\n\
    zh_CN.GBK GBK\n\
    zh_CN.UTF-8 UTF-8" >> /etc/locale.gen && \
    locale-gen
ENV LC_ALL=zh_CN.utf8 \
    WINEPREFIX=/home/wineuser/.wine32 \
    WINEARCH=win32 \
    WINEDEBUG=fixme-all \
    WINEDLLOVERRIDES="wshom.ocx=n;" \
    WINE_EXPLORER_FULLSCREEN=1 \
    WINEESYNC=1 \
    LIBGL_ALWAYS_INDIRECT=1
#    WINEESYNC=1 \
COPY HALo/Fonts/* $WINEPREFIX/drive_c/windows/Fonts/
RUN winecfg -v win10 && \
    winetricks -q dotnet40 dotnet40_kb2468871 wsh57 && \
    regsvr32 $WINEPREFIX/drive_c/windows/system32/wshom.ocx
ENTRYPOINT ["wine","explorer","/desktop=HALo,1800x900","/home/wineuser/HALo/Mediinfo.WinForm.HIS.Update.exe"]
#CMD ["wine","explorer", "/desktop=name,1800x900", "/root/HALo/Mediinfo.WinForm.HIS.Update.exe"]

之后当成命令运行即可

docker run --rm \
           --env="DISPLAY" \
           --volume="${XAUTHORITY:-${HOME}/.Xauthority}:/root/.Xauthority:ro" \
           --volume="/tmp/.X11-unix:/tmp/.X11-unix:ro" \
           --volume="/media/minecraft/Data/software/HALo:/home/wineuser/HALo" \
           --ipc=host \
           halo

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注