📄 xmodem.c
字号:
////////////////////////////////////////////////////////////////////////////
//
// XMODEM.C - Written by Mike Sax for Dr. Dobb's Journal
//
// This file contains two public functions:
// BOOL UploadXModem(HWND hParent, int gnPortID, LPSTR lpFilename);
// BOOL DownloadXModem(HWND hParent, int gnPortID, LPSTR lpFilename);
//
// These functions will transfer the file specified by lpFilename on the
// Windows comm. port specified by gnPortID. The hParent parameter is the
// handle of the window that will be the parent of the XModem status dialog.
//
// To include these functions in your own program, you should include the
// following files with your program: XMODEM.C COMM.C XMODEM.RC. No
// additional functions or variables are required.
//
////////////////////////////////////////////////////////////////////////////
#define dprintf(s) MessageBox(GetFocus(), s, "Debug Message", MB_OK)
// xmodem.c
#define USECOMM 1 // for 3.1 windows.h
#include "windows.h"
#include "wincom.h"
#include "comm.h"
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <memory.h>
#define RETRIES 12
#define CRCTRIES 2
#define PADCHAR 0x1a
#define SOH 1
#define EOT 4
#define ACK 6
#define NAK 0x15
#define CAN 0x18
#define CRC 'C'
// Local data
static int gcTries; // retry counter
static char gachBuffer[130]; // I/O buffer
static int ghStatusDlg; // Handle of the transfer status dialog
static BOOL gbUserCanceled; // Did the user press cancel?
static int gnPortID; // ID of the current comm. port
static FARPROC glpDlgProc; // ProcInstance of status dialog
static BOOL gbParentEnabled; // Parent of status dialog enabled?
static int gbTimeOut; // Time out char when reading?
static DCB gDCB; // Comm. status that we save
// Prototypes:
static void ReceiveError(int, int);
static void Sleep(int);
static void Status(char *);
static void TestWordLen(void);
HANDLE GetCurrentInstance(void);
static BOOL TimeOut(void);
WORD CalculateCRC(char *pchBuffer, int nLen);
static BOOL CreateTransferDialog(HWND hParent, char *szTitle, char *szFilename);
void DestroyTransferDialog(void);
BOOL _export _far PASCAL TransferDlgProc(HWND hDlg, WORD wMessage, WORD wParam, long lParam);
static void DoEvents(void);
static void ModifyCommState(void);
static void RestoreCommState(void);
// Error messages
static char *aszError[] =
{
"Timed Out",
"Invalid SOH",
"Invalid Block #",
"Invalid checksum/crc"
};
enum
{
errTIMEOUT,
errINVALIDSOH,
errINVALIDBLOCK,
errINVALIDCHECKSUM
};
// Give control to other windows
static void DoEvents(void)
{
MSG msg;
while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{ // Our application has to quit: simulate user pressed cancel
PostQuitMessage(msg.wParam);
gbUserCanceled = TRUE;
return;
}
if (!IsDialogMessage(ghStatusDlg, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
// Classic "pause" function for Windows
static void Sleep(int nTicks)
{
DWORD dwEnd = GetTickCount() + nTicks * 55;
while ((dwEnd > GetTickCount()) && !gbUserCanceled)
DoEvents();
}
// Wait x seconds for char in the com. port, return -1 if none, char otherwise
static int ComReadCharTimeOut(int nPortID, int nSeconds)
{
DWORD dwEnd = GetTickCount() + nSeconds * 1000l;
gbTimeOut = FALSE;
while ((dwEnd > GetTickCount()) && !gbUserCanceled)
{
if (CharsWaitingToBeRead(nPortID))
return ComReadChar(nPortID);
DoEvents();
}
gbTimeOut = TRUE;
return -1;
}
// Tansfer status dialog proc.
BOOL _export _far PASCAL TransferDlgProc(HWND hDlg, WORD wMessage, WORD wParam,
long lParam)
{
switch(wMessage)
{
case WM_INITDIALOG:
gbUserCanceled = FALSE;
break;
case WM_COMMAND:
if (wParam != IDCANCEL)
return FALSE; // Let dialog perform default action
case WM_CLOSE: // fall thru!
gbUserCanceled = TRUE;
break;
default:
return FALSE;
}
return TRUE;
}
// Returns the instance of the current task
HANDLE GetCurrentInstance(void)
{
_asm push ss
_asm call GlobalHandle
// return value in ax
}
// Create transfer dialog box. Return TRUE if success, FALSE otherwise
static BOOL CreateTransferDialog(HWND hParent, char *szTitle, char *szFilename)
{
HANDLE hInstance = GetCurrentInstance();
glpDlgProc = MakeProcInstance((FARPROC)TransferDlgProc, hInstance);
if (NULL == glpDlgProc)
return FALSE;
ghStatusDlg = CreateDialog(hInstance, "XMDMSTATUS", hParent, glpDlgProc);
if (ghStatusDlg)
{
DoEvents();
SetWindowText(ghStatusDlg, szTitle);
SetDlgItemText(ghStatusDlg, IDD_FILENAME, szFilename);
ShowWindow(ghStatusDlg, SW_SHOW);
gbParentEnabled = !EnableWindow(hParent, FALSE);
ModifyCommState();
}
else
FreeProcInstance(glpDlgProc);
return ghStatusDlg;
}
// Destroys the transfer status dialog box
void DestroyTransferDialog(void)
{
if (ghStatusDlg)
{
EnableWindow(GetParent(ghStatusDlg), gbParentEnabled);
DestroyWindow(ghStatusDlg);
FreeProcInstance(glpDlgProc);
ghStatusDlg = NULL;
RestoreCommState();
}
}
// Saves current comm. port settings and sets XOn/XOff off, N-8-1
static void ModifyCommState(void)
{
DCB dcb;
GetCommState(gnPortID, &gDCB);
dcb = gDCB;
dcb.fOutX = dcb.fInX = (BYTE)FALSE;
dcb.ByteSize = (BYTE)8;
dcb.Parity = NOPARITY;
dcb.StopBits = (BYTE)ONESTOPBIT;
SetCommState(&dcb);
}
static void RestoreCommState(void)
{
SetCommState(&gDCB);
}
// Upload a file using the XModem protocol return TRUE if success
// hParent: the handle of the window that should be the parent of
// the transfer status dialog box (shouldn't be NULL).
// gnPortID: the port ID of the port which is used for the transfer
// szFilename: the name of the file to be transfered
BOOL UploadXModem(HWND hParent, int gnPortID, char *szFilename)
{
int i, nCheckSum, bEOF = FALSE, chAnswer = 0, nLength, chCRCOut = 0;
WORD wCRC;
char nBlock = 1;
BOOL bResult;
int hFile;
hFile = _lopen(szFilename, OF_READ);
if ((gnPortID < 0) || (hFile < 1))
return FALSE; // Port is not open or file not found
if (!CreateTransferDialog(hParent, "XMODEM Upload (Checksum)", szFilename))
{
_lclose(hFile);
return FALSE; // Couldn't create dialog --> failure
}
gcTries = 0;
while (gcTries++ < RETRIES && chCRCOut != NAK && chCRCOut != CRC)
chCRCOut = ComReadCharTimeOut(gnPortID, 6);
gcTries = 0;
if (chCRCOut == CRC)
SetWindowText(ghStatusDlg, " XMODEM Upload (CRC) ");
else if (chCRCOut != NAK) // Time out or tried 10 times without succes
gcTries = RETRIES;
while (gcTries < RETRIES && !bEOF && chAnswer != CAN)
{
// read the next data block
memset(gachBuffer, 128, PADCHAR);
if ((nLength = _lread(hFile, gachBuffer, 128)) != 128)
bEOF = TRUE;
if (nLength == 0)
break;
SetDlgItemInt(ghStatusDlg, IDD_BLOCK, nBlock, FALSE);
if (gbUserCanceled)
{
ComWriteChar(gnPortID, CAN);
ComWriteChar(gnPortID, CAN);
break;
}
ComWriteChar(gnPortID, SOH); // SOH
ComWriteChar(gnPortID, nBlock); // block number
ComWriteChar(gnPortID, ~nBlock); // 1s complement
nCheckSum = 0;
// send the data block
for (i = 0; i < 128; i++)
{
ComWriteChar(gnPortID, gachBuffer[i]);
nCheckSum += gachBuffer[i]; // checksum calculation
}
// Send error-correcting value (nCheckSum or crc)
if (chCRCOut == NAK)
ComWriteChar(gnPortID, nCheckSum & 255);
else
{
wCRC = CalculateCRC(gachBuffer, 130);
ComWriteChar(gnPortID, (wCRC >> 8) & 255);
ComWriteChar(gnPortID, wCRC & 255);
}
// read ACK, NAK, or CAN from receiver
chAnswer = ComReadCharTimeOut(gnPortID, 10);
if (chAnswer == ACK)
{
nBlock++;
gcTries = 0;
SetDlgItemInt(ghStatusDlg, IDD_ERRORS, 0, FALSE);
SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, "");
}
else
{
bEOF = FALSE;
SetDlgItemInt(ghStatusDlg, IDD_ERRORS, ++gcTries, FALSE);
SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, chAnswer == NAK ?
"Invalid CRC" : "Time out");
// Position to previous block
if (_llseek(hFile, -128l, 1) == -1)
_llseek(hFile, 0L, 0);
}
}
if (bEOF)
{
ComWriteChar(gnPortID, EOT); // send the EOT
ComReadCharTimeOut(gnPortID, 10); // wait for an ACK
bResult = TRUE;
}
else
bResult = FALSE; // transfer aborted
_lclose(hFile);
DestroyTransferDialog();
return bResult; // success
}
// Download a file using the XModem protocol return TRUE if success
// hParent: the handle of the window that should be the parent of
// the transfer status dialog box.
// gnPortID: the port ID of the port which is used for the transfer
// szFilename: the name of the file to be transfered
BOOL DownloadXModem(HWND hParent, int gnPortID, char *szFilename)
{
int nCurrentBlock=0, nSOH= 0, nBlock, nNotBlock, i, hFile;
BOOL bCRC = TRUE, bFirst = TRUE;
WORD wOurChecksum, wChecksumSent;
hFile = _lcreat(szFilename, 0);
if ((gnPortID < 0) || (hFile < 0))
return FALSE; // Port is not open --> failure
if (!CreateTransferDialog(hParent, "XMODEM Download (Checksum)", szFilename))
return FALSE; // Couldn't create dialog --> failure
if(!ghStatusDlg)
return FALSE;
// Send Cs then NAKs until the sender starts sending
gcTries = 0;
while (nSOH != SOH && gcTries < RETRIES)
{
bCRC = (gcTries++ < CRCTRIES);
ComWriteChar(gnPortID, bCRC ? CRC : NAK);
nSOH = ComReadCharTimeOut(gnPortID, 6);
if (nSOH != -1 && nSOH != SOH)
Sleep(6);
}
if (bCRC)
SetWindowText(ghStatusDlg, "XMODEM Download (CRC)");
while ((gcTries < RETRIES) && (!gbUserCanceled))
{
if (gbTimeOut)
ReceiveError(errTIMEOUT, NAK);
SetDlgItemInt(ghStatusDlg, IDD_BLOCK, nCurrentBlock + 1, FALSE);
if (!bFirst)
{
nSOH = ComReadCharTimeOut(gnPortID, 10);
if (nSOH == -1) // TimeOut
continue;
else if (nSOH == CAN)
break;
else if (nSOH == EOT)
{
ComWriteChar(gnPortID, ACK);
break;
}
}
bFirst = FALSE;
nBlock = ComReadCharTimeOut(gnPortID, 1); // block number
nNotBlock = ComReadCharTimeOut(gnPortID, 1); // 1's complement
// get data block
for (i = 0, wOurChecksum = 0 ; i < 128; i++)
{
int nValue;
nValue = ComReadCharTimeOut(gnPortID,1);
if (nValue < 0) // Time out?
break;
gachBuffer[i] = (char)nValue;
wOurChecksum = (wOurChecksum + (*(gachBuffer + i)) & 255) & 255;
}
if (i != 128)
continue;
// checksum or crc from sender
wChecksumSent = ComReadCharTimeOut(gnPortID, 1);
if (bCRC)
wChecksumSent = (wChecksumSent << 8) +
ComReadCharTimeOut(gnPortID, 1);
if (gbTimeOut)
continue;
if (nSOH != SOH) // Check the nSOH
{
ReceiveError(errINVALIDSOH, NAK);
continue;
}
if (LOBYTE(nBlock) == LOBYTE(nCurrentBlock))
_llseek(hFile, -128L, 1);
else if (LOBYTE(nBlock) != LOBYTE(nCurrentBlock + 1) )
{
ComWriteChar(gnPortID, CAN);
ReceiveError(errINVALIDBLOCK, CAN);
break;
}
else
nCurrentBlock++;
// Test the block # 1s complement
if (LOBYTE(nNotBlock) != LOBYTE(~nCurrentBlock))
{
ReceiveError(errINVALIDBLOCK, NAK);
continue;
}
if (bCRC)
wOurChecksum = CalculateCRC(gachBuffer, 130);
// Test wOurChecksum or crc vs one sent
if (wChecksumSent != wOurChecksum)
{
ReceiveError(errINVALIDCHECKSUM, NAK);
continue;
}
nSOH = nBlock = nNotBlock = wChecksumSent = gcTries = 0;
// Write the block to disk
_lwrite(hFile, gachBuffer, 128);
if (gbUserCanceled)
{
ComWriteChar(gnPortID, CAN);
ComWriteChar(gnPortID, CAN);
break;
}
ComWriteChar(gnPortID, ACK);
}
_lclose(hFile);
DestroyTransferDialog();
return (nSOH == EOT); // return TRUE if transfer was succesfull
}
static void SendError(int erno)
{
++gcTries;
SetDlgItemInt(ghStatusDlg, IDD_ERRORS, gcTries, FALSE);
SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, aszError[erno]);
}
static void ReceiveError(int erno, int rtn)
{
++gcTries;
SetDlgItemInt(ghStatusDlg, IDD_ERRORS, gcTries, FALSE);
SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, aszError[erno]);
ComWriteChar(gnPortID, rtn);
Sleep(18);
FlushComm(gnPortID, 0);
FlushComm(gnPortID, 1);
}
WORD CalculateCRC(char *pchBuffer, int nLen)
{
int i;
DWORD dwCRC = 0;
while (nLen--)
{
dwCRC |= (*pchBuffer++) & 255;
for (i = 0; i < 8; i++)
{
dwCRC <<= 1;
if (dwCRC & 0x1000000L)
dwCRC ^= 0x102100L;
}
}
return (WORD) (dwCRC >> 8);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -