使用共享内存(Shared Memory)进行C++进程与C#进程之间的通信

一、背景

  1. 我有一个 C++进程A 与一个 C#进程B,我需要这两个进程进行通信;
  2. C#进程B 负责写入数据,C++ 进程A 负责读取数据;
  3. 管道在 C#进程B 中创建并写入,C++ 进程A 在获得管道句柄后再去读取。

二、代码

1. C# 端代码

public partial class MainWindow : Window
{
    
    
	private bool create_memory_successful = false;
    private readonly int shared_memory_size = 640 * 480 * 4;
    private readonly string shared_memory_name = "shared_memory";
    private readonly string shared_memory_mutex = "shared_memory_mutex";
    private MemoryMappedFile shared_memory = null;
    private MemoryMappedViewStream shared_stream = null;
    private Mutex shared_mutex = null;

	protected override void OnInitialized(EventArgs e)
	{
    
    
		base.OnInitialized(e);
		
		// Create shared memory mapping
		try
        {
    
    
            shared_memory = MemoryMappedFile.CreateOrOpen(shared_memory_name, shared_memory_size);

            create_memory_successful = true;
            Console.WriteLine("*** Create shared memory successful ***");
        }
        catch (Exception ex)
        {
    
    
            Console.WriteLine("*** Create shared memory error: " + ex.ToString() + " ***");
            create_memory_successful = false;
        }
		
		if(create_memory_successful)
		{
    
    
			bool flag = false;
        	shared_mutex = new Mutex(true, shared_memory_mutex , out flag);
        	Console.WriteLine("*** Create mutex successful ***");
		}
	}

	// Just a thread function or timer function
	public void OnImageCaptured(USRawImage )
	{
    
    
		if (create_memory_successful && shared_mutex.WaitOne(10))
		{
    
    
			// Get image size
			byte[] imageWidth = BitConverter.GetBytes(rawImage.PixelWidth);
            byte[] imageHeight = BitConverter.GetBytes(rawImage.PixelHeight);

			// Get shared memory stream object
            shared_stream = shared_memory.CreateViewStream(0, shared_memory_size);
            if (shared_stream.CanWrite)
            {
    
    
                Console.WriteLine("Write data");
                shared_stream.Write(imageWidth, 0, imageWidth.Length);
                shared_stream.Write(imageHeight, 0, imageHeight.Length);

				shared_stream.Dispose();
          	}

			// Release the mutex
			shared_mutex.ReleaseMutex();
		}
	}
        
    protected override void OnClosed(EventArgs e)
    {
    
    
    	shared_memory.Dispose();

        while(!shared_mutex.WaitOne(10))
       	{
    
    
            Console.WriteLine("Waiting mutex release");
            Thread.Sleep(100);
        }
        shared_mutex.ReleaseMutex();
        shared_mutex.Dispose();
    }
}

2. C++端代码

#define sharedMemoryName L"shared_memory"
#define sharedMemoryMutex L"shared_memory_mutex"

HANDLE 	m_shareMutex;

// Just a thread function
void SharedMemoryThread()
{
    
    
	// 1. Get shared memory mapping handle
	HANDLE hMapFile;
	while (1)
	{
    
    
		hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, NULL, sharedMemoryName);
		if (hMapFile == nullptr)
		{
    
    
			_cprintf("/* Waiting shared memory creation...... */\n");
		}
		else
		{
    
    
			break;
		}

		Sleep(1000);
	}
	_cprintf("/* Open shared memory successful! */ \n");

	// 2. Keep getting shared memory content
	while (1)
	{
    
    
		if (hMapFile)
		{
    
    
			// 2.1. Lock the shared memory for reading
			WaitForSingleObject(m_shareMutex, INFINITE);

			// 2.2  Try to bind the shared memory mapping
			LPVOID lpBase = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
			if (lpBase)
			{
    
    
				// 2.3 Get the memory content
				unsigned char* pMemory = (unsigned char*)lpBase;
				
				int imageWidth = 0, imageHeight = 0;
				memcpy(&imageWidth, pMemory, 4);
				memcpy(&imageHeight , pMemory + 4, 4);

				// 2.4 Unbind the shared memory mapping
				UnmapViewOfFile(pMemory);
			}

			// 2.5 Release the mutex
			ReleaseMutex(m_shareMutex);
		}
		else
		{
    
    
			_cprintf("/* Shared memory handle error */\n");
			break;
		}
	}
	
	// 3. Close the shared memory handle
	CloseHandle(hMapFile);
}

