天天看点

C++共享内存封装类

头文件

/******************************************************************
++  File Name :   FFMClass.h
++  Description: 共享內存類
---------------------------------------------------------------
++  Author:  Fei ZhaoDong
++  Create time: 2004/3/25 上午 10:00:00
++  Version:     1.1
++  Modifier:  
++  Activities:    
++  Update List: 2004/3/30 下午 02:59:45
*******************************************************************/
// FFMClass.h: interface for the CFFMClass.
//
//////////////////////////////////////////////////////////////////////
#ifndef FZD_FFMCLASS_H
#define FZD_FFMCLASS_H
//#include <Winbase.h>
#include <aclapi.h>

//////////////////////////////////////////////////////////////////////
// Macro definition
// 以下為共享內存定義
#define DEFAULT_FILENAME NULL    // 默認的文件名
#define DEFAULT_MAPNAME  "Local//_FZD_MAP_"   // 默認的共享內存名
#define DEFAULT_MAPSIZE  (0xFFFF + 1)  // 默認的共享內存大小
const DWORD NETRGUSER_CFM_CODE = 0x1211DBFF; // 校驗碼, 用於命令數據
const DWORD NETRGUSER_CMD_NONE = 0;   // 初始化指令碼, 無指令
// 以下為錯誤碼定義
#define ERROR_LEN    256    // 錯誤描述長度
#define ERROR_INVALID_CMDCODE 0xE00001FF  // 已經存在完全一樣的共享內存
#define ERROR_NO_MAPFILE             0xE00002FF  // 未分配共享內存文件
#define ERROR_INVALID_CFMCODE 0xE00003FF  // 校驗碼不匹配
//////////////////////////////////////////////////////////////////////
// 內存文件格式定義
#pragma pack(1)
// 用於存儲命令數據的內存文件格式
typedef struct _tagDATA_HEADER
{
	DWORD dwConfirmCode; // 校驗碼
	DWORD nCommandCode;  // 指令識別碼
	DWORD dwDataSize;  // 數據的大小
	BYTE  dwReserved[19]; // 保留
	BYTE  bInfo[1];   // 數據起始地址
	_tagDATA_HEADER()
	{
		dwConfirmCode = NETRGUSER_CFM_CODE;
		nCommandCode = NETRGUSER_CMD_NONE;
		dwDataSize = 0;
		ZeroMemory(dwReserved, 19);
		ZeroMemory(bInfo, 1);
	}
}DATA_HEADER, *LPDATA_HEADER;
typedef DWORD (WINAPI *PSetEntriesInAcl)(ULONG, PEXPLICIT_ACCESS, PACL, PACL*);
// 用於存儲應答數據的共享內存文件格式 (暫不用)
typedef struct _tagANS_HEADER
{
}ANS_HEADER, *LPANS_HEADER;

#pragma pack()

