FileMapping用於將存在於磁盤的文件放進一個進程的虛擬地址空間,並在該進程的虛擬地址空間中產生一個區域用於“存放”該文件,這個空間就叫做File View,系統並同時產生一個File Mapping Object(存放於物理內存中)用於維持這種映射關系,這樣當多個進程需要讀寫那個文件的數據時,它們的File View其實對應的都是同一個File Mapping Object,這樣做可節省內存和保持數據的同步性,並達到數據共享的目的。
當然在一個應用向文件中寫入數據時,其它進程不應該去讀取這個正在寫入的數據。這就需要進行一些同步的操作。
下邊來看一下具體的API。
CreateFileMaping 的用法:
HANDLE CreateFileMapping( //返回File Mapping Object的句柄
HANDLE hFile, // 想要產生映射的文件的句柄
LPSECURITY_ATTRIBUTES lpAttributes, // 安全屬性(只對NT和2000生效)
DWORD flProtect, // 保護標致
DWORD dwMaximumSizeHigh, // 在DWORD的高位中存放
File Mapping Object // 的大小
DWORD dwMaximumSizeLow, // 在DWORD的低位中存放
File Mapping Object // 的大小(通常這兩個參數有一個為0)
LPCTSTR lpName // File Mapping Object的名稱。
);
1) 物理文件句柄
任何可以獲得的物理文件句柄,如果你需要創建一個物理文件無關的內存映射也無妨,將它設置成為0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.
如果需要和物理文件關聯,要確保你的物理文件創建的時候的訪問模式和"保護設置"匹配,比如: 物理文件只讀,內存映射需要讀寫就會發生錯誤。推薦你的物理文件使用獨占方式創建。
如果使用INVALID_HANDLE_VALUE,也需要設置需要申請的內存空間的大小,無論物理文件句柄參數是否有效, 這樣CreateFileMapping 就可以創建一個和物理文件大小無關的內存空間給你, 甚至超過實際文件大小,如果你的物理文件有效,而大小參數為0,則返回給你的是一個和物理文件大小一樣的內存空間地址范圍。返回給你的文件映射地址空間是可以通過復制,集成或者命名得到,初始內容為0。
2) 保護設置
就是安全設置, 不過一般設置NULL就可以了, 使用默認的安全配置. 在win2k下如果需要進行限制, 這是針對那些將內存文件映射共享給整個網絡上面的應用進程使用是, 可以考慮進行限制.
3) 高位文件大小
32位地址空間, 設置為0。
4) 共享內存名稱
命名可以包含"Global" 或者"Local" 前綴在全局或者會話名空間初級文件映射. 其他部分可以包含任何除了()以外的字符, 可以參考Kernel Object Name Spaces.
5) 調用CreateFileMapping的時候GetLastError的對應錯誤
ERROR_FILE_INVALID 如果企圖創建一個零長度的文件映射, 應有此報
ERROR_INVALID_HANDLE 如果發現你的命名內存空間和現有的內存映射, 互斥量, 信號量, 臨界區同名就麻煩了
ERROR_ALREADY_EXISTS 表示內存空間命名已經存在
使用函數CreateFileMapping創建一個想共享的文件數據句柄,然後使用MapViewOfFile來獲取共享的內存地址,然後使用OpenFileMapping函數在另一個進程裡打開共享文件的名稱,這樣就可以實現不同的進程共享數據。
下邊是C#是對使用的接口函數聲明:
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFile(string lpFileName,
int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes,
int dwCreationDisposition, int dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpAttributes, int flProtect,
int dwMaximumSizeLow, int dwMaximumSizeHigh, string lpName);
[DllImport("kernel32", SetLastError = true)]
public static extern bool FlushViewOfFile(IntPtr lpBaseAddress,
IntPtr dwNumBytesToFlush);
[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr MapViewOfFile(
IntPtr hFileMappingObject, int dwDesiredAccess, int dwFileOffsetHigh,
int dwFileOffsetLow, IntPtr dwNumBytesToMap);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr OpenFileMapping(
int dwDesiredAccess, bool bInheritHandle, string lpName);
[DllImport("kernel32", SetLastError = true)]
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
我們在示例裡Server端建立的一個FileMapping,命名為:@"Global\MyFileMappingObject";這樣我們在Client端就可以打開同名的FileMapping,這樣在Server和Client之前進行通信。每次Server將數據寫入後,我們通過Message的方式通知Client端數據已經准備好,可以讀取了。(關於Message我們之前說過,不在細說。)
具體看一下代碼:
在看具體的代碼這之前,先看一下其中用到的一些常量定義:
[Flags]
public enum MapProtection
{
PageNone = 0x00000000,
// protection - mutually exclusive, do not or
PageReadOnly = 0x00000002,
PageReadWrite = 0x00000004,
PageWriteCopy = 0x00000008,
// attributes - or-able with protection
SecImage = 0x01000000,
SecReserve = 0x04000000,
SecCommit = 0x08000000,
SecNoCache = 0x10000000,
}
public enum MapAccess
{
FileMapCopy = 0x0001,
FileMapWrite = 0x0002,
FileMapRead = 0x0004,
FileMapAllAccess = 0x001f,
}
public const short FILE_ATTRIBUTE_NORMAL = 0x80;
public const short INVALID_HANDLE_VALUE = -1;
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const uint FILE_SHARE_READ = 0x00000001;
public const uint CREATE_NEW = 1;
public const uint CREATE_ALWAYS = 2;
public const uint OPEN_EXISTING = 3;
Windows Message:
public const int WM_USER = 0x0400;
public const int WM_USER_DATAREADY = WM_USER + 101;
下邊是Server端的:
創建MapView:
fileHandle = Win32Wrapper.CreateFileMapping(
IntPtr.Zero, IntPtr.Zero,
(int)(MapProtection.PageReadWrite | MapProtection.SecCommit),
0, 1000000, Win32Wrapper.MappingFileName);
mappingHandle = Win32Wrapper.MapViewOfFile(
fileHandle, (int)MapAccess.FileMapWrite, 0, 0, new IntPtr(1024));
if (mappingHandle == IntPtr.Zero)
{
MessageBox.Show("Open mapping file failed!");
}
寫入信息並通知Client端:
private void btnSend_Click(object sender, EventArgs e)
{
//Write message
string message = string.IsNullOrEmpty(this.sendTxt.Text) ?
"no message" : this.sendTxt.Text;
byte[] source = Encoding.ASCII.GetBytes(message);
byte[] msg = new byte[1024];
Array.Copy(source, msg, source.Length);
Marshal.Copy(msg, 0, mappingHandle, msg.Length);
Win32Wrapper.FlushViewOfFile(mappingHandle, new IntPtr(1024));
//Send message to client
IntPtr handle = GetClientMainFormHandle();
if (handle != IntPtr.Zero)
Win32Wrapper.SendMessage(
handle, Win32Wrapper.WM_USER_DATAREADY,
IntPtr.Zero, IntPtr.Zero);
}
得到Client端主窗體句柄的代碼:
private IntPtr GetClientMainFormHandle()
{
string name =
@"CSharp.MultiProcess.Communication.FileMappingClient";
Process process = GetProcessByName(name);
return process.MainWindowHandle;
}
private Process GetProcessByName(string processName)
{
Process[] processes = Process.GetProcesses();
foreach (Process p in processes)
{
//just for debug
//this method has some question because
//the visual studio started process name
//is not same with the release. so yan can
//close visual studio to test the project.
//normal, you should use
//if(p.ProcessName == processName)
//debug
if (p.ProcessName.StartsWith(processName))
return p;
//release
if (p.ProcessName == processName)
return p;
}
return null;
}
關閉FileMapping:
private void Server_FormClosing(
object sender, FormClosingEventArgs e)
{
if (mappingHandle != IntPtr.Zero)
{
Win32Wrapper.UnmapViewOfFile(mappingHandle);
Win32Wrapper.CloseHandle(mappingHandle);
}
if (fileHandle != IntPtr.Zero)
Win32Wrapper.CloseHandle(fileHandle);
}
Client端代碼:
當有Message收到時接收發送的數據:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case Win32Wrapper.WM_USER_DATAREADY:
RecevieMessage();
break;
default:
base.WndProc(ref m);
break;
}
}
private void RecevieMessage()
{
IntPtr handle = Win32Wrapper.OpenFileMapping(
(int)MapProtection.PageReadWrite, false,
Win32Wrapper.MappingFileName);
IntPtr mappingFile = Win32Wrapper.MapViewOfFile(
handle, (int)MapAccess.FileMapRead,
0, 0, new IntPtr(1024));
//Marshal.GetLastWin32Error();
byte[] msg = new byte[1024];
Marshal.Copy(mappingFile, msg, 0, 1024);
string message = Encoding.ASCII.GetString(msg);
this.listBox1.Items.Add(message);
Win32Wrapper.UnmapViewOfFile(mappingFile);
Win32Wrapper.CloseHandle(mappingFile);
Win32Wrapper.CloseHandle(handle);
}