Windows共享記憶體 C++及C#實現
阿新 • • 發佈:2019-02-16
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);
}
當然在一個應用向檔案中寫入資料時,其它程序不應該去讀取這個正在寫入的資料。這就需要進行一些同步的操作。
下邊來看一下具體的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);
}