//////////////////////////////////////////////////////////////////////
// 類定義,共享內存服務端
class CFFMServer  
{
public:
	CFFMServer();
	virtual ~CFFMServer();
	CFFMServer(char *szFileName, char *szMapName, DWORD dwSize);
protected:
	PSetEntriesInAcl m_fnpSetEntriesInAcl;
	HANDLE m_hFile;   // 映射文件句柄
	HANDLE m_hFileMap;   // 內存文件句柄
	LPVOID m_lpFileMapBuffer; // 緩衝區指針
	char *m_pFileName;  // 映射文件名
	char *m_pMapName;  // 內存文件名
	DWORD m_dwSize;   // 緩衝區大小
	BOOL m_bCreateFlag;  // 是否已創建共享內存
	DWORD   m_dwLastError;  // 錯誤代碼
private:
	void _Init();    // 初始化參數
	BOOL _IsWinNTLater();  // 判斷當前操作系統
public:
	BOOL Create(char *szFileName = DEFAULT_FILENAME,
		char *szMapName = DEFAULT_MAPNAME,
		DWORD dwSize = DEFAULT_MAPSIZE); // 新建共享內存
	LPVOID GetBuffer();      // 獲取內存文件指針
	DWORD GetSize();      // 獲取內存文件大小
	void Destory();       // 銷毀已有的共享內存
	BOOL WriteCmdData(      // 寫入命令數據
		DWORD nCommandCode,
		DWORD dwDataSize,
		const LPVOID pBuf);
};
//////////////////////////////////////////////////////////////////////
// 類定義,共享內存客戶端
class CFFMClient  
{
public:
	CFFMClient();
	virtual ~CFFMClient();
	CFFMClient(DWORD dwAccess, char *szMapName, DWORD dwSize);
protected:
	HANDLE m_hFileMap;   // 內存文件句柄
	LPVOID m_lpFileMapBuffer; // 內存文件指針
	char *m_pMapName;  // 內存文件名
	DWORD m_dwSize;   // 緩衝區大小
	BOOL m_bOpenFlag;  // 是否已經打開了一個內存文件
	DWORD   m_dwLastError;  // 錯誤代碼
private:
	void _Init();    // 初始化參數
public:
	BOOL Open(DWORD dwAccess = FILE_MAP_READ | FILE_MAP_WRITE,
		char *szMapName = DEFAULT_MAPNAME,
		DWORD dwSize = 0);      // 打開一個內存文件
	LPVOID GetBuffer();       // 獲取當前內存文件的指針
	void Destory();        // 關閉當前對內存文件的訪問
	DWORD GetSize();      // 獲取內存文件大小
	BOOL GetCmdDataSize(DWORD *pDataSize);  // 讀取命令數據大小
	BOOL ReadCmdData(       // 讀取命令數據
		DWORD dwCommandCode,
		DWORD dwBufSize,
		LPVOID pOutBuf);
	BOOL WriteCmdData(      // 寫入命令數據
		DWORD memSize, 
		DWORD nCommandCode,
		DWORD dwDataSize,
		const LPVOID pBuf);
};
#endif // FZD_FFMCLASS_H
           

实现文件

