将IronPython WPF项目编译为exe文件

23

最佳的方法是如何为部署打包IronPython应用程序?经过搜寻网络,我找到了最好的方法(也是我目前正在使用的方法)是使用clr.CompileModules()将整个项目的.py文件粘合在一起成为一个.dll文件,然后只需要一个单独的run.py文件来运行该.dll文件即可:

import clr
clr.AddReference('compiledapp.dll')

import app

这仍然不够理想,因为这意味着我必须:
  1. 分发三个文件(.dll文件、.xaml文件和run.py启动器)
  2. 在主机上安装IronPython
此外,感觉这样做有些...hacky,在Visual Studio 2010中,IronPython已经有了很好的集成。我完全不明白为什么没有集成的构建系统来构建IPy应用程序,因为它最终都会转换成IL。
理想情况下,我希望能够拥有一个单独的.exe文件,并将.xaml文件合并到其中(我读到C#应用程序将XAML编译为BAML并将其合并到可执行文件中),而不需要安装IronPython即可运行。这至少有一半可能吗?(如果exe文件需要一些额外的.DLL文件或其他东西,那也没关系。重要的是它是以.exe形式存在的。)
一些澄清的编辑:我已经尝试过pyc.py,但它似乎没有意识到我的项目不仅仅是app.py。它生成的exe文件大小表明它只是在“编译”app.py而没有将任何其他文件包含在exe文件中。那么,如何告诉它编译我项目中的每个文件呢? 为了帮助可视化,这里是我项目的解决方案资源管理器窗口的截图。
编辑II:看来唯一的方法是使用pyc.py并将每个文件作为参数传递。我对此方法有两个问题:
  1. 我怎么可能处理这么长的命令行?一个命令中最多只能有256个字符。
  2. pyc.py如何知道要保留包/文件夹结构?如上面我的项目截图所示,我的编译程序如何知道访问子文件夹中的模块,例如访问DT\Device? 层次结构是否在dll中被“保留”?

编辑III:由于通过命令行传递70个文件名给pyc.py将会很难处理,并且为了更优雅地解决构建IPy项目的问题,我决定增强pyc.py

我添加了一些代码,通过/pyproj:参数读取一个.pyproj文件,解析XML并从中获取项目中使用的py文件列表。这一步已经运作得非常好了;然而,生成的可执行文件似乎无法访问作为我的项目一部分的python子包(子文件夹)。我的带有.pyproj读取支持补丁的pyc.py版本可以在这里找到:http://pastebin.com/FgXbZY29

当在我的项目上运行这个新的pyc.py时,输出如下:

c:\Projects\GenScheme\GenScheme>"c:\Program Files (x86)\IronPython 2.7\ipy.exe"
pyc.py /pyproj:GenScheme.pyproj /out:App /main:app.py /target:exe
Input Files:
        c:\Projects\GenScheme\GenScheme\__init__.py
        c:\Projects\GenScheme\GenScheme\Agent.py
        c:\Projects\GenScheme\GenScheme\AIDisplay.py
        c:\Projects\GenScheme\GenScheme\app.py
        c:\Projects\GenScheme\GenScheme\BaseDevice.py
        c:\Projects\GenScheme\GenScheme\BaseManager.py
        c:\Projects\GenScheme\GenScheme\BaseSubSystem.py
        c:\Projects\GenScheme\GenScheme\ControlSchemes.py
        c:\Projects\GenScheme\GenScheme\Cu64\__init__.py
        c:\Projects\GenScheme\GenScheme\Cu64\agent.py
        c:\Projects\GenScheme\GenScheme\Cu64\aidisplays.py
        c:\Projects\GenScheme\GenScheme\Cu64\devmapper.py
        c:\Projects\GenScheme\GenScheme\Cu64\timedprocess.py
        c:\Projects\GenScheme\GenScheme\Cu64\ui.py
        c:\Projects\GenScheme\GenScheme\decorators.py
        c:\Projects\GenScheme\GenScheme\DeviceMapper.py
        c:\Projects\GenScheme\GenScheme\DT\__init__.py
        c:\Projects\GenScheme\GenScheme\DT\Device.py
        c:\Projects\GenScheme\GenScheme\DT\Manager.py
        c:\Projects\GenScheme\GenScheme\DT\SubSystem.py
        c:\Projects\GenScheme\GenScheme\excepts.py
        c:\Projects\GenScheme\GenScheme\FindName.py
        c:\Projects\GenScheme\GenScheme\GenScheme.py
        c:\Projects\GenScheme\GenScheme\PMX\__init__.py
        c:\Projects\GenScheme\GenScheme\PMX\Device.py
        c:\Projects\GenScheme\GenScheme\PMX\Manager.py
        c:\Projects\GenScheme\GenScheme\PMX\SubSystem.py
        c:\Projects\GenScheme\GenScheme\pyevent.py
        c:\Projects\GenScheme\GenScheme\Scheme.py
        c:\Projects\GenScheme\GenScheme\Simulated\__init__.py
        c:\Projects\GenScheme\GenScheme\Simulated\Device.py
        c:\Projects\GenScheme\GenScheme\Simulated\SubSystem.py
        c:\Projects\GenScheme\GenScheme\speech.py
        c:\Projects\GenScheme\GenScheme\stdoutWriter.py
        c:\Projects\GenScheme\GenScheme\Step.py
        c:\Projects\GenScheme\GenScheme\TimedProcess.py
        c:\Projects\GenScheme\GenScheme\UI.py
        c:\Projects\GenScheme\GenScheme\VirtualSubSystem.py
        c:\Projects\GenScheme\GenScheme\Waddle.py
Output:
        App
Target:
        ConsoleApplication
Platform:
        ILOnly
Machine:
        I386
Compiling...
Saved to App

所以它正确地读取了.pyproj文件列表...太好了!但运行exe文件给了我这个:

Unhandled Exception: IronPython.Runtime.Exceptions.ImportException: 
No module named Cu64.ui

因此,尽管Cu64\ui.py显然已包含在编译中,但运行时exe找不到它。这就是我在上一次编辑中第2点所担心的。如何保留项目的软件包层次结构?也许需要单独编译每个软件包?

我将为此问题延长悬赏时间。最终,我希望我们能够获得一个工作的pyc.py,它可以读取pyproj文件并在一步中生成可工作的exe。然后,也许可以提交给IronPython的codeplex以包含在下一个版本中... ;]

