在日常的工作中,我们经常需要进行一些二进制文件或协议的读写操作,用C#解析二进制文件常常是一件比较麻烦且容易出错的工作,本文介绍了一种在C#中实现快速读写二进制文件通用的方法。
以一个解析Mp3 ID3V1标签头为例,ID3V1标签保存在MP3文件尾的最后128个字节用来存放ID3信息,其格式具体如下表:
字节 |
长度 (字节) |
说明 |
1-3 |
3 |
存放"TAG"字符,表示ID3 V1.0标准。 |
4-33 |
30 |
歌名 |
34-63 |
30 |
作者 |
64-93 |
30 |
专辑名 |
94-97 |
4 |
年份 |
98-127 |
30 |
附注 |
128 |
1 |
MP3音乐类别,共147种。 |
如果要用C/C++语言来解析这个标签头,一般需要经过如下两个步骤:
首先定义标签头数据结构,
typedefstructtagID3V1
{
char Header[3]; /*标签头必须是"TAG"否则认为没有标签*/
char Title[30]; /*标题*/
char Artist[30]; /*作者*/
char Album[30]; /*专集*/
char Year[4]; /*出品年代*/
char Comment[28]; /*备注*/
char reserve; /*保留*/
char track; /*音轨*/
char Genre; /*类型*/
}ID3V1;
C/C++语言定义的数据结构非常清晰的指明了各字段所占用的内存和偏移位置,由于C语言定义的数据结构是和内存中的偏移位置直接对应上的,因此,定义后数据结构后,从文件中获取数据到数据结构是非常简单的事情。PS:这是个c++的版本,由于只是个示例代码,去掉了异常处理相关流程,C语言版本类似,这里就不举例了。
void main()
{
ifstream file("r:\\te2st.mp3");
ID3V1 id3v1 = {0}; //存放读取的mp3 ID3V1信息
file.seekg((int)(-1*sizeof(id3v1)),ios::end);
file.read((char*)(&id3v1),sizeof(id3v1));
}
从这段代码中可以看到,只需要通过内存拷贝函数就可以将数据从数据一口气复制到数据结构中来,无需手动一个个成员赋值,非常简洁。
现在我们再来看看如何用C#实现这一功能,一般来讲,首先也是定义一个数据结构:
classID3V1
{
public string Header { get;set; }
public string Title { get;set; }
public string Artist { get;set; }
public string Album { get;set; }
public string Year { get;set; }
public string Comment { get;set; }
public byte Reserve { get;set; }
public byte Track { get;set; }
public byte Genre { get;set; }
}
和C语言相比,C#定义的数据结构相对较为抽象,从数据结构中看不到和ID3V1的各字段长度的对应关系,因此只能一个个字段的手动复制,解析函数实现如下:
publicstaticID3V1 ReadFormFile(string file)
{
var tagLength = 128;
var id3v1 = new ID3V1();
byte[] data = new byte[tagLength];
using (var stream =File.OpenRead(file))
{
stream.Seek(-1 * tagLength, SeekOrigin.End);
stream.Read(data, 0, data.Length);
}
var encoding = Encoding.Default;
id3v1.Header = encoding.GetString(data, 0, 3);
id3v1.Title = encoding.GetString(data, 3, 30).TrimEnd('\0');
id3v1.Artist = encoding.GetString(data, 33, 30).TrimEnd('\0');
id3v1.Album = encoding.GetString(data, 63, 30).TrimEnd('\0');
id3v1.Year = encoding.GetString(data, 93, 4);
id3v1.Comment = encoding.GetString(data, 97, 28).TrimEnd('\0');
id3v1.Reserve = data[125];
id3v1.Track = data[126];
id3v1.Genre = data[127];
return id3v1;
}
从上面代码可以看出,C#把其数据格式的解析放在解析函数里面来了,比起C语言来说复杂的多,主要体现在如下地方:
今天终于弄明白怎样使用C++读写二进制文件了。
要读取文件必须包含
可以使用fstream类,这个类可以对文件进行读写操作。
1、打开文件。
打开文件可以有两种方式,第一种可以使用fstream类的构造函数。
fstream file("test.dat",ios_base::in|ios_base::out|ios_base::app);
另外一种方法就是使用open函数。
fstream file;
file.open("test.dat",ios_base::in|ios_base::out|ios_base::app);
这样就可以打开一个可读写的文件了。如果文件不存在的话,就会创建一个新文件并且以读写方式打开。
这里需要说明一点,如果文件不存在的话,open函数中第二个参数必须包含ios_base::out|ios_base::app,
否则就不能正确创建文件。
2、写文件。
先进性写文件的操作否则读一个空文件是没有意义的。
既然是写二进制文件可以向文件中写入一个整形值。写二进制字符只能使用write函数。
但是write函数的原形是write(const char * ch, int size)。第一个参数是char *类型,所以需要把将要写入
文件的int类型转换成char *类型。这里的转换困扰了我好几天,不过终于弄明白了。代码如下。
int temp;
file.write((char *)(&temp),sizeof(temp));
3、读文件。
可以写文件了,读文件就好办多了。读文件需要用到read函数。其参数和write大致相同,read(const char * ch, int size)。
要把内容读到int类型变量中同样涉及到一个类型转换的问题。和写文件一样。
int readInt;
file.read((char *)(&readInt),sizeof(readInt));
这样文件中的int值就读入到int型变量readInt中了。
4、文件指针。
在文件的读写过程中往往需要对文件进行选择性读取。所以需要进行文件指针的移动。这是需要用到seekg和seekp函数。
在fstream类中有两个文件指针,一个是读取文件的指针,一个是写文件的指针分别用tellg和tellp文件来取得指针的位置。
同样seekg和seekp两个函数分别是对这两个指针进行移动的函数。这两个函数的参数都是一样的。
先对几个枚举类型进行一下说明:
ios_base::beg ——文件开始位置
ios_base::cur ——文件当前位置
ios_base::end ——文件末尾位置
下面以seekg为例说明一下指针移动的方法:
file.seekg(3) ——指针移动到第三个字符的位置
file.seekg(ios_base::beg) ——指针移动到文件开头
file.seekg(ios_base::end) ——指针移动到文件末尾
file.seekg(-3,ios_base::cur) ——指针当前位置向前移动三个字符
file.seekg(3,ios_base::cur) ——指针当前位置向后移动三个字符
file.seekg(3,file.tellg()) ——指针当前位置向后移动三个字符
file.seekg(file.tellg()+3) ——指针当前位置向后移动三个字符
5、对文件操作完毕后别忘了关闭文件。
file.close();
以上5个步骤就完成了对文件的读写操作。文本文件的操作是相同的,比二进制文件还要简单。
c++
char format[5]={0};
unsigned short version=0;
unsigned int size=0;
GUID modelid; //#include
double centerX=0.0;
double centerY=0.0;
unsigned int vertexcount=0;
ifstream file("e:\\495C1E900E934DEC819D53E0C624E7D2.essm", ios::in | ios::binary);
file.read(format,4);
format[4]='\0';
file.read((char *)&version,2);
file.read((char *)&size,4);
file.read((char *)&modelid.Data1,4);
file.read((char *)&modelid.Data2,2);
file.read((char *)&modelid.Data3,2);
file.read((char *)modelid.Data4,8);
file.read((char *)¢erX,8);
file.read((char *)¢erY,8);
file.read((char *)&vertexcount,4);
c#
FileStream sFile = new FileStream("e:\\495C1E900E934DEC819D53E0C624E7D2.essm", FileMode.Open);
var tagLength = 100;
byte[] data = new byte[tagLength];
//string data = "";
//sFile.Seek(-1 * tagLength, SeekOrigin.End);
sFile.Read(data, 0, 100);
var encoding = Encoding.Default;
string format = encoding.GetString(data, 0, 4); //0-3
short version = System.BitConverter.ToInt16(data, 4); //4-5 从第4位开始读取一个short型(2位)
int size = System.BitConverter.ToInt32(data, 6);//6-9 从第6位开始读取一个int型(4位)
Guid modelid=new Guid();
int a = System.BitConverter.ToInt32(data, 10);//10-13
short b = System.BitConverter.ToInt16(data, 14);//14-15
short c = System.BitConverter.ToInt16(data, 16);//16-17
//string sss=Encoding.Default.GetString(data, 18, 8);
//sbyte dd = Convert.ToSByte(sss,8);
//char[] ddd = sss.ToCharArray();
byte[] d = new byte[8];
//d=data[18];//有错
sFile.Seek(18, SeekOrigin.Begin);
sFile.Read(d, 0, 8);
double centerX = System.BitConverter.ToDouble(data, 26); //26-33 共8位
double centerY = System.BitConverter.ToDouble(data, 34); //34-41 共8位
int vertexcount = System.BitConverter.ToInt32(data, 42); //42-45 共4位
格式为
name |
type |
length |
|
format |
char |
4 |
|
version |
unsigned short |
2 |
|
size |
unsigned int |
4 |
|
modelid |
guid |
16 |
|
centerX |
double |
8 |
|
centerY |
double |
8 |
|
vertexcount |
unsigned int |
4 |
C++/VC/MFC获得GUID
C++/VC/MFC怎样获得GUID
请记得引入objbase.h头文件
#include
char * GetGUID()
{
static char buf[64] = {0};
GUID guid;
CoInitialize(NULL);
if (S_OK == ::CoCreateGuid(&guid))
{
_snprintf(buf, sizeof(buf),"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
guid.Data1,
guid.Data2,
guid.Data3,
guid.Data4[0],
guid.Data4[1],
guid.Data4[2],
guid.Data4[3],
guid.Data4[4],
guid.Data4[5],
guid.Data4[6],
guid.Data4[7]);
}
CoUninitialize();
return (char*)buf;
}