自动发布 FLA 文件;多次调用 Process.Start

Posted

技术标签:

【中文标题】自动发布 FLA 文件;多次调用 Process.Start【英文标题】:Automating publishing of FLA files; calling Process.Start multiple times 【发布时间】:2014-06-24 21:13:52 【问题描述】:

问题背景

我已使用执行以下操作的 C# 程序为我的 Flash (*.fla) 项目文件创建了“一键式”发布流程:

更新两个主要项目文件中的静态“版本”变量(每个 FLA 项目一个) 更改嵌入式 JSFL 模板文件(自动打开/发布特定 FLA 文件)并将其写入磁盘以供下一步使用 通过 Process.Start 调用 Flash Professional,将路径传递给 flash.exe 以及 JSFL 文件名作为参数,因此 flash 运行 JSFL 文件并发布项目 将项目文件发布到我的 Web 服务器(通过 Win7 上的映射共享驱动器进行简单备份/文件复制WinServer2012 *** 隧道)

UI 有三个按钮,一个用于发布第一个项目,一个用于发布第二个项目,一个用于发布两个项目。第二个项目依赖于第一个项目,所以如果点击“同时发布”按钮,它应该在发布第二个文件之前完全完成第一个文件的发布。

对于每个项目,它将主文档类 AS 文件作为字符串读入内存,并使用正则表达式更新特定的静态“版本”变量以具有当前时间戳。然后它用更新的版本重写文件。 “版本”变量的目的是在运行时在浏览器中显示,所以我确定我正在测试项目的最新编译版本。

Flash Professional 接受 JSFL 文件名作为命令行参数,但不允许将其他参数传递给该 JSFL 文件,因此程序会更改 JSFL 模板以包含正确的参数并将自定义的临时 JSFL 文件传递​​给Flash Professional 通过 Process.Start。

通过另一个通用功能向网络发布,让我可以指定源路径和目标路径列表(每个文件的可选时间戳备份),从而自动将特定已发布文件的备份和复制到我的网络服务器。


问题

首先,我应该提到,当我只发布一个项目文件时,一切正常,而我要解决的问题是时间或事件信号之一。

Flash 是一个仅限单实例的应用程序,因此运行 Process.Start 将启动 Flash Professional(如果它尚未运行)并运行 JSFL 脚本,或者它将在 Flash Professional 的现有实例中运行 JSFL 脚本。

第一个问题是在调用 Process.Start 之后,我无法调用 waitForExit 来完成任务,因为 Flash 保持打开状态。如果 Flash 已经打开,waitForExit 实际上会很快返回,因为第二个 Flash.exe 实例将在将命令转发到主实例后关闭。仅单实例应用程序实际上并没有阻止第二个进程启动,它们只是在检测到第二个进程已经在运行并将命令转发给它时快速杀死第二个进程。出于这个原因,我不能简单地等待进程退出,因为 Flash 可能已打开,也可能未打开。

假设我根本不等待,我的应用程序会很快调用 Process.Start 两次,每个项目一次,传递一个唯一的 JSFL 脚本文件名来为每个项目运行。这样做的问题是第一次调用似乎被丢弃了。我不确定这种行为是植根于 Windows 操作系统还是 Flash Professional 应用程序。当 Flash 尚未打开时会发生这种情况。最初的 Process.Start 调用和相应的参数应该是激活 Flash 的原因,所以我希望它能够通过,但是当 Flash 启动时,当它最终显示主窗口时,它只是运行的第二个脚本。

有趣的是,如果 Flash 已经启动,尽管激活速度很快,但两个脚本似乎都在运行(我看到两个文档都在 IDE 中打开),但同时发布实际上导致 Flash 崩溃(主窗口消失并且进程突然终止没有任何错误)。

所以我需要一种方法来协调发出这些 Process.Start 命令。谢天谢地,JSFL“发布”方法是同步的,JSFL能够执行命令行命令,所以也许一旦发布方法返回,我可以调用一些外部EXE作为协调机制来检测每个脚本何时完成它之前的工作执行下一个?有没有人对这种进程间通信有任何经验可以帮助我?

