相关知识
1
cout<<szT[4096]<<endl;
输出的是szT的第4096个元素;
cout<<szT<<endl;
输出的是整个字符串的内容
2 读文件时
要保证程序里的文件名、字节名于文件中的文件名和字节名相一致;
存储位置要书写正确不能有空格
3 书写输入、输出时要标明是输入输出哪些数和哪些值;如下所示:
if(cchValueMax < retValueLength)
{
cout<< "retValueLength=" <<retValueLength<<endl;
system("pause");}
//return retValueLength;
else
{cout<< "cchValueMax="<<cchValueMax<<endl;
//return cchValueMax;
system("pause");}
}
4 调试错误的方法把某一部分换成数,然后进行调试;
用F9设断点,分步调试。
5 二进制数的赋值
//如果只有一个变量时:
BYTE data = 0x00;
//如果是多个变量时:
BYTE data[10];
data[0] = 0x00;
data[1] = 0x00;
...
data[9] = 0x00;
6 消息钩子:的处理过程
MsgHook.cpp 消息钩子的实现过程;
整个程序的实现过程:SSNAP.h所有的函数都在这里包含,然后通过SSNAP.h里的函数调用Core里的函数,在Core里实现函数。
如 PlugIn.h 。
7 编译时ssnver.h 文件变红,每一次编译时,更新版本号;
8 读程序时遇到的问题
nPathID = SSN_PATH_XXX
代表是什么路径的ID 代表
#define SSN_PATH_APP 1
//wParam 为aPlugin ID
#define SSN_PATH_CONFIG 2
#define SSN_PATH_PLUGIN 3
#define SSN_PATH_3RD 4
//语言路径
//wParam 为aPluginID
//lParam nLocaleId
#define SSN_PATH_LOCALE 5
代表不同的路径ID ,如SSN_PATH_PLUGIN为插件的路径ID, SSN_PATH_LOCALE为语言的路径ID.
NPlugInId:插件ID 是指设有什么插件生成的,则相应的文件后面有AAA_NPlugInId.ini ,显示相应的id号;
wParam、lParam默认值为NULL;
1 //wParam 为PluginID
//lParam nLocaleId 怎么看出来的?是文件中约定的。
2 __in_opt 代表什么?有什么不一样?选择性输入可以有可以没有。
在SsnEdit.cpp的SsnGetLocaleString 函数的使用;
9 VC 获取操作系统语言信息Language Sublanguage
http://blog.csdn.net/jtujtujtu/article/details/4563745
http://topic.csdn.net/t/20030610/04/1896544.html
VC 获取操作系统语言信息 Language Sublanguage
通过函数
GetSystemDefaultLCID();
GetSystemDefaultLanguageID();
可以获取系统的default language信息。
1. void GetSystemLanguage()
2. {
3. LCID lcid = GetSystemDefaultLCID();
4. // or GetSystemDefaultLanguageID();
5. // or GetUserDefaultLCID();
6. // or GetUserDefaultLanguageID();
7. if(lcid == 0x0409)
8. {
9. AfxMessageBox(L"English_US");
10. }
11.
12. if(lcid == 0x0404)
13. {
14. AfxMessageBox("中国繁体");
15. }
16. if(lcid == 0x0804)
17. {
18. AfxMessageBox("中国简体");
19. }
20. }
10 Windows C++ API函数大全
http://blog.csdn.net/danforn/article/details/1871042
AfxMessageBox 是MFC库提供的,提供了多种重载形式,而MessageBox是标准的windows Api.
估计AfxMessageBox最终还是调用了MessageBox
AfxMessageBox()函数在任何类里边都可以使用,而MessageBox()函数只能在CWnd类的继承类中使用。另外,AfxMessageBox()函数的参数没有MessageBox()函数的参数丰富,所以后者较前者灵活。
11 不能在类定义里给变量初始化。
12 定义一个类
classRWini
{
public:
RWini();
~RWini();
CORE_API INT DoSsnReadProfileString( __in INT nPlugInId,__in_opt LPTSTR lpszGroupName, __in LPTSTR lpszKey, __out_ecount(cchValueMax)LPTSTR lpszValue, __in size_t cchValueMax, __in_opt LPTSTR lpszDefaultValue );
CORE_API SSN_RETURN DoSsnWriteProfileString( __in INTnPlugInId, __in_opt LPTSTR lpszGroupName, __in LPTSTR lpszKey, __in LPTSTRlpszValue );
int insert(__in INTnPlugInId, __in_opt LPTSTR lpszGroupName, __in LPTSTR lpszKey,__out_ecount(cchValueMax) LPTSTR lpszValue);
public:
INT nPlugInId[MAX_PATH];
TCHAR lGName[MAX_PATH];
TCHAR lKName[MAX_PATH];
TCHAR lpszValue[MAX_PATH];
INT nCount;
};
RWini::RWini()
{
INT nCount = 0;
}
int RWini::insert(__in INT nPlugInId, __in_opt LPTSTRlpszGroupName, __in LPTSTR lpszKey, __out_ecount(cchValueMax) LPTSTR lpszValue)
{
if (nCount<MAX_PATH)
{
nPlugInId[nCount]=nPlugInId;
tstring lGName = lpszGroupName;
tstring lKName=lpszKey;
tstring lpszValue=lpszValue;
nCount++;
}
}
13 读写ini文件
CORE_APIINT DoSsnReadProfileString( __in INT nPlugInId, __in_opt LPTSTR lpszGroupName,__in LPTSTR lpszKey, __out_ecount(cchValueMax) LPTSTR lpszValue, __in size_tcchValueMax, __in_opt LPTSTR lpszDefaultValue )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
TCHAR szT[MAX_PATH];
GetPrivateProfileString(lpszGroupName,lpszKey,lpszDefaultValue, szT, cchValueMax, szPath);
//lpszGroupName 节名
//lpszKey 键名
//lpszDefaultValue 为空时的默认值
//szT 存放键值的指针变量,用于接收INI文件中键值(数据)的接收缓冲区
//cchValueMaxlpReturnedString的缓冲区大小
//filename INI文件的路径
unsigned intretValueLength=_tcslen(szT);
if(cchValueMax <retValueLength)
returnretValueLength;
else
return cchValueMax;
}
CORE_APISSN_RETURN DoSsnWriteProfileString( __in INT nPlugInId, __in_opt LPTSTRlpszGroupName, __in LPTSTR lpszKey, __in LPTSTR lpszValue )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
//存放的路径用nPlugInId来标识,如存放文件为A_nPlugInId.ini
BOOL ret=WritePrivateProfileString(lpszGroupName,lpszKey,lpszValue,szPath);
//lpszGroupName 节名
//lpszKey 键名
//lpszValue 键值,也就是数据
//filename INI文件的路径
if(ret==TRUE){
return SSN_OK;
}else{
return SSN_FALSE;
}
}
CORE_APIINT DoSsnReadProfileBinary( __in INT nPlugInId, __in_opt LPTSTR lpszGroupName,__in LPTSTR lpszKey, __out BYTE* pData, __in UINT nBytes )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
TCHAR szT[MAX_PATH];
GetPrivateProfileString(lpszGroupName,lpszKey, NULL, szT, MAX_PATH, szPath);
if (szT == NULL)
return 0;
UINT ShouldBytes =_tcslen(szT);
if(ShouldBytes>nBytes){
return ShouldBytes;
}else{
return nBytes;
}
}
CORE_APISSN_RETURN DoSsnWriteProfileBinary( __in INT nPlugInId, __in_opt LPTSTR lpszGroupName,__in LPTSTR lpszKey, __in BYTE* pData, __in UINT nBytes )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
// 二进制转换为字符串,并写出
LPTSTR lpsz = newTCHAR[nBytes*2+1];
UINT i;
for (i = 0; i < nBytes;i++)
{
lpsz[i*2] =(TCHAR)((pData[i] & 0x0F) + 'A'); //低字节
lpsz[i*2+1] =(TCHAR)(((pData[i] >> 4) & 0x0F) + 'A'); //高字节
}
lpsz[i*2] = 0;
BOOL ret=WritePrivateProfileString(lpszGroupName,lpszKey,lpsz,szPath);
if(ret==TRUE){
delete [] lpsz;
return SSN_OK;
}else{
delete [] lpsz;
return SSN_FALSE;
}
}
CORE_APIINT DoSsnReadProfileInt( __in INT nPlugInId, __in_opt LPTSTR lpszGroupName,__in LPTSTR lpszKey, __in_opt INT nDefault )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
INT retValueLength = 0;
retValueLength =GetPrivateProfileInt(lpszGroupName,lpszKey,nDefault,szPath);
//lpszGroupName 节名
//lpszKey 键名
//nDefault 如果没有找到指定的数据返回,则把个变量值赋给返回值
//nPath INI文件的路径
//读取整形值:(返回值为读到的整数)
return retValueLength;
}
CORE_APISSN_RETURN DoSsnWriteProfileInt( __in INT nPlugInId, __in_opt LPTSTRlpszGroupName, __in LPTSTR lpszKey, __in INT nValue )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
TCHAR szT[MAX_PATH];
_stprintf_s(szT,_countof(szT), _T("%d"), nValue);
BOOL ret=WritePrivateProfileString(lpszGroupName, lpszKey, szT,szPath);
if(ret==TRUE){
return SSN_OK;
}else{
return SSN_FALSE;
}
}
CORE_APIDOUBLE DoSsnReadProfileDouble( __in INT nPlugInId, __in_opt LPTSTRlpszGroupName, __in LPTSTR lpszKey, __in_opt DOUBLE dDefault )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
TCHAR d[MAX_PATH];
GetPrivateProfileString(lpszGroupName,lpszKey, NULL, d, 128, szPath);
return (d[0] != 0) ? _wtof(d): dDefault;
}
CORE_APISSN_RETURN DoSsnWriteProfileDouble( __in INT nPlugInId, __in_opt LPTSTRlpszGroupName, __in LPTSTR lpszKey, __in DOUBLE dValue )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_CONFIG,szPath, MAX_PATH, nPlugInId, NULL);
char szT[MAX_PATH];
int digits=100;
_gcvt_s(szT,MAX_PATH,dValue,digits);
BOOL ret=WritePrivateProfileString(lpszGroupName,lpszKey,SSN::CA2W(szT),szPath);
//lpszGroupName 节名
//lpszKey 键名
//szT 键值,也就是数据
//nPath INI文件的路径
if (ret == TRUE )
return SSN_OK;
else
return SSN_FALSE;
}
//TODO: 还没实现,临时拷贝default
CORE_APIINT DoSsnGetLocaleString( __in INT nPlugInId, __in_opt INT nLocaleId, __in_optLPCTSTR szGroupName, __in LPCTSTR szStringId, __out_ecount(cchStringMax) LPTSTRszString, __in size_t cchStringMax, __in LPCTSTR szDefaultString )
{
TCHAR szPath[MAX_PATH] = {0};
DoSsnGetPath(SSN_PATH_LOCALE,szPath, MAX_PATH, nPlugInId, nLocaleId);
GetPrivateProfileString(szGroupName,szStringId,szDefaultString, szString, cchStringMax, szPath);
if (0 == _tcscpy_s(szString,cchStringMax, szDefaultString))
{
return_tcslen(szString);
}
else
{
return 0;
}
}
14编译不了的,看有多少错误,直接改改就可以了。找相应有用的东西,是种能力;
检出哪里出错,点击new->code
15
#ifdef__AFXWIN_H__
之间是MFC框架中的函数
#endif
跳出之外不是MFC框架中的函数
16 迅速了解函数功能的方法:
在主函数上打断点;
17 怎样判断一个头文件是不是MFC框架中的函数;
查看安装目录 C:\IProgram Files\VC\atlmfc\include下的以afx结尾的都是MFC框架下的头文件;
18 寻找安装目录开始-》所有程序-》快捷键打开文件位置;
19
#ifndef _EVEDRI_H_ //如果没有define _EVEDRI_H_
#define _EVEDRI_H_ //那么就define _EVEDRI_H_
......
#endif //endif 用来结束头文件的定义就可以了
20 在前面声明过同名的该函数,只要在同文件中包含了,就是被定义的;
21 在不同类中声明的函数,放到一起,有可能说没有定义,则此时需要定义一个类的对象;形如
CIniSectionA a =newCIniSectionA ();new的是构造函数一个构造函数
22
private:
CIniSectionA(CIniFileA* pIniFile , const std::string&sSectionName );
CIniSectionA(const CIniSectionA& ); //No Copy
CIniSectionA&operator=(constCIniSectionA&); // No Copy
~CIniSectionA( );
前两个是构造函数,第三个是重载运算符,第四个是析构函数;
构造函数就是为类开辟一个内存空间
23 类及类指针的用法;
ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->SetValue(_T("Test3KeyValue"));
在CIniFileA中的函数内调用AddSection(),AddKey(),SetValue()函数;
应为AddSection()函数是CIniFileA类中的,所以可以直接调用;对于AddKey函数是CIniSectionA类中的,SetValue()函数是CIniKeyA类中的;
CIniSectionA*CIniFileA::AddSection( std::string sSection )
CIniKeyA*AddKey( std::string sKeyName );
因为AddSection()函数返回值是一个指向CIniSectionA类的指针,所以可以指向CIniSectionA类中的AddKey()函数,同样AddKey()函数的返回值是指向CIniKeyA类的指针,所以可以指向CIniKeyA类中的SetValue函数。
24调试时要把打断点的函数写在main函数里才能就行跳转调试;
25测试时在main函数函数里引用的函数的参数,会提示未定义,把相应参数直接换成常量即可。
如:ini.Readini(_T("Test5"),_T("Test5Key"),_T("Test5KeyValue") ,_T("C:\\temp\\testout.ini"));
26
F11进入函数,运行,当跳到断点处时,按F11,则进入断点处的函数;
F9打断点
F5是跳过一个断点,进入下一个断点;
F10是分步调试;
27
#ifdef _UNICODE
typedefwchar_tTCHAR;
#ifndef_T
#define_T(s) L##s
#endif
#ifndef_TSTR
#define_TSTR(s) L##s
#endif
#else
typedefwchar_tTCHAR;
#ifndef_T
#define_T(s) s
#endif
#ifndef_TSTR
#define_TSTR(s) s
#endif
#endif
#endif
如果是UNICODE的代码,则前面加L,不括号,如果不是UNICODE的代码,前面加_T(字符串) 。
28
#ifdef _UNICODE
#define tstring std::wstring
#else
#define tstring std::string;
#endif
的意思是如果是_UNICODE,则执行#define tstring std::wstring,否则执行#define tstring std::string;所以直接写tstring就行,会自动进行选择;
29
#ifdef _UNICODE
typedefstd::wstring tstring;
重复定义删掉或注释掉一个定义(错误提示一堆);
30 变量名的定义问题
CIniFile m_pIniFile;定义对象修改成CIniFile* m_pIniFile;
定义变量名时,如果定义的是指针,怎前面加p,如果不是指针,则不要加p,定义变量名是应该注意的细节。
31 判断头文件是不是MFC框架下的,选中头文件,在框上会显示来自mfc.
32 两种解决方法一种是用返回值,写函数的返回值,另一种是把
std::stringCIniFileA::Readini(std::string sSection,std::string sKeyName, std::string sValue , std::string lpszDefaultValue,const std::string& fileName)
sValue写城区地址即& sValue
33 不能把指针直接给LPCTSTR,必须采用形式进行拷贝strcpy
error C2440: '=' :cannot convert from 'std::wstring' to 'LPCTSTR'
34CORE_API SSN_RETURN DoSsnCoreInit()函数为程序最初调用的函数在core.cpp中,所以写测试函数是,把测试函数在DoSsnCoreInit()里被调用;
35 向虚拟机里复制文件时,无法复制,解决方法重启虚拟机
36 调试时把需要访问的文件放在虚拟机中如读ini文件时
37 指针跳到这个函数是越界了
errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
{
_CHAR *p;
size_t available;
/*validation section */
_VALIDATE_STRING(_DEST, _SIZE);
_VALIDATE_POINTER_RESET_STRING(_SRC, _DEST,_SIZE);
p = _DEST;
available = _SIZE;
while ((*p++ = *_SRC++) != 0 && --available> 0)
{
}
if (available == 0)
{
认真看每个参数代表的意思,
38 在虚拟机里运行的是,exe 文件,即蓝色的工程文件
39 空指针不能指向任何一个数;
40
可选择的可以是空,键名和默认值不用在前面判断
返回默认值时把默认值给函数返回值;
41 各个变量都要考虑到了。。。。
bool CIniFileW::Writeini (LPCWSTR sSection,LPCWSTR sKeyName,LPCWSTR sValue,LPWSTR fileName)
{
if((sSection==NULL)||(wcslen(sSection)==0))
{
sSection=L"NULLsSection";
}
if(sKeyName==NULL||(wcslen(sKeyName)==0))
{
returnfalse;
}
AddSection( sSection );
GetSection( sSection )->AddKey(sKeyName);
GetSection( sSection)->AddKey(sKeyName)->SetValue(sValue);
Save( fileName );
returntrue;
}
LPWSTRCIniFileW::Readini(LPCWSTR sSection,LPCWSTR sKeyName, LPWSTR sValue ,LPWSTR lpszDefaultValue,LPWSTRfileName, size_t cchValueMax)
{
if((sSection==NULL)||(wcslen(sSection)==0))
{
sSection=L"NULLsSection";
}
if((sKeyName==NULL)||(wcslen(sKeyName)==0))
{
return L"";
}
//如¨?果?为a空?或¨°长¡è度¨¨为a0 ,ê?则¨°给?他?一°?个?默?认¨?值¦Ì,ê?则¨°就¨ª不?用®?else语®?句?了¢?
std::wstring wfile=fileName;
boolret=Load( wfile,false);
if(ret==true)
{
CIniSectionW* pSection =GetSection(sSection );
if(pSection )
{
CIniKeyW* pKey =pSection->GetKey(sKeyName);
{
if(pKey)
{
wcscpy_s(sValue,cchValueMax,pKey->GetValue().c_str());
}
}
}
}
else
{
if((lpszDefaultValue!=NULL)&&(wcslen(lpszDefaultValue)!=0))
{
wcscpy_s(sValue,cchValueMax,lpszDefaultValue);
}
else
{
wcscpy_s(sValue,cchValueMax,L"");
}
}
returnsValue;
}
42 如果放在指针上是 dcdc或fff 说明它没有返回值
43
CsaveFileName*pFileName=m_FileName.GetFileName(lpszIniFileName);
这个定义不用是全局变量在ReadProfileString函数里写;
CsaveFileName* pFileName
44
CsaveFileNameW 类和CIniFileW类是两个不相关的类,现在CProfile类函数CProfile::ReadProfileString里实现 把 CsaveFileName类型返回的指针指向CIniFileW类中的函数 Readini,实现过程 是这样的
INTCProfile::ReadProfileString
{
CsaveFileName*pFileName=m_FileName.GetFileName(lpszIniFileName);
tstring reValue;
reValue =pFileName->PIniFile->Readini(lpszGroupName,lpszKey,lpszValue,lpszDefaultValue,lpszIniFileName,cchValueMax);
return reValue.length();
}
其中 PIniFile 是在CsaveFileNameW类里定义的CIniFile* PIniFile; 指向CIniFile类的指针,为什么在
在调用的过程中 提示 表达式必须包含指向类的指针类型的错误;
在两个不同的类之间进行类函数的调用过程中 除了申明一个指针指向对方 ,还要写别的吗?
在
CsaveFileNameA::CsaveFileNameA(CFileNameA* pIniFile , const std::string&sFileName ):m_pIniFile(pIniFile) , m_sFileName(sFileName)
{
#ifdef _CINIFILE_DEBUG
std::cout << "CsaveFileNameA::CsaveFileNameA()"<< std::endl;
#endif
PIniFile=newCIniFileA();
}
CsaveFileNameA::~CsaveFileNameA()
{
#ifdef _CINIFILE_DEBUG
std::cout << "CsaveFileNameA::~CsaveFileNameA()"<< std::endl;
#endif
if(PIniFile)
{
deletePIniFile;
PIniFile = NULL;
}
}
函数里添加 相应的开辟类、释放类等内容;
不同的类是相互引用其函数,在声明指向对方类的指针后,还要在其函数的构造函数和析构函数或开始函数和结束函数中创建对方类和释放刚创建的类。
CsaveFileNameW::CsaveFileNameW(CFileNameW* pIniFile , const std::wstring&sFileName ):m_pIniFile(pIniFile) , m_sFileName(sFileName)
{
#ifdef _CINIFILE_DEBUG
std::wcout <<"CsaveFileNameW::CsaveFileNameW()"<<std::endl;
#endif
PIniFile = new CIniFileW();
}
CsaveFileNameW::~CsaveFileNameW()
{
#ifdef _CINIFILE_DEBUG
std::wcout <<"CsaveFileNameW::~CsaveFileNameW()"<<std::endl;
#endif
if (PIniFile)
{
delete PIniFile;
PIniFile = NULL;
}
}
45一直打不开文件 由于文件的位置 和文件名不对;
46可以读写字符串不能读写汉字
不读写中文
std::locale sys_loc("");
std::locale::global(sys_loc); //如果不设置为全局, wostream 会使用默认的, 则不能输出中文
47 类的实现过程
.h 文件创建一个类 在类内声明 构造函数或析构函数 如
class CIniFileW;
class CFileNameW
{
public:
static const wchar_t* const LF;
public:
CFileNameW();
~CFileNameW();
classCsaveFileNameW
{
friend class CFileNameW;
#ifdef _WIN32
// Added forversions earlier than VS2008
#if defined(_MSC_VER)&& (_MSC_VER <= 1400)
friend struct ci_less_w;
#endif
#endif
private:
CsaveFileNameW(CFileNameW* pIniFile , const std::wstring& sFileName );
CsaveFileNameW( constCsaveFileNameW& ); // No Copy
CsaveFileNameW& operator=(constCsaveFileNameW&); // No Copy
~CsaveFileNameW();
在.cpp文件中实现这些构造函数和析构函数,
CFileNameW::CFileNameW()
{
#ifdef _CINIFILE_DEBUG
std::wcout << L"CFileNameW::CFileNameW()"<< std::endl;
#endif
}
CFileNameW::~CFileNameW()
{
#ifdef _CINIFILE_DEBUG
std::wcout << L"CFileNameW::~CFileNameW()"<< std::endl;
#endif
RemoveAllFileNames();
}
CsaveFileNameW::CsaveFileNameW(CFileNameW* pIniFile , const std::wstring&sFileName ):m_pIniFile(pIniFile) , m_sFileName(sFileName)
{
#ifdef _CINIFILE_DEBUG
std::wcout << "CsaveFileNameW::CsaveFileNameW()"<< std::endl;
#endif
m_pFile = newCIniFileW();
}
CsaveFileNameW::~CsaveFileNameW()
{
#ifdef _CINIFILE_DEBUG
std::wcout << "CsaveFileNameW::~CsaveFileNameW()"<< std::endl;
#endif
if (m_pFile)
{
deletem_pFile;
m_pFile = NULL;
}
}
实现过程中,构造函数是用来初始化和分配内存的,析构函数则是用来释放内存的。
48 写代码是可能存在的问题
//下面三个函数不需要对外公开可以设成private 或者protect []
//大小写不规范, 最好是m_FileNames []
//命名不规范,建议m_pIniFile []
//没有地方释放此处分配的内存, 建议在CFileNameW::~CFileNameW()中调用RemoveAllFileNames释放 []