XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
阅读:4205回复:8

Windows驱动编程基础教程(连载2)

楼主#
更多 发布于:2008-05-22 21:25
    本文作者为楚狂人。有问题请联系QQ 16191935,msn walled_river@hotmail.com

Windows驱动编程基础教程(1.4-2.1)

1.4 字符串的连接

    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的指针。
    
1.5 字符串的打印

    字符串的连接另一种常见的情况是字符串和数字的组合。有时数字需要被转换为字符串。有时需要把若干个数字和字符串混合组合起来。这往往用于打印日志的时候。日志中可能含有文件名、时间、和行号,以及其他的信息。
    熟悉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取得更方便的效果。

2.1内存的分配与释放

    内存泄漏是C语言中一个臭名昭著的问题。但是作为内核开发者,读者将有必要自己来面对它。在传统的C语言中,分配内存常常使用的函数是malloc。这个函数的使用非常简单,传入长度参数就得到内存空间。在驱动中使用内存分配,这个函数不再有效。驱动中分配内存,最常用的是调用ExAllocatePoolWithTag。其他的方法在本章范围内全部忽略。回忆前一小节关于字符串的处理的情况。一个字符串被复制到另一个字符串的时候,最好根据源字符串的空间长度来分配目标字符串的长度。下面的举例,是把一个字符串src拷贝到字符串dst。
    
    // 定义一个内存分配标记
    #define MEM_TAG    ‘MyTt’
    // 目标字符串,接下来它需要分配空间。
    UNICODE_STRING dst = { 0 };
    // 分配空间给目标字符串。根据源字符串的长度。
    dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonpagedPool,src->Length,MEM_TAG);
    if(dst.Buffer == NULL)
    {
        // 错误处理
        status = STATUS_INSUFFICIENT_RESOUCRES;
        ……
    }
    dst.Length = dst.MaximumLength = src->Length;
    status = RtlCopyUnicodeString(&dst,&src);
    ASSERT(status == STATUS_SUCCESS);

    ExAllocatePoolWithTag的第一个参数NonpagedPool表明分配的内存是锁定内存。这些内存永远真实存在于物理内存上。不会被分页交换到硬盘上去。第二个参数是长度。第三个参数是一个所谓的“内存分配标记”。
    内存分配标记用于检测内存泄漏。想象一下,我们根据占用越来越多的内存的分配标记,就能大概知道泄漏的来源。一般每个驱动程序定义一个自己的内存标记。也可以在每个模块中定义单独的内存标记。内存标记是随意的32位数字。即使冲突也不会有什么问题。
    此外也可以分配可分页内存,使用PagedPool即可。
    ExAllocatePoolWithTag分配的内存可以使用ExFreePool来释放。如果不释放,则永远泄漏。并不像用户进程关闭后自动释放所有分配的空间。即使驱动程序动态卸载,也不能释放空间。唯一的办法是重启计算机。
    ExFreePool只需要提供需要释放的指针即可。举例如下:

    ExFreePool(dst.Buffer);
    dst.Buffer = NULL;
    dst.Length = dst.MaximumLength = 0;
    
    ExFreePool不能用来释放一个栈空间的指针。否则系统立刻崩溃。像以下的代码:

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

    会招来立刻蓝屏。所以请务必保持ExAllocatePoolWithTag和ExFreePool的成对关系。

最新喜欢:

greenpeacegreenp... g20062558g20062...
microbe
驱动小牛
驱动小牛
  • 注册日期2007-12-10
  • 最后登录2011-01-17
  • 粉丝1
  • 关注0
  • 积分914分
  • 威望420点
  • 贡献值1点
  • 好评度88点
  • 原创分0分
  • 专家分1分
沙发#
发布于:2008-05-23 08:39
我每篇都要顶!
hhyDriver
驱动小牛
驱动小牛
  • 注册日期2007-06-06
  • 最后登录2009-01-19
  • 粉丝0
  • 关注0
  • 积分154分
  • 威望150点
  • 贡献值0点
  • 好评度146点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2008-05-23 11:10
顶 顶 顶
cm007
驱动牛犊
驱动牛犊
  • 注册日期2007-10-31
  • 最后登录2009-11-04
  • 粉丝0
  • 关注0
  • 积分2分
  • 威望38点
  • 贡献值0点
  • 好评度21点
  • 原创分0分
  • 专家分0分
地板#
发布于:2008-05-23 14:20
强烈支持!!!!!!!
dt1985324
驱动牛犊
驱动牛犊
  • 注册日期2008-05-06
  • 最后登录2009-02-10
  • 粉丝0
  • 关注0
  • 积分16分
  • 威望106点
  • 贡献值1点
  • 好评度20点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2008-05-30 15:12
 我收藏了.....
zhoujiamurong
驱动小牛
驱动小牛
  • 注册日期2006-03-20
  • 最后登录2009-05-06
  • 粉丝4
  • 关注0
  • 积分1081分
  • 威望360点
  • 贡献值0点
  • 好评度215点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2008-06-24 17:39
RTL_CONST_STRING不能用,请问楼主需要什么头文件么?
zhoudream
驱动牛犊
驱动牛犊
  • 注册日期2007-07-13
  • 最后登录2010-06-01
  • 粉丝0
  • 关注0
  • 积分9分
  • 威望55点
  • 贡献值0点
  • 好评度18点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2008-06-29 00:17
定顶
angya
驱动牛犊
驱动牛犊
  • 注册日期2008-07-02
  • 最后登录2009-03-25
  • 粉丝0
  • 关注0
  • 积分6分
  • 威望23点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
7楼#
发布于:2008-07-02 16:41
基础的东西,就是好,学习
g20062558
驱动牛犊
驱动牛犊
  • 注册日期2010-06-03
  • 最后登录2012-05-07
  • 粉丝0
  • 关注2
  • 积分12分
  • 威望141点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
8楼#
发布于:2011-04-06 23:23
太感谢了    我每篇都收藏
游客

返回顶部