1
命令行长度为8191个字符(http://support.microsoft.com/kb/830473)。`clr.CompileModules` 能够很好地处理包 - 它通过 __init__.py 来识别它们,这与 Python 包的通常做法相同。 - Lukas Cenovsky
是的,您可以使用clr.CompileModules,然后制作一个加载.dll的存根.exe。我认为在C#中有一个LoadAssembly函数可以使用。 - jcao219
啊 - 我的意思是 pyc.py 使用 clr.CompileModules,可以很好地处理包。所以只需使用 pyc.py 就可以了,应该能正常工作;-) - Lukas Cenovsky
我知道这已经很老了,但我需要做同样的事情!有什么有效的方法吗? - crazyGamer
据我所记,不幸的是,我最终一次性部署了整个代码库。但是你可以尝试使用我的pyc版本(并尝试进一步改进它!) - Aphex
5个回答

5
使用pyc.py生成app.exe,不要忘记包括app.dll和IronPython库。
至于XAML-我创建了一个专门用于.xaml文件的项目,在VS中进行编译,然后从IronPython中使用它们。例如:
<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="/CompiledStyle;component/Style.xaml" /> 
</ResourceDictionary.MergedDictionaries> 

好的,我已经尝试过pyc.py,但仍然不明白如何使用我需要的所有文件。我的项目由5个文件夹中的78个文件组成,并通过运行app.py启动;但是,正如我在对jcao219的评论中提到的那样,运行“ipy.exe pyc.py /main:app.py /target:winexe”会产生一个无用的app.exe,当启动时什么也不会发生。 - Aphex
我建议你从简单的“Hello World”应用程序开始。试图让78个文件工作太多了。从一个文件开始,再添加另一个文件,然后是子文件夹等。此外,当你通过简单的“ipy.exe app.py”启动应用程序时,它是否能正常运行? - Lukas Cenovsky
是的,ipy.exe app.py 运行得很好。顺便说一下,我在第一篇帖子中添加了一个当前项目结构的截图... 我开始觉得我需要将所有文件作为命令行参数传递给 pyc.py。这 真的 是我构建和分发 IPy 应用程序的方式吗?要输入 ipy.exe pyc.py app.py <78 .py files here> /main:app.py /target:winexe 吗? - Aphex
是的,就是这样。或者你可以编写一些脚本来为你完成它。IronPython对于像这样构建的支持不如C#那么好,因为C#已经填补了这个领域。 - jcao219
jcao219 是正确的 - 你必须把你想要编译的所有文件都给 pyc.py。还请注意,在指定 /main 时,pyc.py 中存在一个 bug 强制创建控制台应用程序。只需删除第 97 行即可解决此问题。 - Lukas Cenovsky
我本来就有这个担心。我想我可以编写一个构建脚本,查询我的项目文件夹中的每个py文件,并将它们传递给pyc... 我只有两个问题。请查看我编辑过的原始帖子。 - Aphex

5

我发布了一份Python脚本,可以获取一个IronPython文件的依赖关系,并将所有内容编译成单独的二进制文件。你可以在Ironpython 2.6 .py -> .exe上找到它。希望对你有用。它也应该适用于WPF,因为它捆绑了WPF支持。


5
"问题归结为IL,但它与C#代码生成的IL不兼容,所以不能直接编译为独立的.exe文件。您需要使用pyc.py将代码编译为带有CompileModules创建的DLL的存根EXE。然后将这些文件与IronPython.dll、IronPython.Modules.dll、Microsoft.Dynamic.dll、Microsoft.Scripting.Debugging.dll、Microsoft.Scripting.dll以及XAML文件一起分发。"
"要编译其他文件,只需将它们作为参数添加即可:ipy.exe pyc.py /main:app.py /target:winexe another.py another2.py additional.py"