/******************************************************************
++  File Name : FFMClass.cpp
++  Description: 共享內存類
---------------------------------------------------------------
++  Author:  Fei ZhaoDong
++  Create time: 2004/3/25 上午 10:00:00
++  Version:     1.0
++  Modifier:  
++   Activities:    
++  Update List: 2004/3/29 下午 02:59:45
*******************************************************************/
// FFMClass.cpp: implementation of the CFFMClass.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "FFMClass.h"
//#include <aclapi.h>
//#include <windows.h>
//#include <aclapi.h>
//#include <lmerr.h>
//
//#include <stdio.h>
//////////////////////////////////////////////////////////////////////
// CFFMServer
//////////////////////////////////////////////////////////////////////
CFFMServer::CFFMServer()
{
	m_dwLastError = 0;
	m_fnpSetEntriesInAcl = NULL;
	_Init();
}
CFFMServer::~CFFMServer()
{
	Destory();
}
CFFMServer::CFFMServer(char *szFileName, char *szMapName, DWORD dwSize)
{
	// 以自定義設置創建共享內存塊
	_Init();
	Create(szFileName, szMapName, dwSize);
}
// 初始化各個參數
void CFFMServer::_Init()
{
	m_hFile = NULL;
	m_hFileMap = NULL;
	m_lpFileMapBuffer = NULL;
	m_pFileName = NULL;
	m_pMapName = NULL;
	m_dwSize = 0;
	m_bCreateFlag = FALSE;
}
// 判斷是否NT4.0以上操作系統
BOOL CFFMServer::_IsWinNTLater()
{
	OSVERSIONINFO Ver;
	BOOL bAbleVersion = FALSE;
	Ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if (GetVersionEx(&Ver))
	{
		if (Ver.dwPlatformId == VER_PLATFORM_WIN32_NT
			&& Ver.dwMajorVersion >= 4)
		{
			bAbleVersion = TRUE;
		}
	}
	else
	{
		m_dwLastError = GetLastError();
	}
	return bAbleVersion;
}
// 釋放當前共享內存,並重新初始化參數
void CFFMServer::Destory()
{
	if (m_lpFileMapBuffer != NULL)
	{
		UnmapViewOfFile(m_lpFileMapBuffer);
		m_lpFileMapBuffer = NULL;
	}
	if (m_hFileMap != NULL)
	{
		CloseHandle(m_hFileMap);
		m_hFileMap = NULL;
	}
	if (m_hFile && m_hFile != INVALID_HANDLE_VALUE)
	{
		CloseHandle(m_hFile);
		m_hFile = NULL;
	}
	if (m_pFileName != NULL)
	{
		free(m_pFileName);
		m_pFileName = NULL;
	}
	if (m_pMapName != NULL)
	{
		free(m_pMapName);
		m_pMapName = NULL;
	}
	_Init();
}
static void FreeSidEx(PSID oSID)
{
	try
	{
		FreeSid(oSID);
	}
	catch(...)
	{
	}
}
// 創建共享內存塊
BOOL CFFMServer::Create(char *szFileName, char *szMapName, DWORD dwSize)
{
	// 釋放已有的共享內存塊
	if (m_bCreateFlag)
	{
		Destory();
	}
	// 拷貝各個參數
	if (szFileName)
	{
		m_pFileName = _strdup(szFileName);
	}
	if (szMapName)
	{
		m_pMapName = _strdup(szMapName);
	}
	else
	{
		m_pMapName = _strdup(DEFAULT_MAPNAME);
	}
	if (dwSize > 0)
	{
		m_dwSize = dwSize;
	}
	else
	{
		m_dwSize = DEFAULT_MAPSIZE;
	}
	// 以下創建共享內存
	if (m_pFileName)
	{
		m_hFile = CreateFile(
			m_pFileName,
			GENERIC_READ|GENERIC_WRITE,
			FILE_SHARE_READ|FILE_SHARE_WRITE,
			NULL,
			OPEN_ALWAYS,
			FILE_ATTRIBUTE_NORMAL,
			NULL
			);
	}
	else
	{
		// 默認情況下,在頁面文件中創建共享內存
		m_hFile = INVALID_HANDLE_VALUE;
	}
	if (_IsWinNTLater())
	{
		// Set DACL
		const int NUM_ACES = 2;   // number if ACEs int DACL
		// evryone -- read
		// creator -- full access
		// 初始化參數
		PSID pEveryoneSID        = NULL; // everyone群組SID
		PSID pCreatorSID   = NULL; // creator群組SID
		PACL pFileMapACL         = NULL; // 準備新內存文件的DACL
		PSECURITY_DESCRIPTOR  pSD = NULL; // 內存文件的SD
		SECURITY_ATTRIBUTES   saFileMap; // 內存文件的SA
		EXPLICIT_ACCESS    ea[NUM_ACES]; // 外部訪問結構 
		BOOL bHasErr   = FALSE; // 返回值
		// 以下創建SID
		SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
		SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
		// Evryone
		if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID,
			0, 0, 0, 0, 0, 0, 0, &pEveryoneSID))
		{
			bHasErr = TRUE;
			goto Finish;
		}
		// Creator
		if (!AllocateAndInitializeSid(&SIDAuthCreator, 1, SECURITY_CREATOR_OWNER_RID,
			0, 0, 0, 0, 0, 0, 0, &pCreatorSID))
		{
			bHasErr = TRUE;
			goto Finish;
		}
		// 填充ACE
		ZeroMemory(&ea, NUM_ACES * sizeof(EXPLICIT_ACCESS));
		// S-1-1-0 evryone, 唯讀權限
		ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
		ea[0].grfAccessMode = SET_ACCESS;
		ea[0].grfInheritance= NO_INHERITANCE;
		ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
		ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
		ea[0].Trustee.ptstrName  = (LPTSTR) pEveryoneSID;
		// S-1-3-0 creator owner, 完全權限
		ea[1].grfAccessPermissions = STANDARD_RIGHTS_ALL;
		ea[1].grfAccessMode = SET_ACCESS;
		ea[1].grfInheritance= NO_INHERITANCE;
		ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
		ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
		ea[1].Trustee.ptstrName  = (LPTSTR) pCreatorSID;
		// 創建並填充ACL
		if(NULL == m_fnpSetEntriesInAcl)
		{
			HINSTANCE hLib = ::LoadLibrary("Advapi32.dll");
			if(NULL != hLib)
			{
				m_fnpSetEntriesInAcl = (PSetEntriesInAcl)GetProcAddress(hLib,"SetEntriesInAclA");
				::FreeLibrary(hLib);
				hLib = NULL;
			}
		}
		if (ERROR_SUCCESS != m_fnpSetEntriesInAcl(NUM_ACES, ea, NULL, &pFileMapACL))
		{
			bHasErr = TRUE;
			goto Finish;
		}
		// 創建並初始化SD
		pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); 
		if (NULL == pSD)
		{
			bHasErr = TRUE;
			goto Finish;
		} 
		if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) 
		{
			bHasErr = TRUE;
			goto Finish;
		}
		// 添加ACL到SD中去
		if (!SetSecurityDescriptorDacl(pSD,
			TRUE,     // fDaclPresent flag   
			pFileMapACL, 
			FALSE))   // not a default DACL 
		{
			bHasErr = TRUE;
			goto Finish;
		}
		// 設置SA
		saFileMap.nLength = sizeof(SECURITY_ATTRIBUTES);
		saFileMap.bInheritHandle = FALSE;
		saFileMap.lpSecurityDescriptor = pSD;
		// 創建共享內存文件
		if (m_hFile != NULL)
		{
			m_hFileMap = CreateFileMapping(
				m_hFile,
				&saFileMap,
				PAGE_READWRITE,
				0,
				m_dwSize,
				m_pMapName);
			if (NULL == m_hFileMap)
			{
				m_hFileMap = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
					TRUE, m_pMapName);
				if (NULL == m_hFileMap)
				{
					m_dwLastError = GetLastError(); 
					Destory();
					goto Finish;
				}
			}
		}
