windows内核开发笔记三:字符串

windows内核开发笔记三:字符串

ANSI 编码

ANSI一种字符代码,为使计算机支持更多语 言,通常使用0x80~0xFF 范围的2 个字节来表示1 个字符。不同的国家和地区制定了不同的标准,由此产生了GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。 这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为ANSI 编码。

 

关于MBCS字符集(Multi Byte Character System):GB2312、GBK等编码被称为MBCS,MBCS同ASCII是完全兼容的。对于前ASCII的128个字符,在MBCS字符集下有完全相同的编码,而汉字等字符用多个字节存储。也意味着MBCS下,字符的长度有可能有1个字节的,也有多个字节的。

 

所以ANSI编码其实是一个统称,不同的语言使用不同的编码规范,并没有跨语言统一编码。比如 windows7 notepad记事本保存的时候,在弹出对话框的下方有“编码”下拉框,默认是“ANSI”,也可以选择“Unicode”(其实是UTF-16)和 “UTF-8”。如果选择了ANSI,那么实际使用的编码规范就跟你的操作系统版本强相关了,如果是简体中文的windows就会用GBK标准。

typedef struct _STRING {

    USHORT Length;

    USHORT MaximumLength;

    PSTR Buffer;

} ANSI_STRING, *PANSI_STRING;

 

Unicode编码

就是把地球上所有的语言的符号,都用统一的字符集来表示,一个编码真正做到了唯一。Unicode只是确定了字符的二进制编码,但并没有确定字符存储的具体实现方式。比如UTF-8 ,UTF16是常见的Unicode实现方式。

 

UTF-8编码是变长的,一个字符可能是1个字节,2个字节,3个字节或者4个字节长。一般来说,欧洲的字母字符长度为1到2个字节,而亚洲的大部分字符则是3个字节,附加字符为4个字节长。Unix平台中普遍支持UTF-8字符集,HTML和大多数浏览器也支持UTF-8。

 

UTF-16编码的字符,要么是2个字节,要么是4个字节表示的。windows2000以上版本使用UTF-16,老版本windows用的ANSI。

 

typedef struct _UNICODE_STRING {

     USHORT Length; // 字符串的长度(字节数)

     USHORT MaximumLength; // 字符串缓冲区的长度(字节数)

    PWSTR Buffer; // 字符串缓冲区

} UNICODE_STRING, *PUNICODE_STRING;

1.  字符串的初始化

   在头文件ntdef.h中有一个宏方便这种定义。使用这个宏之后,我们就可以简单的定义一个常数字符串如下:

    #include <ntdef.h>

    UNICODE_STRING str = RTL_CONSTANT_STRING(L“my first string!”);

    这只能在定义这个字符串的时候使用。为了随时初始化一个字符串,可以使用RtlInitUnicodeString。示例如下:

    UNICODE_STRING str;

    RtlInitUnicodeString(&str,L”my first string!”);

     用本方法初始化的字符串,不用担心内存释放方面的问题。因为我们并没有分配任何内存。

 

2  字符串的拷贝

     因为字符串不再是空结束的,所以使用wcscpy来拷贝字符串是不行的。UNICODE_STRING可以用RtlCopyUnicodeString来进行拷贝。在进行这种拷贝的时候,最需要注意的一点是:拷贝目的字符串的Buffer必须有足够的空间。如果Buffer的空间不足,字符串会拷贝不完全。这是一个比较隐蔽的错误。

     UNICODE_STRING dst; // 目标字符串

     WCHAR dst_buf[256]; // 我们现在还不会分配内存,所以先定义缓冲区

     UNICODE_STRING src = RTL_CONST_STRING(L”My source string!”);

   // 把目标字符串初始化为拥有缓冲区长度为256的UNICODE_STRING空串。

     RtlInitEmptyString(dst,dst_buf,256*sizeof(WCHAR));

    RtlCopyUnicodeString(&dst,&src); // 字符串拷贝!

     以上这个拷贝之所以可以成功,是因为256比L” My source string!”的长度要大。如果小,则拷贝也不会出现任何明示的错误。但是拷贝结束之后,与使用者的目标不符,字符串实际上被截短了。

      如果没有调用RtlInitEmptyString。结果dst字符串被初始化认为缓冲区长度为0。虽然程序没有崩溃,却实际上没有拷贝任何内容。

      在拷贝之前,最稳妥的方法是根据源字符串的长度动态分配空间。