int main()
{
    
    
	m_shareMutex = CreateMutex(NULL, false, sharedMemoryMutex);
	std::thread sharedThread(SharedMemoryThread);
	sharedThread.join();
}

3. C# 端关键函数

/*
@mapName		: 共享内存名称。
@capacity		:共享内存大小。
*/
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public static System.IO.MemoryMappedFiles.MemoryMappedFile CreateOrOpen(string mapName, long capacity);
/*
@offset			: 写入位置距离共享内存起始位置的偏移量。
@size			: 内存流大小。
*/
public System.IO.MemoryMappedFiles.MemoryMappedViewStream CreateViewStream(long offset, long size);

4. C++端关键函数

/*
@dwDesiredAccess	: 访问权限。
					: FILE_MAP_ALL_ACCESS -- 最高访问权限; FILE_MAP_READ -- 只读或复制权限; FILE_MAP_WRITE -- 读/写权限。
@bInheritHandle		: 如果为TRUE, 则CreateProcess()函数创建的进程可以继承句柄。否则,无法继承句柄。
@lpName				: 共享内存名称。

@返回值				:成功则返回句柄,失败则返回 NULL。 
*/
HANDLE OpenFileMappingW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName);
/*
@hFileMappingObject		: 共享内存句柄。
@dwDesiredAccess		: 访问权限。参考OpenFileMappingW()中的dwDesiredAccess参数。
@dwFileOffsetHigh		: 视图开始位置的文件偏移量的高顺序 DWORD。
@dwFileOffsetLow		: 视图开始位置的文件偏移量的低顺序 DWORD。
@dwNumberOfBytesToMap	:映射到视图的文件映射的字节数。 所有字节必须位于 CreateFileMapping 指定的最大大小范围内。 如果此参数为 0 ,则映射将从指定的偏移量扩展到文件映射的末尾。

@返回值					:成功则返回映射视图的起始地址;失败则返回NULL。
*/
LPVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap
);
/*
@lpMutexAttributes	:指向安全属性的指针,一般为NULL即可。
@bInitialOwner		:初始化互斥对象的所有者。简单点说,如果是FALSE,则表示刚创建的这个mutex不属于任何线程,处于有信号的状态。那么这时候,无论哪个线程去lock,都可以直接返回,即lock成功,无需等待。如果是TRUE,则表示当前线程的mutex已经被当前线程lock了,此时另一个线程就无法lock了。
@lpName				:mutex名称,用于跨进程的加锁。

@返回值				:mutex的句柄。
*/
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);
/*
@hHandle			: mutex句柄
@dwMilliseconds		:为mutex设置的等待时长(毫秒)。如果在等待时长内获得mutex,则立刻返回;如果超出等待时长,也立刻返回。
					:如果 dwMilliseconds = 0,则不等待立刻返回;如果 dwMilliseconds = INFINITE,则持续等待,直到拿到mutex。

@返回值				:返回值为 WAIT_OBJECT_0 的话,则说明在等待时长内获得mutex;返回值为 WAIT_TIMEOUT 则说明等待超时。
*/
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

总结

  1. 注意,我是先打开C++进程,然后再启动C#进程,然后在C#进程中创建共享内存并写入,然后在C++进程中获取共享内存句柄然后再读取。代码都是基于这个流程来考虑的。
  2. 当C#进程关闭时,C++进程仍会持续去读取共享内存中的数据。因为虽然是C#进程创建的共享内存,可是管理却是操作系统。所以即使C#进程关闭了,操作系统检测到内存句柄仍在C++进程使用,所以就不会回收,所以C++进程仍能持续读到内存。直到C++进程也关闭句柄之后,操作系统才会回收该内存。
  3. 进程间通信,加锁是非常重要的。请注意加锁的时序和释放顺序。

猜你喜欢

转载自blog.csdn.net/A_water_/article/details/129715606
今日推荐