TL;DR

我需要知道如何构建一个简单的可执行文件,以便在从命令行调用时,它会向特定的外部进程发送一条消息,指示操作已完成。基本上,在 Flash Professional 中运行的 JSFL 脚本必须在文件完成发布后通过 "undocumented" FLfile.runCommandLine 方法调用 exe,然后该 exe 必须通知我的自动化程序,以便它知道 Flash 已完成文件发布并准备就绪运行另一个 JSFL 脚本来发布下一个文件。

【问题讨论】:

【参考方案1】:

我通过以下项目解决了这个问题:http://www.codeproject.com/Articles/17606/NET-Interprocess-Communication

它提供了一个简单、零配置的 IPC 作为类库实现。

我将它添加到我的自动化程序中,并允许可执行文件运行,参数表明它应该向主实例发出信号并关闭。主要逻辑只是检查: Environment.GetCommandLineArgs() 是否有标志表明它应该发送 IPC 消息并关闭而不是实际显示主窗体。

下面是主程序信号系统的完整实现:​​

static class Program

    private static readonly string MUTEX_AND_CHANNEL_NAME = "FlashPublishingAutomation";
    private static bool acquired_app_lock = false;
    private static Mutex app_lock;

    private static XDListener listener;
    public static ManualResetEvent publishCompleteSignal = new ManualResetEvent( true );

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        string[] args = Environment.GetCommandLineArgs();
        if ((args.Length > 1) && (args[1] == "-publishcomplete"))
        
            XDBroadcast.SendToChannel( MUTEX_AND_CHANNEL_NAME, "publishcomplete" );
            Application.Exit();
            return;
        
        else
        
            bool createdNew = false;
            MutexSecurity security = new MutexSecurity();
            MutexAccessRule rule = new MutexAccessRule( "Users", MutexRights.Synchronize | MutexRights.Modify | MutexRights.ReadPermissions, AccessControlType.Allow );
            security.AddAccessRule( rule );
            app_lock = new Mutex( false, "Global\\" + MUTEX_AND_CHANNEL_NAME, out createdNew, security ); //Name must start with "Global\\" in order to be a system-wide mutex for all logged on usesr.
            acquired_app_lock = app_lock.WaitOne( TimeSpan.Zero, true );

            if (!acquired_app_lock)
            
                MessageBox.Show( "An instance of FlashPublishingAutomation is already running.\r\nOnly one instance is allowed." );
            
            else
            
                listener = new XDListener();
                listener.RegisterChannel( MUTEX_AND_CHANNEL_NAME );
                listener.MessageReceived += listener_MessageReceived;
                Application.ApplicationExit += Application_ApplicationExit;
                Application.Run(new Form1());
            

            if (acquired_app_lock)
                app_lock.ReleaseMutex();
            app_lock.Close();
        
    

    static void listener_MessageReceived(object sender, XDMessageEventArgs e)
    
        switch (e.DataGram.Message)
        
            case "publishcomplete":
                publishCompleteSignal.Set();
                break;
        
    

    static void Application_ApplicationExit(object sender, EventArgs e)
    
        listener.MessageReceived -= listener_MessageReceived;
        listener.UnRegisterChannel( MUTEX_AND_CHANNEL_NAME );
    