3  字符串的连接

     UNICODE_STRING不再是简单的字符串。操作这个数据结构往往需要更多的耐心。读者会常常碰到这样的需求:要把两个字符串连接到一起。简单的追加一个字符串并不困难。重要的依然是保证目标字符串的空间大小。下面是范例:

    NTSTATUS status;

    UNICODE_STRING dst; // 目标字符串

    WCHAR dst_buf[256]; // 我们现在还不会分配内存,所以先定义缓冲区

    UNICODE_STRING src = RTL_CONST_STRING(L”My source string!”);

    // 把目标字符串初始化为拥有缓冲区长度为256的UNICODE_STRING空串

    RtlInitEmptyString(dst,dst_buf,256*sizeof(WCHAR));

    RtlCopyUnicodeString(&dst,&src); // 字符串拷贝!

 

    status = RtlAppendUnicodeToString(&dst,L”my second string!”);

    if(status != STATUS_SUCCESS)

    {

      ……

     }

​​​​​​​

     NTSTATUS是常见的返回值类型。如果函数成功,返回STATUS_SUCCESS。否则的话,是一个错误码。RtlAppendUnicodeToString在目标字符串空间不足的时候依然可以连接字符串,但是会返回一个警告性的错误STATUS_BUFFER_TOO_SMALL。

     另外一种情况是希望连接两个UNICODE_STRING,这种情况请调用RtlAppendUnicodeStringToString。这个函数的第二个参数也是一个UNICODE_STRING的指针。

4 字符串的打印

    字符串的连接另一种常见的情况是字符串和数字的组合。有时数字需要被转换为字符串。有时需要把若干个数字和字符串混合组合起来。这往往用于打印日志的时候。日志中可能含有文件名、时间、和行号,以及其他的信息。

    熟悉C语言的读者会使用sprintf。这个函数的宽字符版本为swprintf。该函数在驱动开发中依然可以使用,但是不安全。微软建议使用RtlStringCbPrintfW来代替它。RtlStringCbPrintfW需要包含头文件ntstrsafe.h。在连接的时候,还需要连接库ntsafestr.lib。       下面的代码生成一个字符串,字符串中包含文件的路径,和这个文件的大小。

    #include <ntstrsafe.h>

    // 任何时候,假设文件路径的长度为有限的都是不对的。应该动态的分配

    // 内存。但是动态分配内存的方法还没有讲述,所以这里再次把内存空间

    // 定义在局部变量中,也就是所谓的“在栈中”

    WCHAR buf[512] = { 0 };

    UNICODE_STRING dst;

    NTSTATUS status;

     ……

    // 字符串初始化为空串。缓冲区长度为512*sizeof(WCHAR)

    RtlInitEmptyString(dst,dst_buf,512*sizeof(WCHAR));

   // 调用RtlStringCbPrintfW来进行打印

    status = RtlStringCbPrintfW(dst->Buffer,L”file path = %wZ file size = %d \r\n”,&file_path,file_size);

   // 这里调用wcslen没问题,这是因为RtlStringCbPrintfW打印的

   // 字符串是以空结束的。

   dst->Length = wcslen(dst->Buffer) * sizeof(WCHAR);

    RtlStringCbPrintfW在目标缓冲区内存不足的时候依然可以打印,但是多余的部分被截去了。返回的status值为STATUS_BUFFER_OVERFLOW。调用这个函数之前很难知道究竟需要多长的缓冲区。一般都采取倍增尝试。每次都传入一个为前次尝试长度为2倍长度的新缓冲区,直到这个函数返回STATUS_SUCCESS为止。

    值得注意的是UNICODE_STRING类型的指针,用%wZ打印可以打印出字符串。在不能保证字符串为空结束的时候,必须避免使用%ws或者%s。其他的打印格式字符串与传统C语言中的printf函数完全相同。可以尽情使用。

    另外就是常见的输出打印。printf函数只有在有控制台输出的情况下才有意义。在驱动中没有控制台。但是Windows内核中拥有调试信息输出机制。可以使用特殊的工具查看打印的调试信息(请参阅附录1“WDK的安装与驱动开发的环境配置”)。

     驱动中可以调用DbgPrint()函数来打印调试信息。这个函数的使用和printf基本相同。但是格式字符串要使用宽字符。DbgPrint()的一个缺点在于,发行版本的驱动程序往往不希望附带任何输出信息,只有调试版本才需要调试信息。但是DbgPrint()无论是发行版本还是调试版本编译都会有效。为此可以自己定义一个宏:

  #if DBG

    KdPrint(a) DbgPrint##a

#else

   KdPrint (a)

#endif

   不过这样的后果是,由于KdPrint (a)只支持1个参数源码天空,因此必须把DbgPrint的所有参数都括起来当作一个参数传入。导致KdPrint看起来很奇特的用了双重括弧:

  // 调用KdPrint来进行输出调试信息

  status = KdPrint ((L”file path = %wZ file size = %d \r\n”,&file_path,file_size));

  这个宏没有必要自己定义,WDK包中已有。所以可以直接使用KdPrint来代替DbgPrint取得更方便的效果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jyl_sh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值