ipy.exe pyc.py /main:app.py /target:winexe 会生成一个微小的3kb的 app.exe 文件,但它并没有任何作用。这里还需要做些什么吗? - Aphex
它还会生成app.dll。将其与先前提到的那些dll和您的XAML文件一起分发。 - jcao219
确实有一个小的app.dll被生成了。看起来它太小而无法实际使用,但为了尝试,请问我可以在哪里找到那5个我需要的DLL?我会尝试将其与这些DLL放在同一个文件夹中运行。 - Aphex
5个DLL文件要么在C:\Windows\Microsoft.NET\assembly\GAC_MSIL中,要么在IronPython安装目录中。但如果你正在使用最新版本的IronPython,那么很可能是前者。 - jcao219
好的,我已经从GAC复制了DLL文件。但是,这个exe文件仍然没有任何反应。我认为pyc.py可能只编译了我的项目文件的一部分。 - Aphex
显示剩余2条评论

3
为了创建一组程序集以便于分发IronPython应用程序,您可以使用pyc.py或SharpDevelop。
使用pyc.py进行编译:
ipy.exe pyc.py /main:Program.py Form.py File1.py File2.py ... /target:winexe
考虑到项目中的文件数量,您可以尝试使用SharpDevelop而不是维护长的pyc.py命令行。您需要在SharpDevelop中创建一个新的IronPython项目,并将文件导入项目中。您可能需要逐个导入文件,因为SharpDevelop没有导入多个文件的方法,除非它们在子文件夹中。
然后,您可以使用SharpDevelop将应用程序编译为可执行文件和dll文件。所有其他所需的文件,例如IronPython.dll、Microsoft.Scripting.dll,都将位于bin/debug或bin/release文件夹中。SharpDevelop在幕后使用clr.CompileModules和自定义的MSBuild任务来生成二进制文件。
编译后,您的项目中定义的任何IronPython软件包都应该可以从应用程序中使用。
通过将xaml作为资源嵌入,可以打包XAML。然后使用类似以下代码的代码:
import clr

clr.AddReference('PresentationFramework')
clr.AddReference('System')

from System.IO import FileMode, FileStream, Path
from System.Reflection import Assembly
from System.Windows import Application
from System.Windows.Markup import XamlReader

executingAssemblyFileName = Assembly.GetEntryAssembly().Location
directory = Path.GetDirectoryName(executingAssemblyFileName)
xamlFileName = Path.Combine(directory, "Window1.xaml")

stream = FileStream(xamlFileName, FileMode.Open)
window = XamlReader.Load(stream)
app = Application()
app.Run(window)

SharpDevelop 3.2无法正确嵌入资源文件,因此您需要使用SharpDevelop 4。

如果您正在使用IronPython 2.7,则可以使用新的clr.LoadComponent方法,该方法接受一个对象和XAML文件名或流,并将该对象连接到XAML。

虽然C#编译器可以将您的XAML编译为BAML资源,但在IronPython中执行相同操作会出现一些问题。如果您不通过x:Class属性将XAML链接到类,则可能会将XAML编译为BAML资源并将其嵌入到程序集中。但是,您将不会获得任何自动生成的代码,因此您需要自己创建该代码。另一个问题是这种方法不能在SharpDevelop上直接使用。您需要编辑SharpDevelop.Build.Python.targets文件,并将<TargetLanguage>从Python更改为C#。尝试使用x:Class属性将不起作用,因为BAML阅读器无法访问任何相关的IronPython类。这是因为编译后的IronPython应用程序生成的IL与C#或VB.NET程序集中的IL非常不同。


感谢关于SharpDevelop的介绍。虽然我不太愿意从Visual Studio 2010转移到其他IDE,但这可能是值得一试的东西。请查看我的最近编辑,了解我为使pyc读取所有文件所做的一些工作;我离让它正常工作非常接近了。 - Aphex

2

我安装了带有PTVS(ironpython 2.7)的Visual Studio 2015。我创建了一个非常简单的WPF项目,但是无法编译exe。我一直得到“ImportError: No module named wpf”的异常。

import clr
clr.AddReferenceToFileAndPath("c:\\path\\to\\IronPython.Wpf.dll")
clr.AddReferenceToFileAndPath('c:\\path\\to\\PresentationCore.dll')
clr.AddReferenceToFileAndPath('c:\\path\\to\\PresentationFramework.dll')
clr.AddReferenceToFileAndPath('c:\\path\\to\\WindowsBase.dll')

from System.Windows import Application, Window

import wpf

class MyWindow(Window):
    def __init__(self):
        wpf.LoadComponent(self, 'RegExTester.xaml')

    def OnSearch(self, sender, e):
        self.tbOut.Text = "hello world"

if __name__ == '__main__':
    Application().Run(MyWindow())

我得到的错误是因为clr子句必须在import wpf之前。编译步骤如下:
  1. 安装CPython 2.7的pip(不是IronPython!)

  2. 安装ipy2asm

python -m pip install ironpycompiler

  1. 像这样编译应用程序

ipy2asm compile -t winexe -e -s program.py


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接