问题背景
我已经使用执行以下操作的C#程序为Flash(* .fla)项目文件创建了“一键式”发布过程:
更新两个主要项目文件中的静态“版本”变量(每个FLA项目一个)
更改嵌入式JSFL模板文件(自动打开/发布特定的FLA文件),并将其写入磁盘以供下一步使用
通过Process.Start调用Flash Professional,将路径传递给flash.exe以及JSFL文件名作为参数,因此Flash运行JSFL文件并发布项目
将项目文件发布到我的Web服务器(通过Win7 <-> WinServer2012 VPN隧道通过映射的共享驱动器进行简单的备份/文件复制)
UI具有三个按钮,一个按钮用于发布第一个项目,一个按钮用于发布第二个项目,一个按钮用于发布两个项目。第二个项目取决于第一个项目,因此如果并且当单击“发布两者”按钮时,它应该在发布第二个文件之前完全完成发布第一个文件。
对于每个项目,它将主文档类AS文件作为字符串读取到内存中,并使用Regex更新特定的静态“版本”变量以具有当前时间戳。然后,它将使用更新的版本重写文件。 “版本”变量的目的是在运行时在浏览器中显示,因此我确定我正在测试该项目的最新编译版本。
Flash Professional接受JSFL文件名作为命令行参数,但是不允许将其他参数传递给该JSFL文件,因此该程序将JSFL模板更改为包括适当的参数,并将定制的临时JSFL文件通过传递给Flash Professional。 Process.Start。
通过其他通用功能可以发布到Web,该通用功能使我可以指定源路径和目标路径的列表(每个文件都带有可选的带时间戳的备份),该列表可以自动将特定的已发布文件备份和复制到我的Web服务器。
问题
首先,我应该提到的是,当我仅发布一个项目文件时,一切正常,而我要解决的问题是定时或事件信号之一。
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脚本来发布下一个文件。
最佳答案
我通过以下项目解决了这个问题: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 );
}
}
单击项目的按钮时将调用的“发布”方法(以及“ fillTemplate”方法:
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] )
。
关于flash - 自动发布FLA文件;多次调用Process.Start,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23525495/