前些日子感觉得练习一下VC了,所以就有想,平时发短信那么累,手机键盘又不好用,而我又有数据线,为什么不可以用电脑直接发送短信呢?想法一出来,就开始找资料开始行动吧!
由于程序涉及的方面很多,因此只讲关键的主要的部分。
首先,得了解手机和电脑之间是通过什么通讯的,我的手机是有红外接口,电脑上接一个红外适配器,就可以与手机连接了,而Windows是把红外设备当作一个串口来看待的,所以关键就是在于如何用程序来控制COM端口来发送和接收数据。在网上找了很多资料然后就开始编写代码:
列举出系统中的所有的串口:这个需要操作注册表来实现,代码如下:
void CSendMsgDlg::GetAllCom()
{ HKEY hKey; LONG ret; OSVERSIONINFO osvi; BOOL bOsVersionInfoEx; char keyinfo[100],comm_name[200],ValueName[200]; int i; DWORD sType,Reserved,cbData,cbValueName;ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); memset(keyinfo,0,100); strcpy(keyinfo,"HARDWARE\\DEVICEMAP\\SERIALCOMM"); i=0; sType=REG_SZ;Reserved=0; bOsVersionInfoEx =GetVersionEx(&osvi); ret=RegOpenKeyEx(HKEY_LOCAL_MACHINE,keyinfo,0,KEY_ALL_ACCESS,&hKey); if (ret==ERROR_SUCCESS){ if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) { do { cbData=200;cbValueName=200; memset(comm_name,0,200); memset(ValueName,0,200); ret=RegEnumValue(hKey,i,ValueName,&cbValueName,NULL,&sType,(LPBYTE)comm_name,&cbData); if (ret==ERROR_SUCCESS) { //m_list.Add(comm_name); m_comm.AddString(comm_name); i++; } }while (ret==ERROR_SUCCESS); } } RegCloseKey(hKey);}得到所有的串口了,现在就应该打开串口了,一般来说计算机自己有两个串口,而红外线的标志一般是COM3。
打开串口的代码:
void CSendMsgDlg::OnButton1()
{ char str[100]; DWORD dwThreadID; memset(str,0,100); int sel = m_comm.GetCurSel(); if(sel == -1) { AfxMessageBox("对不起,请选择一个接口!"); return; } m_comm.GetLBText(sel,str); m_hCom = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);if(m_hCom == INVALID_HANDLE_VALUE)
{ AfxMessageBox("对不起,连接失败!"); return; }ASSERT(m_hCom!=INVALID_HANDLE_VALUE);
SetCommMask(m_hCom, EV_RXCHAR|EV_TXEMPTY );//设置事件驱动的类型 SetupComm( m_hCom, 1024,512) ; //设置输入、输出缓冲区的大小 PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ); //清干净输入、输出缓冲区 COMMTIMEOUTS CommTimeOuts ; //定义超时结构,并填写该结构CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant = 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 0; CommTimeOuts.WriteTotalTimeoutConstant = 5000; SetCommTimeouts( m_hCom, &CommTimeOuts ) ;//设置读写操作所允许的超时 DCB dcb; GetCommState(m_hCom, &dcb ) ; //读串口原来的参数设置 dcb.BaudRate =9600; dcb.ByteSize =8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT ; dcb.fBinary = TRUE ; dcb.fParity = FALSE; SetCommState(m_hCom, &dcb ) ; //串口参数配置 memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) ); memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) );m_OverlappedRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
m_OverlappedWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); m_hExit = CreateEvent(NULL,NULL,FALSE,NULL);hCommWatchThread = CreateThread( (LPSECURITY_ATTRIBUTES) NULL,0,(LPTHREAD_START_ROUTINE)CommWatchProc,this,0, &dwThreadID );
if(hCommWatchThread == NULL)
{ AfxMessageBox("对不起,连接失败!"); return; }
ASSERT(hCommWatchThread!=NULL);
m_bConnected = true;
m_type = 0;
SendData("AT\r\n",strlen("AT\r\n"));
PurgeComm(m_hCom, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);m_conn.EnableWindow(false);
m_view.EnableWindow(true); m_send.EnableWindow(true); }由于在CreateFile里面设置了一个参数“FILE_FLAG_OVERLAPPED”,所以与串口要按照异步的方式进行通讯,所以启动一个线程专门作为接收数据的线程:
UINT CSendMsgDlg::CommWatchProc(LPARAM parm)
{CSendMsgDlg * dlg = (CSendMsgDlg *)parm;
HANDLE m_hComDev = dlg->m_hCom; DWORD dwBytesRead; char buffer[8000]; DWORD ret;while(true)
{ memset(buffer,0,8000); int r = dlg->ReadData(buffer,8000); // TRACE("%s",buffer); if(strlen(buffer) != 0) dlg->ProcessData(buffer); Sleep(1000); } return 0;}下面是读写COM口的函数:
int CSendMsgDlg::SendData(char *buffer, DWORD dwBytesWritten)
{ BOOL bWriteStat; DWORD dwBytesRead; bWriteStat = WriteFile(m_hCom, buffer, dwBytesWritten, &dwBytesWritten, &m_OverlappedWrite ); if(!bWriteStat) { if(GetLastError()==ERROR_IO_PENDING) { WaitForSingleObject(m_OverlappedRead.hEvent,1000); return ((int)dwBytesRead); } return(0); } return ((int)dwBytesRead);}int CSendMsgDlg::ReadData(char *buffer, DWORD dwBytesRead)
{ BOOL bReadStatus; bReadStatus = ReadFile( m_hCom, buffer, dwBytesRead, &dwBytesRead,&m_OverlappedRead); if(!bReadStatus) { if(GetLastError()==ERROR_IO_PENDING) { WaitForSingleObject(m_OverlappedRead.hEvent,1000); return ((int)dwBytesRead); } return(0); } return ((int)dwBytesRead);}当然了,这里的方法并不是唯一选择。上面主要是对COM口的操作,这跟手机和短信无关,下面的部分就是与手机短信有关的部分了:
首先,得知道如何利用手机的AT指令集,我们现在需要的指令不多,只有读短信和发短信。
关于AT指令的其他命令网上有很多资料这里也不再提及了
命令:AT+CMGL
命令说明:获取短信列表
格式举例:
AT+CMGL
+CMGL: 1, 1, ,380891683108200205f0240D91683128500474F7000850403191611200126211572856FE4E6699865B664E6054620021+CMGL: 2, 1, ,700891683108200205f0240D91683128500474F70008504031919113003262114E0076F45C31662F4E09597D5B66751F0020563F563F51765B9E6211662F57287BEE74039986770B4EBA5BB68DF3821E+CMGL: 3, 1, ,640891683108200205f0240D91683128500474F70008504031913272002C5F53713667094E864E0D8FC790FD662F59274E005C0F59B959B94E864F608001725B8FD860F354035AE98349+CMGL: 4, 1, ,820891683108200205f0240D91683128500474F70008504031918284003E4F608D767D27542C4F6076848BFE542700206B6A5FC3773C8FD84E0D5C1100204E004F1A80015E0863D095EE4F604F6053EF522B556590FD4E0D4F1A0020+CMGL: 5, 1, ,1540891683108200205f0240D91683128500474F70008504031916344008690A34F60600E4E484E0D4ECE59345F0059CB542C002080AF5B9A662F5FEB80038BD54E864F60624D77406025002056F0559D5496556189814E0D5C3157507B2C4E006392572880015E08773C76AE5E954E0B5C314E0D56F04E860020621173B05728592959294E5F662F4EE5549655617EF46301751F547D554A89814E0D65E9776177404E86+CMGL: 6, 1, ,880891683108200205f0240D91683128500474F70008504031914422004490A34F605C3153BB4E70901A51FA9898768480015E08002089814E0D5C31627E4EBA66FF4F6080030020603B776189C95BB9661380D662404EE56211767D59294E0D7761+CMGL: 7, 1, ,630891683108701305f0240BA13178855898F10008503013011285202C4F60684C4E0A7684004C0049004E005500584E2D5348501F621162FF5BBF820D88C54E004E0B884C4E0DFF1F+CMGL: 8, 1, ,620891683108200205f0240D91683128500474F70008504031913523002A54CE54404F6090A34E48806A660E597D597D770B4E66591A505A70B9989880AF5B9A5C3180FD8FC77684+CMGL: 9, 1, ,320891683108301105f0040D91683139116779F30008503013120565000C53EF4EE563A752364E865417+CMGL: 10, 1, ,360891683108301105f0040D91683139116779F300085030131285230010662F4E0D662F7F51901F5F886162554A+CMGL: 11, 1, ,380891683108301105f0040D91683139116779F30008503013220031001259295440002090A3662F4E0D662F4E2D6BD2+CMGL: 12, 1, ,380891683108301105f0040D91683139116779F30008503013221055001290A36211548B529E554A91CD542F884C5417+CMGL: 16, 1, ,1510891683108705305f0040BA13178536816F3000850402090311220848FD979CD65F650194F6057284E0A73ED4E865427FF0C621160F395EE95EE4F604E004E9B4E8B60C530024F6053EF4EE5544A8BC9621153BB5E74672C79D1658779D159276982662F591A5C114E0A7EBF541730029EBB70E64F604E8630024F604E5F628A5F2068A6534E768475358BDD544A8BC962115427FF0C514D5F97621165E0804A+CMGL: 17, 1, ,102
0891683108301105f0040D91683139116779F300085040306112230052636E8BF46C5F6D9B76840028003400305929653B514B59275B6682F18BED56DB7EA700290020633A597D7684002E002053EF4EE55E2E4F608BA1521260278FC77EA7002E00204F604E708BD598984E865417+CMGL: 18, 1, ,850891683108705305f0040BA13178536816F3000850404022206220424ECA59298BB25230534A8DEF5C316CA175354E86FF0C611F89C9602A602A768430024F604EE5540E8FD8662F53EB621155E654AA5427611F89C96BD48F834EB25207+CMGL: 20, 1, ,560891683108705505f0040D91683175804276F800085040508182950024534E4E3A76847EB34E9B9E1F4EBA5E26774088AB5B504E0A73ED3000771F768460506016+CMGL: 21, 1, ,410891683108701305f0040BA13178858581F80008504080905461201666534F1F300067094E8B76F86C42300056DE75358BDD+CMGL: 22, 1, ,900891683108705505f0040D91683175804276F8000850408011033000464F60660E59297ED9623F4E1C62534E2A75358BDD300053F778016211665A4E0A544A8BC94F603000621190A353EF4EE54E0A7F514E8630004ECA592998864E867B148BB0672C+CMGL: 23, 1, ,660891683108705505f0040D91683175804276F80008504080223543002E0031003300350030003700330031003400320036003253EB52185E086BCD30004F605E2E6211628A94B153E07740+CMGL: 24, 1, ,520891683108705505f0040D91683175804276F80008504080227571002000310036003500300020621173B057286CA194B1300089817B4953D15DE58D44+CMGL: 25, 1, ,380891683108705505f0040D91683175804276F8000850408022950400124F6073B057284E0D4F1A997F6B7B54273000+CMGL: 26, 1, ,480891683108705505f0040D91683175804276F80008504080322082001C534E4E3A898162DB4E2A5199006A00610076006176844F6067654E0D+CMGL: 27, 1, ,440891683108200205f0240D91683128500474F7000850409000416200184F60771F5F3A4EE5540E53EF4E0D65629A9A62704F604E86+CMGL: 28, 1, ,740891683108200205f0240D91683128500474F700085040900081500036621165394E864EE5540E4E0D4F1A6B3A8D1F60A880014EBA5BB64E86002C4F606B3A8D1F621162114E5F4E0D4F1A62A560287684002E+CMGL: 29, 1, ,820891683108200205f0240D91683128500474F70008504090002273003E62116DF14FE155846709558462A560766709607662A5002C5BF94E864F607ED9621163024E86591A5C115C0F65F64E864EC04E4865F650195230592A9633+CMGL: 30, 1, ,820891683108200205f0240D91683128500474F70008504090009222003E62118FD85F975929592963D091924F60002054CE4F608FD94E2A783481115B5000204F608D767D277ED9621163024E0A8FC751E05929621168C067E553BB+CMGL: 31, 1, ,580891683108200205f0240D91683128500474F7000850409000535400266211521A6D825B8C630775326CB97B495E725B8C4E8662115C31776100204F60572873A95565+CMGL: 32, 1, ,1080891683108200205f0240D91683128500474F700085040900014410058776189C94E4B524D6D82630775326CB95C314F1A505A4E2A597D68A6800C4E1468A690FD4F1A5B9E73B04E0D4FE14F608BD58BD5002C62114E0D8DDF4F6073A94E86660E59298FD85F9765E98D7751FA53BB73A900380038+CMGL: 33, 1, ,760891683108200205f0240D91683128500474F7000850409000542200385FD84E864F60662F753776844E864E0D8FC76CA14E8B73B057286D41884C75374EBA6D82630775326CB90020621177414E0D5F00773C4E86+CMGL: 34, 1, ,1540891683108200205f0240D91683128500474F7000850401190207100864E00592954B14FE9676552304E0053E38BB8613F4E9565C1002C62115F2F4E0B81708BB84E864E2A613F8FD85F804E9591CC62544E2A786C5E01002E4F604E5F60F38BB8613F4F464F605F2F817065F64E0D5C0F5FC37FFB51654E9591CC002E621188AB60CA54464E86002C5583558381EA8BED9053003A00208FD8771F7075563F00200021+CMGL: 35, 1, ,660891683108705505f0040D91683175804276F80008504031418592002E653E4E2A00760073007376845B8988C565874EF65230670D52A156684E0A97623000628A57305740544A8BC96211+CMGL: 36, 1, ,420891683108200205f0240D91683128500474F70008504031815581001680017CCA6D82795E522B5FD87ED96211630200510051+CMGL: 37, 1, ,720891683108200205f0240D91683128500474F70008504031819571003454CE54DF4F608FD84E0A8BFE002C626B76F273ED5427003F770B676562116BCF592965E94E0A90FD5F9763D091924F604E006B21+CMGL: 38, 1, ,1580891683108705505f0040D91683157011065F00008504001227091008A84288FBE59C66D3E624B4E0B53BB89C25BDF654C60C5002C4E0D4E004F1A513F624B4E0B53065FD956DE6765002C752898DF6307548C4E2D63074F5C51FA0056578B624B52BF002C84288FBE59C69AD8517476848BF4201C62114EEC80DC52294E86003F201D624B4E0B8BF4201C522B4ED65988626F4E86002C5C31526954B14EEC4FE94E863002201D+CMGL: 40, 1, ,340891683108200205f0240D91683128500474F70008504001226243000E84288FBE6BCD662F621190E84E0B呵呵有点长了,这是刚刚我从我手机中读取出来的数据,是经过编码以后的,有兴趣的话可以“翻译”出来看看是什么内容:)
关于SMS PDU格式的说明,推荐一个地址:
这里的资料已经够全了,基本可以包括所有的操作。
OK,读取手机短信不是我们的主要目的,在这里就不罗索了,只要仔细研究一下SMS PDU格式的那个文档,基本就不会有什么问题,我们的主要目的是发短信,现在就着重讲讲这一部分好了:
发短信的指令格式和编码格式也可以参考上面的地址,里面讲的也很详细了,我也不在这里罗嗦了,现在的关键问题是,如何根据格式来编码和解码。
首先,我们先建立一个Class来保存要发送的数据,每个字段用相应的类型表示,还要有一个“打包”的函数,就是把每一部分的数据组合起来。
class CMsgSend
{ public: CString GetMsgData(); void Pack(CString c_number,CString s_number,CString msg); CMsgSend(); virtual ~CMsgSend(); CString m_strData; CString SCA; CString PDUType; CString MR; CString DA; CString PID; CString DCS; CString VP; CString UDL; CString UD;};其中m_strData保存“打包”以后的数据,其他各个成员变量的含义请参照文档。Pack是打包函数,需要三个参数:短信服务中心号码、接收短信手机号码和信息内容。
另外,我们还需要一个工具类来实现编码和解码:
class CMyTools
{ public: static void HexToChar(CString sHex,char *p);//将十六进制转换为字符数组 static CString DeCodeChinese(CString strSrc);//中文解码 static CString EnCodeChinese(CString strSrc);//中文编码 static CString SwapConvert(CString str);//交换,例如1234567890变换为2143658709,具体什么用看文档 static BYTE HexToChar(CString str);//十六进制转换为字符类型 static CString DeCodeEnglish(CString srcStr);//英文解码 static CString EnCodeEnglish(CString srcStr);//英文编码};CString CMyTools::EnCodeEnglish(CString srcStr)
{ CString result; BYTE cur,c1,c2; CString tmp; int len,i=0,j=0; len = srcStr.GetLength() - 1; result = ""; while(i <= len) { c1 = srcStr.GetAt(i); if(i < len) { c2 = srcStr.GetAt(i+1); cur = (c1 >> j) | (c2 << (7-j) & 0xff); } else cur = (c1 >> j) & 0x7f; tmp.Format("%2.2X",cur); result = result + tmp; i++; j = (j+1) % 7; if(j == 0) i++; } return result;}CString CMyTools::DeCodeEnglish(CString srcStr)
{ CString strDest,strData; int n = 0,i,flag = 0,j = 0; strData = ""; int len = srcStr.GetLength(); for(i=0;i<len-1;i+=2) { CString strTmp; char tmp; strTmp.Format("0x%1c%1c",srcStr.GetAt(i),srcStr.GetAt(i+1)); tmp = HexToChar((LPSTR)(LPCTSTR)strTmp); strData = strData + tmp; }len = len/2 + len/2/8;
for(i = 0;i<len;i++) { strDest = strDest + " "; } for(i=0;i<len;i++) { BYTE c1,c2; if(i == 0) { c1 = strData.GetAt(i); strDest.SetAt(i,c1 & 0x7f); } else { c1 = strData.GetAt(j); c2 = strData.GetAt(j+1); strDest.SetAt(i,(c2 << n) | (c1 >> (8 - n))); strDest.SetAt(i,strDest.GetAt(i) & 0x7f); if(i%8 != 0) j++; } n = (i % 8) + 1; } return strDest;}BYTE CMyTools::HexToChar(CString hex)
{ int base = 1; int ret = 0; for(int i=hex.GetLength()-1;i>=0;i--) { char chex = hex.GetAt(i); int ihex; if(chex >= 'A' && chex <='F') { ihex = 15 - ('F' - chex); } else if(chex >= 'a' && chex <='f') { ihex = 15 - ('f' - chex); } else ihex = chex - '0'; ret += ihex * base; base*=16; } return ret;}CString CMyTools::SwapConvert(CString str)
{ CString result; result = str; for(int i=0;i<strlen(str);i+=2) { result.SetAt(i,str.GetAt(i+1)); result.SetAt(i+1,str.GetAt(i)); } return result;}CString CMyTools::EnCodeChinese(CString strSrc)
{CString strResult = "",strTmp,str;
char lpBuff; int len = strSrc.GetLength(); int i; WCHAR * wc; wc = new WCHAR[len];for(i=0;i<strSrc.GetLength();i++)
{ if((strSrc.GetAt(i) & 0x80) >> 7 == 0) len++; }MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,(LPSTR)(LPCTSTR)strSrc,len,wc,len/2);
for(i=0;i<len/2;i++) { strTmp.Format("%4.4X",wc[i]); strResult += strTmp; } return strResult;}CString CMyTools::DeCodeChinese(CString strSrc)
{ char *pBuf; CString strMsg = strSrc; int nLength = strMsg.GetLength(); if (nLength%2 == 1) { strMsg = strMsg.Left(nLength-1); int nNewLen = strMsg.GetLength(); pBuf = new char[nNewLen/2]; } else { pBuf = new char[nLength/2]; } HexToChar(strMsg,pBuf); int i = 0,nZero = 0; while ((i = strMsg.Find("00",i))>=0) { i += 2; nZero++; } char u_ret[255]; memset(u_ret,0,255); int nResult=WideCharToMultiByte( CP_ACP, // code page WC_COMPOSITECHECK, // character-type options (LPCWSTR)pBuf, // string to map nLength/4, //str.GetLength(), // number of bytes in string (LPSTR)u_ret, // wide-character buffer sizeof(u_ret), // size of buffer NULL, NULL ); CString sMessage; sMessage.Format("%s",u_ret); return sMessage;}void CMyTools::HexToChar(CString sHex, char *p)
{ unsigned char byteToHex[] ={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; int nLen = sHex.GetLength(); char *chBuf = p,//记录内存块的首地址 *pch; CString sTemp,sLeft,sRight; for (int i=0; i<nLen/4; i++) { sTemp = sHex.Left(4); sHex.Delete(0,4); sLeft = sTemp.Left(2); sRight = sTemp.Right(2); *chBuf = (char)strtoul(sRight.GetBuffer(2),&pch,16); chBuf++; sRight.ReleaseBuffer(); *chBuf = (char)strtoul(sLeft.GetBuffer(2),&pch,16); chBuf++; sLeft.ReleaseBuffer(); } *chBuf='\0';}
编码解码的工具都全了,现在来看“打包”函数:
void CMsgSend::Pack(CString c_number,CString s_number,CString msg)
{ int len; SCA= "00";//PDUTtpe
PDUType = "31";//MR
MR = "00";//DA
s_number = s_number + "F"; len = s_number.GetLength(); DA.Format("%2.2X81",len-1); DA = DA + CMyTools::SwapConvert(s_number);//PID
PID = "00";//DCS
DCS = "08";//VP
VP = "A7";//UD
UD = CMyTools::EnCodeChinese(msg);//UDL
UDL.Format("%2.2X",UD.GetLength()/2); m_strData = SCA + PDUType + MR + DA + PID + DCS + VP + UDL + UD;}例如我想给13875998800发送一条内容是hello的信息,短信中心号码为8613800731500,我们可以这样调用:msg.Pack("8613800731500","13875998800","hello");
sendMsg = msg.GetMsgData(); m.Format("AT+CMGS=%d\r\n",(sendMsg.GetLength()-msg.SCA.GetLength())/2); SendData((LPSTR)(LPCTSTR)m,m.GetLength()); m.Format("%s%c",sendMsg,26); SendData((LPSTR)(LPCTSTR)m,m.GetLength());具体的编码过程上面文档里面说的已经够详细了,也不在这里多说。
这里由于篇幅原因省略了很多东西,反正原理就是这些,核心代码也就是上面的一些编码解码的东西了,有些代码我也是从网上找到然后修改了一下来用的。
其实用AT指令还可以干很多事情,甚至拨打电话之类的都没问题,拨打电话的AT指令很简单:
ATD13875998800,只要发送这个指令过去,手机就会拨打13875998800这个号码。不过经过实践,发现被叫的手机无法正常的接听,提示要与电脑设备连接才可以接听,具体如何实现就没研究过了.....