我正在探索IPC的各种技术,目前:内存映射文件。简而言之,在C#中我通过
创建了这个using (var f = MemoryMappedFile.CreateOrOpen("TestMap", 10, MemoryMappedFileAccess.ReadWrite))
{
var view = f.CreateViewAccessor();
// read/write to the view ...
}
在VBA中(是的,我知道......)我通过调用kernel32函数来打开它,基本上是
hFileMap = OpenFileMapping(GENERIC_READ Or GENERIC_WRITE, 0, "TestMap")
hMM = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0)
Dim data(0 To 9) As Byte
' .. point data towards the hMM array data .. leaving out the code for brevity
我的印象是两者的数据应完全同步,即当我将vba中的数据(0)设置为某个字节值,可以保证与C#中的相应视图同步,例如, view.ReadByte(0);在C#之后(即使之后很快),它将保证返回正确/更新的值。
这个假设是否正确? 或者是否存在同步延迟,这意味着进程B(VBA)中的数组可能并不总是与进程A(C#)中的视图访问器100%同步?如果有,是否有任何内置方式强制并等待同步?
由于
问我正面临一个问题,这个问题表明我最初的假设实际上是错误的。当我在VBA中更新数组并在C#中很快读取它之后,它似乎仍然包含旧数据。当我引入人工时间延迟时,数据会重新排列。
试图确保这实际上是问题的原因,而不是在这里出现其他一些错误(在我发出信号或其他方式的某些概念性问题)
对于上下文,这里有一点我正在尝试的方法:
我有两个进程相互通信;协议相当清楚,两个进程总是知道轮到发送消息了。因此,总会有一个“读者”流程只读取数据,而“作者”流程会推送这些数据(角色可能会切换,但总是会明确定义哪个流程具有什么角色)。
我的想法是创建一个长度为N的内存映射文件,它可以对应于类似
的布局enum IpcStatus : Byte
{
// The reader can kepe reading, all fine
Readable = 0,
// MemoryMappedFileLayout.Data is full, and the writer wants to write some more data.
// Once the reader has read the entire buffer, it must reset the file, i.e. set
// MemoryMappedFileLayout.DataLength back to 0, and MemoryMappedFileLayout.Status back to 0.
// The writer will keep polling this, and continues writing once Status is 0 again
// and then begin reading from Data[0] onwards again
BufferFull = 1,
// The writer is currently updating MemoryMappedFileLayout.DataLength - so the reader
// shouldn't read this value, but rather wait/spin until that's done
LengthLocked = 2
}
struct MemoryMappedFileLayout
{
// How many bytes of Data have actually been written / are meaningful?
Int32 DataLength;
IpcStatus Status;
byte[N-sizeof(Int32)-sizeof(IpcStatus)] Data;
}
所以假设N = 10(内存映射文件长10个字节),这意味着Data包含5个字节(10-sizeof(int32-sizeof(byte))。 假设memFile [0到9]是一个长度为10的字节数组,代表了这个内存。
假设作者想写7个字节的数据:
writer copies the first 5 bytes into memFile[5 to 9]
writer sets memFile[4] to IpcStatus.LengthLocked
writer sets memFile[0 To 3] to (Int32)5 // i.e. sets data length to 5
writer sets memFile[4] to IpcStatus.BufferFull // i.e. signals to client that it needs to write more, but buffer full
writer keeps spinning and re-reading memFile[4] until it is == IpcStatus.Readable
writer copies last 2 bytes it needs to write into memFile[5 To 6]
writer sets memFile[4] To IpcStatus.LengthLocked
writer sets memFile[0 To 3] T0 (Int32)2
writer sets memFile[4] to IpcStatus.Readable
与此同时,读者可以做到以下几点。让我们说读者知道它需要读取7个字节。
reader checks if memFile[4] is IpcStatus.LengthLocked; if so, spin and re-read until it is not anymore
reader reads memFile[0 To 3] -> first value it sees will be (Int32)5 // i.e. read length of available data, which is 5
reader reads memFile[5 To 9] and copies into its own output buffer
reader reads memFile[4] and sees its == IpcStatus.BufferFull at this point, so:
reader sets memFile[0 To 3] = 0 (e.g. data length = 0)
reader sets memFile[4] = IpcStatus.Readable
reader checks if memFile[4] is IpcStatus.LengthLocked; if so, spin and re-read until it is not anymore
reader reads memFile[0 To 3] -> it will now see (Int)2 // i.e. read length of available data, which is 2
reader reads memFile[5 To 6] and copies to its own output buffer