在《多样、互动的WinForm UI设计与开发思路》一文中,我提到过把Flash作为控件嵌入到WinForm程序中以提高软件的互动效果并降低开发难度这样一种思路。但这样一个系统,我们往往不希望随应用程序打包,或者让用户看到,很多很多的FLASH文件。其中一个办法就是把flash文件作为资源嵌入到系统中。但这样又出现一个问题,Flash Player只有一个Movie属性用于指定当前播放的媒体,而这个字符串属性只能是本地文件或者一个URL。我们可以在运行时把资源里的flash释放到一个临时目录,但这样一来失去了打包的意义,二来无法保护flash文件。
我们希望最好的解决方案,就是Flash Player提供类似于DataSet.ReadXml()这样的方法,既可以从指定名字的Xml文件读取数据,又可以从一个数据流(Stream)读数据。但是找遍了Flash Player的所有参数,似乎只有MovieData这个属性有点接近,如果我们能直接把Flash文件的内容赋值给它就好了。不过目前我还没有找到关于这个属性的资料 -_-
另外一个思路,就是在我们的程序运行的时候,动态建立一个自己的http服务器,可以直接从资源中读取内容。事实上,对于大部分要求文件或url形式的内容,都可以通过建立虚拟http服务器来模拟。但实现起来要费点功夫,而且http会在本机留下缓存,安全性不高。
最后在whoo的《从内存播放Flash》一文找到了另外一种方法,就是通过命名管道(Named Pipe)。
首先我们看看命名管道为什么能够解决这个问题。这要从它的运行机制讲起。当一个命名管道被建立起来后,它的服务端和客户端之间的数据交流,就完全是通过和操作文件一样的方式来进行,即CreateFile(),ReadFile()和WriteFile()三个函数。也就是说客户端可以像读取文件一样,来读取管道里的数据。
我们通过调用Win32 API 既可创建管道。这里先简单讲一下服务器端的处理流程:
1、通过CreateNamedPipe()函数创建一个命名管道,如果成功,会返回这个管道实例的句柄。
2、管道创建成功后,调用ConnectNamedPipe()函数等到客户端连接。
3、如果有客户端连接进来,可以通过对文件的操作来交换数据,例如ReadFile()和WriteFile()。
在.NET里我们不必用这些Win32 API函数来读写文件,直接用System.IO命名空间下的类即可(将文件读写的内存指针指向管道句柄即可)。
注:目前该方法只针对在Flash Player播放器打开 \\.\pipe\testPipe 才有效。如果您使用的是Flash Player 7.0,那么一定要用 \\?\pipe\testPipe才行。
至于在html页面或WinForm里的Flash Player控件,指定其Movie属性为"\\.\pipe\testPipe",目前尚无效。
以下是简单的例子:
using System.IO;
namespace PipeServer {
class Entry {
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args) {
CreatePipe();
}
private const int BUFFER_SIZE = 1024;
private const int PIPE_TIMEOUT = 1000;
private const string PIPE_NAME = @"\\.\pipe\testPipe";
public static void CreatePipe(){
API.SECURITY_ATTRIBUTES sec = new API.SECURITY_ATTRIBUTES();
int pipeHandle = API.CreateNamedPipe( PIPE_NAME, API.PIPE_ACCESS_DUPLEX
, API.PIPE_WAIT, API.PIPE_UNLIMITED_INSTANCES, BUFFER_SIZE, BUFFER_SIZE
, PIPE_TIMEOUT, ref sec);
Console.WriteLine(pipeHandle);
if ( pipeHandle == API.INVALID_HANDLE_VALUE ){
Console.WriteLine("Cannot create pipe.");
return;
}else{
Console.WriteLine("Create pipe successful. Waiting for connection");
}
API.OVERLAPPED lapped = new API.OVERLAPPED();
int connected = API.ConnectNamedPipe( pipeHandle, ref lapped );
if ( connected > 0 ){
// 如果从资源文件读入swf文件,用下面这句
// using ( Stream swf = typeof(API).Assembly.GetManifestResourceStream("PipeServer.test.swf")) {
using ( Stream swf = File.Open(@"test.swf", FileMode.Open)) {
using (FileStream pipe = new FileStream( new IntPtr(pipeHandle), FileAccess.Write)) {
int reads;
byte[] buffer = new byte[BUFFER_SIZE];
while ( (reads = swf.Read(buffer, 0, BUFFER_SIZE) ) > 0 ) {
pipe.Write(buffer, 0, reads);
}
pipe.Flush();
}
}
Console.WriteLine("Someone arrived.");
API.DisconnectNamedPipe(pipeHandle);
API.CloseHandle( pipeHandle );
}else{
API.CloseHandle( pipeHandle );
}
}
}
}
using System.Runtime.InteropServices;
namespace PipeServer {
public class API {
public const int INVALID_HANDLE_VALUE = -1;
public const int PIPE_ACCESS_INBOUND = 0x1;
public const int PIPE_ACCESS_OUTBOUND = 0x2;
public const int PIPE_ACCESS_DUPLEX = 0x3;
public const int PIPE_WAIT = 0x0;
public const int PIPE_UNLIMITED_INSTANCES = 255;
[DllImport("kernel32.dll", EntryPoint="CreateNamedPipe")]
public static extern int CreateNamedPipe (
string lpName,
int dwOpenMode,
int dwPipeMode,
int nMaxInstances,
int nOutBufferSize,
int nInBufferSize,
int nDefaultTimeOut,
ref SECURITY_ATTRIBUTES lpSecurityAttributes
);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES {
public int nLength;
public int lpSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", EntryPoint="ConnectNamedPipe")]
public static extern int ConnectNamedPipe (
int hNamedPipe,
ref OVERLAPPED lpOverlapped
);
[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED {
public int Internal;
public int InternalHigh;
public int offset;
public int OffsetHigh;
public int hEvent;
}
[DllImport("kernel32.dll", EntryPoint="CloseHandle")]
public static extern int CloseHandle (
int hObject
);
[DllImport("kernel32.dll", EntryPoint="DisconnectNamedPipe")]
public static extern int DisconnectNamedPipe (
int hNamedPipe
);
}
}