以及单击项目按钮时调用的“发布”方法(以及“填充模板”方法:

private static readonly string FLASH_PATH = @"C:\Program Files (x86)\Adobe\Adobe Flash CS6\Flash.exe";
public void publish( string fla_directory, string fla_filename, string jsfl_filename )

    Program.publishCompleteSignal.Reset();
    string template = fillTemplate( fla_directory, fla_filename );
    string curdir = Environment.CurrentDirectory;
    string tempJSFLfilepath = Path.Combine( curdir, jsfl_filename );
    File.WriteAllText( tempJSFLfilepath, template );
    Process p = Process.Start( FLASH_PATH, tempJSFLfilepath );
    Program.publishCompleteSignal.WaitOne( 30000 ); //wait for signal from JSFL runnCommandLine; timeout after 30 seconds; may want to increase this value if Flash needs time to startup or files take a long time to publish


private string fillTemplate( string fla_directory, string fla_filename )

    string fileuri = "file:///" + Path.Combine( fla_directory, fla_filename ).Replace( '\\','/' ); //convert path to file URI
    return EmbeddedResources.OpenAndPublishJSFLTemplate
        .Replace( "FLAFILEPATH", HttpUtility.javascriptStringEncode( fileuri ) )
        .Replace("FLAFILENAME", HttpUtility.JavaScriptStringEncode( fla_filename ) )
        .Replace("COMPLETECOMMAND", HttpUtility.JavaScriptStringEncode( "\"" + Application.ExecutablePath + "\"" + " -publishcomplete" ));

此外,这是自动化程序在 Flash 中执行之前填写的 JSFL 模板。它作为字符串嵌入到 EmbeddedResources.OpenAndPublishJSFLTemplate` 下。 C# 应用程序将 FLAFILENAME、FLAFILEPATH 和 COMPLETECOMMAND 字符串替换为目标 FLA 文件名、FLA uri(格式为 file:///path_to_FLA),最后是上面实现的 C# 应用程序本身的路径(加上“-publishcomplete “ 转变)。 C# 应用程序通过 System.Windows.Forms.Application.ExecutablePath 获取自己的路径。填充此模板后,它将作为 JSFL 文件写入磁盘,并通过 Process.Start 作为参数传递给 Flash Professional (flash.exe)。一旦 JSFL 文件发布了 FLA,它就会执行带有“-publishcomplete”标志的自动化程序的新实例,该标志向自动化程序的主实例发出信号以触发手动重置事件。

总之,自动化程序会在调用 Flash 之前重置一个事件,然后在 Flash 完成发布后等待信号,然后再尝试发布下一个文件。

var myDocument = null;
var wasOpen = false;
var isOpen = false;
var openDocs = fl.documents;
var filename = "FLAFILENAME"; //template parameter: the filename (name only, without the path) of the FLA file to publish
var filepath = "FLAFILEPATH"; //template parameter: the URI (beginning with "file:///") of the FLA file to publish
for(var i=0;i < openDocs.length; i++)

    myDocument = openDocs[i];
    if (myDocument.name.toLowerCase() == filename.toLowerCase())
    
        wasOpen = true;
        isOpen = true;
        break;
    

if (!wasOpen)

    myDocument = null;
    fl.openDocument( filepath );
    openDocs = fl.documents;
    for(var i=0;i < openDocs.length; i++)
    
        myDocument = openDocs[i];
        if (myDocument.name.toLowerCase() == filename.toLowerCase())
        
            isOpen = true;
            break;
        
    

if (isOpen && (myDocument != null))

    //Publish the document
    myDocument.publish(); //this method is synchronous, so it won't return until the publish operation has fully completed

    //Signal the automation program that publishing has completed (COMPLETECOMMAND should be 
    FLfile.runCommandLine("COMPLETECOMMAND"); //tempate parameter: the automation program's executable path plus the "-publishcomplete" argument

else
    alert( "Publishing of " + filename + " failed.  File was not open and failed to open." );

我实际上对我在这里创建的东西印象深刻。实现了两个超大(上万行,上百个类)FLA项目的端到端发布(版本控制、编译、备份、部署到web服务器),一键完成不到 10 秒。

如果将 JSFL 模板简化为仅调用甚至不打开文件的静默 FLA 发布方法,并允许您指定要使用的发布配置文件,这可能会运行得更快:fl.publishDocument( flaURI [, publishProfile] )

【讨论】:

以上是关于自动发布 FLA 文件;多次调用 Process.Start的主要内容,如果未能解决你的问题,请参考以下文章

错误记录Android 编译时技术报错 ( 注解处理器 process 方法多次调用问题 )

是否可以从命令行发布 FLA?

自动点击问题

多次 SignalR 调用使 UI 没有响应

如何使用 Pythons Multiprocessing 库的 Process 类多次运行一个函数?

Out of Process COM Server - 每个调用进程一个服务器进程?