0

目标

我的目标是为pandoc创建一个简单的前端。我了解到 execl 是在系统中调用可执行文件的好方法。

请注意以下代码中的函数 btn_pressed,它使用上述方法调用 pandoc。

[indent=4]
uses
    Posix
    Gtk


class TestWindow:Window
    _file_chooser:FileChooserButton
    _entry:Gtk.Entry
    _button:Gtk.Button
    _file:File

    construct()
        title = "Pandoc GUI"
        window_position = WindowPosition.CENTER
        destroy.connect( Gtk.main_quit )
        var folder_chooser = new FileChooserButton("Choose a Folder",FileChooserAction.SELECT_FOLDER)
        folder_chooser.set_current_folder( Environment.get_home_dir() )

        //I used selection_changed directly as per the question in stack_exchange
        //http://stackoverflow.com/questions/34689763/the-signal-connect-syntax
        folder_chooser.selection_changed.connect( folder_changed )

        _file_chooser = new FileChooserButton("Choose a File",FileChooserAction.OPEN)
        _file_chooser.set_current_folder( Environment.get_home_dir() )

        _file_chooser.file_set.connect( file_changed )
        _entry = new Gtk.Entry()
        _entry.set_text("Here the file name")

        _button = new Button.with_label("Convert to pdf")
        _button.set_sensitive(false)
        _button.clicked.connect(btn_pressed)

        var box = new Box( Orientation.VERTICAL, 0 )
        box.pack_start( folder_chooser, true, true, 0 )
        box.pack_start( _file_chooser, true, true, 0 )
        box.pack_start( _entry, true, true, 0 )
        box.pack_start( _button, true, true, 0 )
        add( box )

    def folder_changed( folder_chooser_widget:FileChooser )
        folder:string = folder_chooser_widget.get_uri()
        _file_chooser.set_current_folder_uri( folder )

    def file_changed ( file_chooser_widget: FileChooser )
        _file = File.new_for_uri(file_chooser_widget.get_uri())

        try
            info:FileInfo = _file.query_info (FileAttribute.ACCESS_CAN_WRITE, FileQueryInfoFlags.NONE, null)
            writable: bool = info.get_attribute_boolean (FileAttribute.ACCESS_CAN_WRITE)
            if !writable
                _entry.set_sensitive (false)
            else
                _button.set_sensitive (true)
        except e: Error
            print e.message

        _entry.set_text(_file.get_basename())

    def btn_pressed ()
        var md_name=_entry.get_text()+".md -s -o "+_entry.get_text()+".pdf"
        execl("/usr/bin/pandoc", md_name)
        _button.set_sensitive (false)

init
    Gtk.init( ref args )
    var test = new TestWindow()
    test.show_all()
    Gtk.main()

错误

在执行时,我的代码根本没有得到任何响应,也没有任何 pdf 被渲染。

问题

  • 如何使用 execl 调试二进制文件的执行?
4

1 回答 1

1

我会使用GLib.Subprocess调用外部命令,因为它可以更好地控制外部命令的输入和输出。不过,将下面的示例更改为execl应该很容易。

首先是将外部命令与窗口对象分离。这使它更具可测试性。为此,使用了一个单独的对象——Subprocess调用的包装器。将此代码另存为ToPDF.gs

namespace FileConverters

    class ToPDF

        const _command:string = "pandoc"

        def async convert( source:string, output:string )
            try
                var flags = SubprocessFlags.STDOUT_PIPE \
                            | SubprocessFlags.STDERR_PIPE
                var subprocess = new Subprocess( flags, 
                                                 _command, 
                                                 source, 
                                                 output 
                                                )
                output_buffer:Bytes
                yield subprocess.communicate_async( null, 
                                                    null,
                                                    out output_buffer, 
                                                    null 
                                                   )
                if ( subprocess.get_exit_status() == 0 )
                    debug( "command successful: \n %s",
                           (string)output_buffer.get_data() 
                          )
                else
                    debug( "command failed" )
            except err:Error
                debug( err.message )

该类ToPDF现在与您的应用程序的其余部分分离。这意味着它可以重复使用。为了说明这一点,下面显示了一个使用该类的集成测试。

也使用ToPDF异步代码。所以我先解释一下。使方法异步意味着它将与应用程序的主线程同时运行。通过同时调用外部程序,这意味着主线程在等待外部程序完成时不会锁定。使用async意味着该功能一分为二。第一部分被调用convert.begin( source, output )并且将运行到yield命令。此时程序的执行分成两部分。主线程将返回给 的调用者convert.begin,但在后台启动的是Subprocess. 完成Subprocess后,它返回convert并完成方法调用。

将集成测试另存为ToPDFTest.gs

uses FileConverters

init
    var a = new ToPDF()
    a.convert.begin( "source_file.md", "output_file.pdf" )

    var loop = new MainLoop()
    var loop_quitter = new LoopQuitter( loop )
    Timeout.add_seconds( 2, loop_quitter.quit )
    loop.run()

class LoopQuitter
    _loop:MainLoop

    construct( loop:MainLoop )
        _loop = loop

    def quit():bool
        _loop.quit()
        return false

编译valac --pkg gio-2.0 ToPDF.gs ToPDFTest.gs 然后运行测试G_MESSAGES_DEBUG=all ./ToPDFTest

测试使用MainLoop,它是 的基类Gtk.Main。为了模拟长时间运行的程序,设置了两秒超时,然后MainLoop.quit()调用以结束测试。不幸的是MainLoop.quit(),没有正确的Timeout回调函数签名,因此使用了包装类LoopQuitter

像这样的集成测试通常在软件发布之前保留并运行,以确保应用程序与其他软件模块一起工作。

ToPDF与您的窗口集成,您需要更改

execl("/usr/bin/pandoc", md_name)

类似于

var to_pdf = new Fileconverts.ToPDF()
to_pdf.convert.begin( md_name, pdf_name )

您可能还希望将其包装在类似于避免 Genie 中的全局变量的命令模式中。您可能还想修改它以向用户提供更好的反馈。

于 2016-06-24T19:16:45.007 回答