[C#][学习笔记]一个可以存/取HEX文件的类HexFileStream
[说在前面的话]最近要开发一个类似programmer的软件模块,自然要牵涉到Hex文件的读取和写入,
于是我习惯性的先搜索各大网站,想寻找前人的肩膀,无奈,搜索的结果很出乎我意料,
难道C#程序员都不需要读取Hex文件么?这应该是微软System.IO的一部分吧?
然而,想当然归想想当然,后来仔细一想,天下文件格式多了去了,微软才不管你
死活呢,要写,估计也是Intel写吧。求人不如求己,于是自己写了一个。
受水猫启发,单单发一个东西给大家用或者等着喊牛是要挨打的。于是,我决定将
自己的思维过程和开发过程简单的拆解开来,分阶段的接受群众的监督和检查。但不管
怎么说,先放一个能用的HexFileStream类才是王道。
<font color=red>[使用方法]
1、通过Add reference方法,加入对HexFileStream.dll的引用。
2、在代码中加入对命名空间的引用:
using Utilities.IO;
3、在代码中建立一个HexFileStream
HexFileStream tStream = null;
try
{
tStream = new HexFileStream(openFileDialog1.FileName, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite);
}
catch (Exception Err)
{
Err.ToString();
return;
}
4、设置存储器空间的大小——比如你用M128,你就要设置存储器空间大小
为128 * 1024,当然,如果空间大小是可变的,那么就设置0
tStream.SetLength(0);
5、读取所需的数据,并显示出来
Byte[] tBuffer = new Byte;
//! 和Stream的用法相同
tStream.Read(tBuffer, 0, tBuffer.Length);
//! m_bvExample是一个ByteViewer,ByteViewer是微软提供的一个类,用以将二进制
//! 数组以Hex字符串的方式显示在窗体上。
m_bvExample.SetBytes(tBuffer);
[版本说明]
v0.50 由于偷懒,目前版本仅仅支持各类Hex文件的读取,暂不支持写入,也不支持异步读取。
构造函数目前仅允许使用文件路径来创建HexFileStream,暂不支持使用文件指针。
构造函数必须指定操作方法为Read或者ReadWrite,否则,任何Read操作都将导致异常。
[相关下载]
1、点击此处下载HexFileStream.dll ourdev_539960.rar(文件大小:11K) (原文件名:HexFileStream.rar)
2、点击此处下载测试工程ourdev_539962.rar(文件大小:61K) (原文件名:HexFileStreamTester.rar) “情况不明的时候,先从必须要做的东西开始入手”
开发一个较大的项目,由于最终实现的功能可能较为复杂,则在项目的一开始就要求
较为精确的规划,对很多初学者来说,是相当有难度得,也不现实。比较现实的做法是:
<font color=blue>从必须要做的事情入手,然后将遇到的新问题分类,再次找到必须要做的事情,最终完成
大部分工作。
这种开发方式有其局限性,不过很容易通过经验的积累来弥补这种局限。至于是什么
局限,开发的经验会告诉你。
废话少说,一个HexFileStream最重要且最先要做的,当然是能够从Hex文件中解码出
我们需要的数据啦。所以,做一个Record类不用犹豫。
Record.h
using System;
using System.Collections.Generic;
using System.Text;
namespace Utilities.IO
{
//! \name record structure in Intel-Hexadecimal file
//! @{
internal abstract class Record
{
protected Boolean m_bAvailable = false;
//! \brief property for check whether this record is available or not.
public Boolean Available
{
get { return m_bAvailable; }
}
//! \name record type
//! @{
internal enum Type : byte
{
DATA_RECORD = 0, //!< data record
END_OF_FILE_RECORD = 1, //!< end of file record
EXTEND_SEGMENT_ADDRESS_RECORD = 2, //!< extend segment address record
START_SEGMENT_ADDRESS_RECORD = 3, //!< start segment address record
EXTEND_LINEAR_ADDRESS_RECORD = 4, //!< extend linear address record
START_LINEAR_ADDRESS_RECORD = 5 //!< start linear address record
}
//! @}
//! \brief property for getting record type
/*! \note Each record has a RECTYP field which specifies the record type of this record.
* The RECTYP field is used to interpret the remaining information within the
* record. The encoding for all the current record types are:
* ’00’ Data Record
* ’01’ End of File Record
* ’02’ Extended Segment Address Record
* ’03’ Start Segment Address Record
* ’04’ Extended Linear Address Record
* ’05’ Start Linear Address Record
*/
public abstract Type RecordType
{
get;
}
/*! \note Each record has a RECLEN field which specifies the number of bytes of
* information or data which follows the RECTYP field of the record. Note that
* one data byte is represented by two ASCII characters. The maximum value of
* the RECLEN field is hexadecimal ’FF’ or 255.
*/
public abstract Int32 RecordLength
{
get;
}
/*! \note Each record has a LOAD OFFSET field which specifies the 16-bit starting load
* offset of the data bytes, therefore this field is only used for Data Records.
* In other records where this field is not used, it should be coded as four
* ASCII zero characters (’0000’ or 03030303OH).
*/
public virtual UInt16 LoadOffset
{
get { return 0; }
}
/*! \note Each record has a variable length INFO/DATA field, it consists of zero or
* more bytes encoded as pairs of hexadecimal digits. The interpretation of
* this field depends on the RECTYP field.
*/
public abstract Byte[] Data
{
get;
}
//! \brief method to parse record string
//! \param tHexRecord a record string
//! \return a reference to a new record object
public static Record Parse(String tHexRecord)
{
//! check input
if (null == tHexRecord)
{
return null;
}
tHexRecord = tHexRecord.Trim().ToUpper();
if ("" == tHexRecord)
{
return null;
}
//! \brief check record head
if (':' != tHexRecord)
{
return null;
}
if (!IsHexNumber(tHexRecord.Substring(1)))
{
return null;
}
Int32 tRecordLength = 0;
Int32 tCheckSUM = 0;
Int32 tLoadOffSet = 0;
Byte tRecordType = 0;
Byte[] tData = null;
//! try to get record length
try
{
tRecordLength = Int32.Parse(tHexRecord.Substring(1, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
}
catch (Exception Err)
{
Err.ToString();
return null;
}
tCheckSUM += tRecordLength;
//! check record string length
if (tHexRecord.Length < (11 + tRecordLength * 2))
{
//! incomplete record
return null;
}
//! try to get LoadOffset
try
{
tLoadOffSet = UInt16.Parse(tHexRecord.Substring(3,4), System.Globalization.NumberStyles.AllowHexSpecifier);
}
catch (Exception Err)
{
Err.ToString();
return null;
}
tCheckSUM += tLoadOffSet >> 8;
tCheckSUM += tLoadOffSet & 0xFF;
//! try to get record type
try
{
tRecordType = Byte.Parse(tHexRecord.Substring(7, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
}
catch (Exception Err)
{
Err.ToString();
return null;
}
//! check record type value
if (tRecordType > 5)
{
return null;
}
else if (
((Type)tRecordType != Type.DATA_RECORD)
&&(tLoadOffSet != 0)
)
{
//! illegal record loadoffset
return null;
}
tCheckSUM += tRecordType;
//! get data byte
tData = new Byte;
if (null == tData)
{
//! failed to allocated memory
return null;
}
//! read all data bytes in a record
for (Int32 n = 0; n < tData.Length; n++)
{
try
{
tData = Byte.Parse(tHexRecord.Substring(9 + n * 2, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
}
catch (Exception Err)
{
Err.ToString();
return null;
}
tCheckSUM += tData;
}
//! get check sum
try
{
tCheckSUM += Byte.Parse(tHexRecord.Substring(9 + tData.Length * 2, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
}
catch (Exception Err)
{
Err.ToString();
return null;
}
if (0 != (tCheckSUM & 0xFF))
{
//! check sum error
return null;
}
//! \brief check record type
switch ((Type)tRecordType)
{
case Type.DATA_RECORD: //!< data record
return new DataRecord((UInt16)tLoadOffSet, tData);
case Type.END_OF_FILE_RECORD: //!< EOF record
return new EndOfFileRecord();
case Type.EXTEND_LINEAR_ADDRESS_RECORD: //!< extend linear address record
return new ExtendLinearAddressRecord(tData);
case Type.EXTEND_SEGMENT_ADDRESS_RECORD: //!< extend segment address
return new ExtendSegmentAddressRecord(tData);
case Type.START_LINEAR_ADDRESS_RECORD: //!< start linear address record
return new StartLinearAddressRecord(tData);
case Type.START_SEGMENT_ADDRESS_RECORD:
return new StartSegmentAddressRecord(tData); //!< start segment address
default:
//! brief no such type
return null;
}
return null;
}
//! \brief get HEX record String
public override String ToString()
{
StringBuilder tsbHexRecord = new StringBuilder();
if (m_bAvailable)
{
return null;
}
UInt32 tCheckSUM = 0;
//! record head
tsbHexRecord.Append(':');
//! record length
tsbHexRecord.Append(this.RecordLength.ToString("X2"));
tCheckSUM += (UInt32)RecordLength;
//! record load offset
tsbHexRecord.Append(this.LoadOffset.ToString("X4"));
tCheckSUM += (UInt32)(LoadOffset & 0x00FF);
tCheckSUM += (UInt32)(LoadOffset >> 8);
//! record type
tsbHexRecord.Append(((Byte)this.RecordType).ToString("X2"));
tCheckSUM += (Byte)RecordType;
//! record data
foreach (Byte tItem in Data)
{
tsbHexRecord.Append(tItem.ToString("X2"));
tCheckSUM += tItem;
}
//! record check sum
tsbHexRecord.Append(((Byte)((UInt16)0x100 - (UInt16)(tCheckSUM & 0xFF))).ToString("X2"));
return tsbHexRecord.ToString();
}
//! \brief method for check whecher string only consist of hex value
static public Boolean IsHexNumber(String tHexString)
{
if (null == tHexString)
{
return false;
}
tHexString = tHexString.Trim().ToUpper();
if ("" == tHexString)
{
return false;
}
foreach (Char tSymble in tHexString.ToCharArray())
{
if (!Char.IsNumber(tSymble))
{
if ((tSymble < 'A') || (tSymble > 'F'))
{
return false;
}
}
}
return true;
}
}
//! @}
/*! \note The End of File Record specifies the end of the hexadecimal object
* file.
*/
//! \name the End of File record
//! @{
internal class EndOfFileRecord : Record
{
//! \brief length always is zero.
public override int RecordLength
{
get { return 0; }
}
//! \brief get record type
public override Record.Type RecordType
{
get { return Type.END_OF_FILE_RECORD; }
}
//! \brief no data
public override byte[] Data
{
get
{
return new Byte;
}
}
}
//! @}
/*! \note The 32-bit Extended Linear Address Record is used to specify bits
* 16-31 of the Linear Base Address (LBA), where bits 0-15 of the LBA
* are zero. Bits 16-31 of the LBA are referred to as the Upper Linear
* Base Address (ULBA). The absolute memory address of a content byte
* in a subsequent Data Record is obtained by adding the LBA to an
* offset calculated by adding the LOAD OFFSET field of the containing
* Data Record to the index of the byte in the Data Record (0, 1, 2,
* ... n). This offset addition is done modulo 4G (i.e., 32-bits),
* ignoring any carry, so that offset wrap-around loading (from
* OFFFFFFFFH to OOOOOOOOOH) results in wrapping around from the end
* to the beginning of the 4G linear address defined by the LBA. The
* linear address at which a particular byte is loaded is calculated
* as:
*
* (LBA + DRLO + DRI) MOD 4G
*
* where:
* DRLO is the LOAD OFFSET field of a Data Record.
* DRI is the data byte index within the Data Record.
*
* When an Extended Linear Address Record defines the value of LBA,
* it may appear anywhere within a 32-bit hexadecimal object file.
* This value remains in effect until another Extended Linear Address
* Record is encountered. The LBA defaults to zero until an Extended
* Linear Address Record is encountered.
*/
//! \name the Extend Linear Address Record
//! @{
internal class ExtendLinearAddressRecord : Record
{
private UInt16 m_UpperLinearBaseAddress = 0;
private Byte[] m_Data = null;
public ExtendLinearAddressRecord(Byte[] tData)
{
if (null == tData)
{
return;
}
else if (2 != tData.Length)
{
return ;
}
m_Data = tData;
try
{
Byte[] tTemp = new Byte;
tTemp = tData;
tTemp = tData;
m_UpperLinearBaseAddress = BitConverter.ToUInt16(tTemp, 0);
}
catch (Exception Err)
{
Err.ToString();
return;
}
m_bAvailable = true;
}
//! \brief record data length should always be 2
public override Int32 RecordLength
{
get
{
if (!m_bAvailable)
{
return 0;
}
return 2;
}
}
//! \brief get record type
public override Record.Type RecordType
{
get { return Type.EXTEND_LINEAR_ADDRESS_RECORD; }
}
//! \brief get data
public override Byte[] Data
{
get
{
if (!m_bAvailable)
{
return null;
}
return m_Data;
}
}
//! \brief get the Upper Linear Base Address
public UInt32 UpperLinearBaseAddress
{
get
{
if (!m_bAvailable)
{
return 0;
}
return ((UInt32)m_UpperLinearBaseAddress) << 16;
}
}
}
//! @}
//! \name the Start Segment Address Record
//! @{
internal class StartSegmentAddressRecord : Record
{
private UInt32 m_StartSegmentAddress = 0;
private Byte[] m_Data = null;
public StartSegmentAddressRecord(Byte[] tData)
{
if (null == tData)
{
return;
}
else if (4 != tData.Length)
{
return;
}
m_Data = tData;
try
{
Byte[] tTemp = new Byte;
tTemp = tData;
tTemp = tData;
tTemp = tData;
tTemp = tData;
m_StartSegmentAddress = BitConverter.ToUInt32(tTemp, 0);
}
catch (Exception Err)
{
Err.ToString();
}
m_bAvailable = true;
}
//! \brief always return 4
public override int RecordLength
{
get
{
if (!m_bAvailable)
{
return 0;
}
return 4;
}
}
//! \brief get record type
public override Record.Type RecordType
{
get { return Type.START_SEGMENT_ADDRESS_RECORD; }
}
//! \brief get data
public override Byte[] Data
{
get
{
if (!m_bAvailable)
{
return null;
}
return m_Data;
}
}
//! \brief get start segment address
public UInt32 StartSegmentAddress
{
get
{
if (!m_bAvailable)
{
return 0;
}
return m_StartSegmentAddress & 0x000FFFFF;
}
}
public UInt16 CS
{
get
{
if (!m_bAvailable)
{
return 0;
}
return (UInt16)(m_StartSegmentAddress & 0x0000FFFF);
}
}
public UInt16 IP
{
get
{
if (!m_bAvailable)
{
return 0;
}
return (UInt16)((m_StartSegmentAddress >> 16) & 0x000F);
}
}
}
//! @}
/*! \note The Data Record provides a set of hexadecimal digits that represent
* the ASCII code for data bytes that make up a portion of a memory image.
* The method for calculating the absolute address (linear in the 8-bit
* and 32-bit case and segmented in the 16-bit case) for each byte of data
* is described in the discussions of the Extended Linear Address Record
* and the Extended Segment Address Record.
*/
//! \name the Data Record
//! @{
internal class DataRecord : Record
{
private Byte[] m_Data = null;
private UInt16 m_LoadOffset = 0;
//! \brief constructor for the Data Record
public DataRecord(UInt16 tLoadOffset, Byte[] tData)
{
if (null == tData)
{
return;
}
else if (tData.Length > 255)
{
return;
}
m_Data = tData;
m_LoadOffset = tLoadOffset;
m_bAvailable = true;
}
//! \brief get record data length
public override int RecordLength
{
get
{
if (null == m_Data)
{
return 0;
}
return m_Data.Length;
}
}
//! \brief get load offset
public override UInt16 LoadOffset
{
get
{
if (!m_bAvailable)
{
return 0;
}
return m_LoadOffset;
}
}
//! \brief get record type
public override Record.Type RecordType
{
get { return Type.DATA_RECORD; }
}
//! \brief get record data
public override Byte[] Data
{
get
{
if (!m_bAvailable)
{
return null;
}
return m_Data;
}
}
}
//! @}
/*! \note The 16-bit Extended Segment Address Record is used to specify bits
* 4-19 of the Segment Base Address (SBA), where bits 0-3 of the SBA
* are zero. Bits 4-19 of the SBA are referred to as the Upper Segment
* Base Address (USBA). The absolute memory address of a content byte
* in a subsequent Data Record is obtained by adding the SBA to an
* offset calculated by adding the LOAD OFFSET field of the containing
* Data Record to the index of the byte in the Data Record (0, 1, 2,
* ... n). This offset addition is done modulo 64K (i.e., 16-bits),
* ignoring any carry, so that offset wrap-around loading (from OFFFFH
* to OOOOOH) results in wrapping around from the end to the beginning
* of the 64K segment defined by the SBA. The address at which a
* particular byte is loaded is calculated as:
*
* SBA + ( MOD 64K)
*
* where:
* DRLO is the LOAD OFFSET field of a Data Record.
* DRI is the data byte index within the Data Record.
*
* When an Extended Segment Address Record defines the value of SBA,
* it may appear anywhere within a 16-bit hexadecimal object file. This
* value remains in effect until another Extended Segment Address Record
* is encountered. The SBA defaults to zero until an Extended Segment
* Address Record is encountered.
*/
//! \name the Extend Segment Address Record
//! @{
internal class ExtendSegmentAddressRecord : Record
{
private UInt16 m_ExtendSegmentUpperBaseAddress = 0;
private Byte[] m_Data = null;
public ExtendSegmentAddressRecord(Byte[] tData)
{
if (null == tData)
{
return;
}
else if (2 != tData.Length)
{
return;
}
m_Data = tData;
try
{
Byte[] tTemp = new Byte;
tTemp = tData;
tTemp = tData;
m_ExtendSegmentUpperBaseAddress = BitConverter.ToUInt16(tTemp, 0);
}
catch (Exception Err)
{
Err.ToString();
return;
}
m_bAvailable = true;
}
//! \brief always return 2
public override int RecordLength
{
get
{
if (!m_bAvailable)
{
return 0;
}
return 2;
}
}
//! \brief get record type
public override Record.Type RecordType
{
get { return Type.EXTEND_SEGMENT_ADDRESS_RECORD; }
}
//! \brief get data
public override Byte[] Data
{
get
{
if (!m_bAvailable)
{
return null;
}
return m_Data;
}
}
//! \brief get extend segment base address
public UInt32 ExtendSegmentBaseAddress
{
get
{
if (!m_bAvailable)
{
return 0;
}
return ((UInt32)m_ExtendSegmentUpperBaseAddress) << 4;
}
}
}
//! @}
/*! \note The Start Linear Address Record is used to specify the execution start
* address for the object file. The value given is the 32-bit linear address
* for the EIP register. Note that this record only specifies the code
* address within the 32-bit linear address space of the 80386. If the code
* is to start execution in the real mode of the 80386, then the Start
* Segment Address Record should be used instead, since that record specifies
* both the CS and IP register contents necessary for real mode.
*/
//! \name the Start Linear Address Record
//! @{
internal class StartLinearAddressRecord : Record
{
private UInt32 m_StartLinearAddress = 0;
private Byte[] m_Data = null;
public StartLinearAddressRecord(Byte[] tData)
{
if (null == tData)
{
return;
}
else if (4 != tData.Length)
{
return;
}
m_Data = tData;
try
{
Byte[] tTemp = new Byte;
tTemp = tData;
tTemp = tData;
tTemp = tData;
tTemp = tData;
m_StartLinearAddress = BitConverter.ToUInt32(tTemp, 0);
}
catch (Exception Err)
{
Err.ToString();
return ;
}
m_bAvailable = true;
}
//! \brief always return 4
public override Int32 RecordLength
{
get
{
if (!m_bAvailable)
{
return 0;
}
return 4;
}
}
//! \brief get record type
public override Record.Type RecordType
{
get { return Type.START_LINEAR_ADDRESS_RECORD; }
}
//! \brief get data
public override Byte[] Data
{
get
{
if (!m_bAvailable)
{
return null;
}
return m_Data;
}
}
//! \brief get start linear address (EIP)
public UInt32 StartLinearAddress
{
get
{
if (!m_bAvailable)
{
return 0;
}
return m_StartLinearAddress;
}
}
}
//! @}
} To be continue... To be continue... 呵呵 C#正在学习中
代码风格很好啊只是还不习惯全E文./emotion/em005.gif mark C#~~~~~ mark!!!!! mark mark mark 学习! mark 谢谢!
正在找这个! 写过vc 6.0下的hex类, 傻孩子的这个支持大于64K的hex文件, 不错, 网上的代码基本只支持小于64K的. 前段时间我也在网上找C#的Hex控件,最后找到一个,还比较好用的。名字叫Be.Windows.Forms.HexBox,功能很全,开源的。网上搜一下就能下载到。
我已经应用到了我的程序中了。如下:
http://cache.amobbs.com/bbs_upload782111/files_47/ourdev_693721ND6817.jpg
(原文件名:未命名.jpg)
http://cache.amobbs.com/bbs_upload782111/files_47/ourdev_693723GJY5GJ.jpg
(原文件名:未命名1.jpg) 好东西 好东西 学习了 谢谢 mark shandian 发表于 2011-11-10 10:06 static/image/common/back.gif
前段时间我也在网上找C#的Hex控件,最后找到一个,还比较好用的。名字叫Be.Windows.Forms.HexBox,功能很全 ...
试用了一下,这个控件确实不错,应该做电子的很多地方都可以用。我上传到这里吧 mark
學習了 學習{:smile:}{:smile:} 有没MFC的呀。 mark~~~~~~~~~~~~~~~~~~ 学习了!! 谢谢傻孩子大侠,希望您能多写一些关于C#的文章! 学习了,好东西{:victory:} mark 傻孩子的东西不错 . 这帖太有用报。 文件已经下载不了,楼主能不能再上传一下啊? 不错 16楼的先试试 学习了! C# HEX文件读写,标记一下。 用附件中的HexFileStreamTester.rar,打开一个数据大小为65684字节(非hex文件本身的大小)的hex文件,显示全部为FF啊
是不是不能显示超过64K的文件? 感谢,让我柳岸花明又一村! C# hex /HexBox
页:
[1]