Finish:
		//if (pSD != NULL)
		//{
		//	LocalFree(pSD);
		//}		
		pSD = NULL;
		if (pFileMapACL != NULL)
		{
			LocalFree(pFileMapACL);
			pFileMapACL = NULL;
		}
		if (pEveryoneSID != NULL)
		{
			FreeSidEx(pEveryoneSID);
			pEveryoneSID = NULL;
		}
		if (pCreatorSID != NULL)
		{
			FreeSidEx(pCreatorSID);
			pCreatorSID = NULL;
		}
		if (bHasErr) 
		{
			m_dwLastError = GetLastError();
			return FALSE;
		}
	}
	else
	{
		// 創建共享內存文件
		if (m_hFile)
		{
			m_hFileMap = CreateFileMapping(
				m_hFile,
				NULL,
				PAGE_READWRITE,
				0,
				m_dwSize,
				m_pMapName);
			if (NULL == m_hFileMap)
			{
				m_dwLastError = GetLastError();
				Destory();
				return FALSE;
			}
		}
	}
	// 映射文件指針到用戶
	if (m_hFileMap)
	{
		m_lpFileMapBuffer = MapViewOfFile(
			m_hFileMap,
			FILE_MAP_ALL_ACCESS,
			0,
			0,
			m_dwSize);
		if (NULL == m_lpFileMapBuffer)
		{
			m_dwLastError = GetLastError();
			Destory();
			return FALSE;
		}
	}
	m_bCreateFlag = TRUE;
	return TRUE;
}
// 獲取內存文件指針
LPVOID CFFMServer::GetBuffer()
{
	return (m_lpFileMapBuffer)?(m_lpFileMapBuffer):(NULL);
}
// 獲取內存文件大小
DWORD CFFMServer::GetSize()
{
	return m_dwSize;
}
BOOL CFFMServer::WriteCmdData(DWORD nCommandCode, DWORD dwDataSize, const LPVOID pBuf)
{
	// 檢驗數據的合理性
	if (NULL == GetBuffer())
	{
		m_dwLastError = ERROR_NO_MAPFILE;
		SetLastError(ERROR_NO_MAPFILE);
		return FALSE;
	}
	if (NETRGUSER_CMD_NONE == nCommandCode)
	{
		m_dwLastError = ERROR_INVALID_CMDCODE;
		SetLastError(ERROR_INVALID_CMDCODE);
		return FALSE;
	}
	if (dwDataSize > 0 && pBuf == NULL)
	{
		m_dwLastError = ERROR_INVALID_USER_BUFFER;
		SetLastError(ERROR_INVALID_USER_BUFFER);
		return FALSE;
	}
	if (dwDataSize + sizeof(DATA_HEADER) > GetSize())
	{
		m_dwLastError = ERROR_BUFFER_OVERFLOW;
		SetLastError(ERROR_BUFFER_OVERFLOW);
		return FALSE;
	}
	// 填寫數據結構
	// 文件頭
	DATA_HEADER dataHeader;
	dataHeader.nCommandCode = nCommandCode;
	dataHeader.dwDataSize = dwDataSize;
	ZeroMemory(GetBuffer(), GetSize());
	memcpy(GetBuffer(), &dataHeader, sizeof(DATA_HEADER));
	// 數據塊
	LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
	memcpy(pData->bInfo, pBuf, dwDataSize);
	return TRUE;
}
//////////////////////////////////////////////////////////////////////
// CFFMClient
//////////////////////////////////////////////////////////////////////
CFFMClient::CFFMClient()
{
	m_dwLastError = 0;
	_Init();
}
CFFMClient::~CFFMClient()
{
	Destory();
}
CFFMClient::CFFMClient(DWORD dwAccess, char *szMapName, DWORD dwSize)
{
	_Init();
	if (!Open(dwAccess, szMapName, dwSize))
	{
		Destory();
	}
}
// 初始化參數
void CFFMClient::_Init()
{
	m_hFileMap = NULL;
	m_lpFileMapBuffer = NULL;
	m_pMapName = NULL;
	m_bOpenFlag = FALSE;
}
// 關閉當前對內存文件的訪問
void CFFMClient::Destory()
{
	if (m_lpFileMapBuffer)
	{
		UnmapViewOfFile(m_lpFileMapBuffer);
		m_lpFileMapBuffer = NULL;
	}
	if (m_hFileMap)
	{
		CloseHandle(m_hFileMap);
		m_hFileMap = NULL;
	}
	if (m_pMapName)
	{
		free(m_pMapName);
		m_pMapName = NULL;
	}
	_Init();
}
// 打開一個內存文件
BOOL CFFMClient::Open(DWORD dwAccess, char *szMapName, DWORD dwSize)
{
	if (m_bOpenFlag)
	{
		Destory();
	}
	if (szMapName != NULL)
	{
		m_pMapName = _strdup(szMapName);
	}
	else
	{
		m_pMapName = _strdup(DEFAULT_MAPNAME);
	}
	m_hFileMap = OpenFileMapping(dwAccess, TRUE, m_pMapName);
	if (NULL == m_hFileMap)
	{
		m_dwLastError = GetLastError();
		Destory();
		return FALSE;
	}
	m_lpFileMapBuffer = MapViewOfFile(m_hFileMap, dwAccess, 0, 0, dwSize);
	if (NULL == m_lpFileMapBuffer)
	{
		m_dwLastError = GetLastError();
		Destory();
		return FALSE;
	}
	m_bOpenFlag = TRUE;
	return TRUE;
}
// 獲取當前內存文件的指針
LPVOID CFFMClient::GetBuffer()
{
	return (m_lpFileMapBuffer)?(m_lpFileMapBuffer):(NULL);
}
// 讀取命令數據大小
BOOL CFFMClient::GetCmdDataSize(DWORD *pDataSize)
{
	_ASSERT(pDataSize != NULL);
	*pDataSize = 0;
	LPDATA_HEADER pHeader = (LPDATA_HEADER)GetBuffer();
	if (NULL == pHeader)
	{
		m_dwLastError = ERROR_NO_MAPFILE;
		SetLastError(ERROR_NO_MAPFILE);
		return FALSE;
	}
	if (NETRGUSER_CFM_CODE != pHeader->dwConfirmCode)
	{
		m_dwLastError = ERROR_INVALID_CFMCODE;
		SetLastError(ERROR_INVALID_CFMCODE);
		return FALSE;
	}
	if (NETRGUSER_CMD_NONE == pHeader->nCommandCode)
	{
		m_dwLastError = ERROR_INVALID_CMDCODE;
		SetLastError(ERROR_INVALID_CMDCODE);
		return FALSE;
	}
	*pDataSize = pHeader->dwDataSize;
	return TRUE;
}
// 讀取命令數據
BOOL CFFMClient::ReadCmdData(DWORD dwCommandCode, DWORD dwBufSize, LPVOID pOutBuf)
{
	_ASSERT (pOutBuf != NULL);
	LPDATA_HEADER pHeader = (LPDATA_HEADER)GetBuffer();
	if (NULL == pHeader)
	{
		m_dwLastError = ERROR_NO_MAPFILE;
		SetLastError(ERROR_NO_MAPFILE);
		return FALSE;
	}
	if (NETRGUSER_CFM_CODE != pHeader->dwConfirmCode)
	{
		m_dwLastError = ERROR_INVALID_CFMCODE;
		SetLastError(ERROR_INVALID_CFMCODE);
		return FALSE;
	}
	if (NETRGUSER_CMD_NONE == pHeader->nCommandCode)
	{
		m_dwLastError = ERROR_INVALID_CMDCODE;
		SetLastError(ERROR_INVALID_CMDCODE);
		return FALSE;
	}
	if (pHeader->dwDataSize > dwBufSize)
	{
		m_dwLastError = ERROR_BUFFER_OVERFLOW;
		SetLastError(ERROR_BUFFER_OVERFLOW);
		return FALSE;
	}
	if (pHeader->nCommandCode != dwCommandCode)
	{
		m_dwLastError = ERROR_INVALID_CMDCODE;
		SetLastError(ERROR_INVALID_CMDCODE);
		return FALSE;
	}
	ZeroMemory(pOutBuf, dwBufSize);
	// 拷貝數據到緩衝區
	memcpy(pOutBuf, pHeader->bInfo, pHeader->dwDataSize);
	return TRUE;
}
BOOL CFFMClient::WriteCmdData(DWORD memSize, DWORD nCommandCode, DWORD dwDataSize, const LPVOID pBuf)
{
	if(!memSize)
		memSize = DEFAULT_MAPSIZE;	
	m_dwSize = memSize;
	// 檢驗數據的合理性
	if (NULL == GetBuffer())
	{
		m_dwLastError = ERROR_NO_MAPFILE;
		SetLastError(ERROR_NO_MAPFILE);
		return FALSE;
	}
	if (NETRGUSER_CMD_NONE == nCommandCode)
	{
		m_dwLastError = ERROR_INVALID_CMDCODE;
		SetLastError(ERROR_INVALID_CMDCODE);
		return FALSE;
	}
	if (dwDataSize > 0 && pBuf == NULL)
	{
		m_dwLastError = ERROR_INVALID_USER_BUFFER;
		SetLastError(ERROR_INVALID_USER_BUFFER);
		return FALSE;
	}
	if (dwDataSize + sizeof(DATA_HEADER) > GetSize())
	{
		m_dwLastError = ERROR_BUFFER_OVERFLOW;
		SetLastError(ERROR_BUFFER_OVERFLOW);
		return FALSE;
	}
	// 填寫數據結構
	// 文件頭
	DATA_HEADER dataHeader;
	dataHeader.nCommandCode = nCommandCode;
	dataHeader.dwDataSize = dwDataSize;
	ZeroMemory(GetBuffer(), GetSize());
	memcpy(GetBuffer(), &dataHeader, sizeof(DATA_HEADER));
	// 數據塊
	LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
	memcpy(pData->bInfo, pBuf, dwDataSize);
	return TRUE;
}
// 獲取內存文件大小
DWORD CFFMClient::GetSize()
{
	return m_dwSize;
}