篇一:

在驱动程序中,经常会调用其他的驱动程序;其中,手动构造IRP,然后将IRP传递到相应驱动程序的派遣函数中是一种比较简单的方法,下面就来介绍下手动创建IRP的几种不同的方法及其特点。

创建IRP总共有4种方法。分别通过调用:IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControl和IoAllocateIrp这4个内核函数来完成。这其中,IoAllocateIrp是比较底层的内核函数,其余的三个内核函数是属于靠近上层的内核函数,而且这三个函数都是通过调用IoAllocateIrp实现的。

这几个函数都是文档化的函数,原型都可以在DDK Documentation中查到,这里就不多说了,下面主要来说说它们的不同点:

1.      可创建的IRP类型

这四个函数可以创建的IRP的类型是不同的。IoBuildSynchronousFsdRequest用于创建同步的IRP请求,但是只可以创建以下类型的IRP:IRP_MJ_PNP,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS和IRP_MJ_SHUTDOWN;IoBuildAsynchronousFsdRequest可创建的IRP类型和IoBuildSynchronousFsdRequest一样(从名字就可以看出来),只是它是用来创建异步的IRP请求。IoBuildDeviceIoControl可以创建的IRP类型为:IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL。而且IoBuildDeviceIoControl只能创建同步的IRP。在这三个函数中,都有一个ULONG的输入参数指定创建的IRP类型。IoAllocateIrp函数的使用比较灵活,他可以创建任意类型的IRP,但不是由参数指定,而是创建后自行填写,要求用户对IRP的结构有比较熟悉的理解。

2.      创建后IRP对象的删除

IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest和IoBuildDeviceIoControl内核函数在创建完IRP后,不需要程序员负责删除IRP,操作系统会自动删除。而用IoAllocateIrp内核函数创建IRP时,需要程序员自己调用IoFreeIrp内核函数删除IRP对象。

3.      关联的事件

IoBuildSynchronousFsdRequest和IoBuildDeviceIoControl在创建IRP时,需要为它们准备好一个事件,这个事件会和IRP请求相关联,当IRP请求被结束时该事件触发。程序中要用KeWaitForSingleObject函数等待。IoBuildAsynchronousFsdRequest函数创建IRP时则不需要准备事件,不过可以通过IRP的UserEvent子域来通知IRP请求的结束。

当执行IoCompleteRequest内核函数时,操作系统会检查IRP的UserEvent子域是否为空。如果该子域为空,则它代表一个事件指针,这时IoCompleteRequest会设置这个事件。

篇2:

近来学习Windows内核方面的东西,觉得对I/O处理过程没有一个总体的概念。于是,就花了很长的时间搜集了很多这方面的知识总结了一下。

在Windows内核中的请求基本上是通过 I/ORequest Packet 完成的。前面说过,设备对象是唯一可以接受请求的实体。下面,我就来详细地说下IRP请求是怎么样一步一步完成的。

首先,我们就需要知道IRP是怎么产生。IRP是由I/O管理器发出的,I/O管理器是用户态与内核态之间的桥梁,当用户态进程发出I/O请求时,I/O管理器就捕获这些请求,将其转换为IRP请求,发送给驱动程序。I/O管理器无疑是非常重要的,具有核心地位。它负责所有I/O请求的调度和管理工作,根据请求的不同内容,选择相应的驱动程序对象,设备对象,并生成、发送、释放各种不同的IRP。整个I/O处理流程是在它的指挥下完成的。

一个IRP是从非分页内存中分配的可变大小的结构,它包括两部分:IRP首部和辅助请求参数数组,如图1所示。这两部分都是由I/O管理器建立的。

图1 IRP简单结构图

IRP首部中包含了指向IRP输入输出缓冲区指针、当前拥有IRP的驱动指针等。

紧接着首部的是一个IO_STACK_LOCATION结构的数组。它的大小由设备栈中的设备数确定(设备栈的概念会在下文中阐述)。IO_STACK_LOCATION结构中保存了一个I/O请求的参数及代码、请求当前对应的设备指针、完成函数指针(IoCompletion)等。

那么,由I/O管理器产生的IRP请求发送到哪去了呢?这里我们就要来说说设备栈的概念了,操作系统用设备对象(device object)表示物理设备,每一个物理设备都有一个或多个设备对象与之相关联,设备对象提供了在设备上的所有操作。也有一些设备对象并不表示物理设备。一个唯软件驱动程序(software-only driver,处理I/O请求,但是不把这些请求传递给硬件)也必须创建表示它的操作的设备对象。设备常常由多个设备对象所表示,每一个设备对象在驱动程序栈(driver stack)中对应一个驱动程序来管理设备的I/O请求。一个设备的所有设备对象被组织成一个设备栈(device stack)。而且,IO_STACK_LOCATION数组中的每个元素和设备栈中的每个设备是一一对应的,一般情况下,只允许层次结构中的每个设备对象访问它自己对应的IO_STACK_LOCATION。无论何时,一个请求操作都在一个设备上被完成,I/O管理器把IRP请求传递给设备栈中顶部设备的驱动程序(IRP是传递给设备对象的,通过设备对象的DriverObject成员找到驱动程序)。驱动程序访问它对应的设备对象在IRP中IO_STACK_LOCATION数组中的元素检查参数,以决定要进行什么操作(通过检查结构中的MajorFunction字段,确定执行什么操作及如何解释Parameters共用体字段的内容)。驱动程序可以根据IO_STACK_LOCATION结构中的MajorFunction字段进行处理。每一个驱动或者处理IRP,或者把它传递给设备栈中下一个设备对象的驱动程序。

传递IRP请求到底层设备的驱动程序需要经过下面几个步骤:

1.      为下一个IO_STACK_LOCATION结构设置参数。可以有以下两种方式:

· 调用IoGetNextIrpStackLocation函数获得下个结构的指针,再对参数进行赋值;

· 调用IoCopyCurrentIrpStackLocationToNext函数(如果第2步中驱动设置了IoCompletion函数),或者调用IoSkipCurrentIrpStackLocation函数(如果第2步中驱动没有设置IoCompletion函数)把当前的参数传递给下一个。

2.       如果需要的话,调用IoSetCompletionRoutine函数设置IoCompletion函数进行后续处理。

3.       调用IoCallDriver函数将IRP请求传递给下一层驱动。这个函数会自动调整IRP栈指针,并且执行下一层驱动的派遣函数。

当驱动程序把IRP请求传递给下一层驱动之后,它就不再拥有对该请求的访问权,强行访问会导致系统崩溃。如果驱动程序在传递完之后还想再访问该请求,就必须要设置IoCompletion函数。IRP请求可以再其他驱动程序或者其他线程中完成或取消。

当某一驱动程序调用IoCompleteRequest函数时,I/O操作就完成了。这个函数使得IRP的堆栈指针向上移动一个位置,如图2所示:

图2 IRP完成时栈指针的移动

图2所示的当C驱动程序调用完IoCompleteRequest函数后I/O栈的情况。左边的实线箭头表明栈指针现在指向驱动B的参数和回调函数;虚线箭头是之前的情况。右边的空心箭头指明了IoCompletion函数被调用的顺序。

如果驱动程序把IRP请求传递给设备栈中的下层设备之前设置了IoCompletion函数,当I/O栈指针再次指回到该驱动程序时,I/O管理器就将调用该IoCompletion函数。

IoCompletion函数的返回值有两种:

(1)STATUS_CONTINUE_COMPLETION:告诉I/O管理器继续执行上层驱动程序的IoCompletion函数。

(2)STATUS_MORE_PROCESSING_REQUIRED:告诉I/O管理器停止执行上层驱动程序,并将栈指针停在当前位置。在当前驱动程序调用IoCompleteRequest函数后再继续执行上层驱动的IoCompletion函数。

当所有驱动都完成了它们相应的子请求时,I/O请求就结束了。I/O管理器从Irp‑>IoStatus.Status成员更新状态信息,从Irp‑>IoStatus.Information成员更新传送字节数。

写的比较仓促,如有不正之处,希望大家指教!

篇3:

代码及EzDriverInstaller下载地址 : http://www.rayfile.com/zh-cn/files/9840cf8f-c41f-11e1-b25b-0015c55db73d/

(编译环境:VS2008+DDK库(参考:Window XP驱动开发(十六) XP下新建驱动程序工程并编译的第二种方法))

我有一篇文章是介绍以文件句柄形式调用其它驱动程序的方法:

Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式)

现在介绍以设备指针调用其它驱动程序的方法。

1、通过设备指针调用其他驱动程序

前面介绍了如何使用ZwCreateFile内核函数打开设备,还介绍了如何用ZwReadFile内核函数读取设备。

这些操作和应用程序中的CreateFile和ReadFile 函数的使用很类似。其实,CreateFile和ReadFile这两个API函数分别调用了ZwCreateFile和ZwReadFile内核函数。

ZwReadFile内核函数内部会创建IRP_MJ_READ类型的IRP, 然后通过这个IRP传送到相应驱动的派遣函数中。

本节介绍的驱动程序调用其他驱动程序的方法,不是借用ZwCreateFile和ZwReadFile等内核函数,而是“手动”构造各个IRP,

然后将IRP传递到相应的驱动程序的派遣函数里。

1、1  用IoGetDeviceObjectPointer获得设备指针

每个内核中的句柄都会和一个内核对象的指针联系起来。例如,进程对象的句柄和进程对象的指针关联,

线程对象的句柄和线程对象的指针关联,内核事件句柄和内核对象关联。

ZwCreateFile内核函数可以通过设备名打开设备句柄,这个设备句柄和一个文件对象的指针关联。

IoGetDeviceObjectPointer内核函数可以通过设备名获得文件对象指针,而不是获得设备句柄,其声明如下:

[cpp] view plaincopy print?
  1. IoGetDeviceObjectPointer(
  2. __in  PUNICODE_STRING ObjectName,
  3. __in  ACCESS_MASK DesiredAccess,
  4. __out PFILE_OBJECT *FileObject,
  5. __out PDEVICE_OBJECT *DeviceObject
  6. );
IoGetDeviceObjectPointer(__in  PUNICODE_STRING ObjectName,__in  ACCESS_MASK DesiredAccess,__out PFILE_OBJECT *FileObject,__out PDEVICE_OBJECT *DeviceObject);

第一个参数ObjectName:设备名,用UNICODE字符串表示;

第二个参数DesiredAccess :以什么样的权限得到设备句柄;

第三个参数FileObject:同时会返回一个和设备相关的文件对象指针;

第四个参数DeviceObejct:返回的设备对象指针。

Windows内核会为每一个对象指针保存一个“引用计数”,当对象被创建时引用计数为1。如果想引用这个对象,计数会加1。

如果删除对象时,Windows先将引用计数减1,如果引用计数不是0,系统不会删除对象。

当调用IoGetDeviceObjectPointer内核函数后,设备对象的引用计数就会加1,当用完这个设备对象后,应用调用ObDereferenceObject内核函数,

使其引用计数减1。

[cpp] view plaincopy print?
  1. #define ObDereferenceObject(a)                                     \
  2. ObfDereferenceObject(a)
#define ObDereferenceObject(a)                                     \ObfDereferenceObject(a)

当第一次调用IoGetDeviceObjectPointer内核函数时,会根据设备名打开设备,这时文件对象指针计数为1。此后如果再次调用

IoGetDeviceObjectPointer打开设备,就不是真正地打开设备了,而是只将引用计数加1。打开设备时,系统会创建一个

IRP_MJ_CREATE类型的IRP,并将这个IRP传递到驱动程序的派遣函数中。

每次调用ObDereferenceObject内核函数都会将“引用计数”减1,如果减至0就会关闭设备。关闭设备时,系统会创建一个IRP_MJ_CLOSE类型的IRP,

将将其传递到相应驱动的派遣函数中。

从上述内容可以看出IoGetDeviceObjectPointer和ObDereferenceObject内核函数完全正确可以代替ZwCreateFile和ZwCloseFile内核函数。另外,这种方法还能获

得设备对象指针关联的文件对象指针。

1、2 创建IRP传递给驱动的派遣函数

本节介绍如何手动创建IRP,并将 其传递给相应的程序程序。这样的好处是比ZwReadFile内核灵活。ZwReadFile内核函数是针对设备句柄操作的,而传递IRP是通过设备对象的指针操作。

(1)可以通过IoBuildSynchronousFsdRequest和IoBuildAsynchronousFsdRequest两个内核函数创建IRP,它们分别用来创建同步类型的IRP和异步类型的IRP。

这两个内核函数可以创建IRP_MJ_PNP、IRP_MJ_READ、IRP_MJ_WRITE、MJ_FLUSH_BUFFERS和IIRP_MJ_SHUTDOWN类型的IRP。

可以通过IoBuildDeviceIoControlRequest内核函数创建IRP_MJ_INTERNAL_DEVICE_CONTROL和IRP_MJ_DEVICE_CONTROL两个类型的IRP,

这两个内核函数只能创建同步类型的IRP。

另外,还可以使用IoAllocateIrp内核函数,它可以创建任意类型的IRP。IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest这三个内核函数都是属于靠近上层的内核函数。

而IoAllocateIrp是比较底层的内核函数,以下三个内核都是通过IoAllocateIrp实现的。

(2)创建完IRP后,还要构造IRP的I/O堆栈,每层I/O堆栈对应一个设备对象。由于示例程序DriverA是单层驱动程序,所以只需要构造IRP的第一层I/O堆栈。

(3)最后是通过IoCallDriver内核函数调用相应的驱动。IoCallDriver 内核函数会根据IRP的类型,找到相应的派遣函数。

总结一下,手动创建IRP有以下几个步骤:

(1)先得到设备的指针。一种方法是用IoGetDeviceObjectPointer内核函数得到设备对象的指针;

另一种方法是通过ZwCreateFile内核函数先得到设备句柄,然后调用ObReferenceObjectByPointer内核函数通过设备句柄得到设备对象指针。

(2)手动创建IRP,有4个内核函数可以选择,它们是IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest和

IoAllocateIrq,其中IoAllocateIrp内核函数是最灵活的,使用也最复杂。

(3)构造IRP的I/O堆栈。

(4)调用IoCallDriver内核函数,其内部会调用设备对象的派遣函数。

1、3  用IoBuildSynchronousFsdRequest创建IRP

函数声明如下:

[cpp] view plaincopy print?
  1. PIRP
  2. IoBuildSynchronousFsdRequest(
  3. __in  ULONG MajorFunction,
  4. __in  PDEVICE_OBJECT DeviceObject,
  5. __inout_opt PVOID Buffer,
  6. __in_opt ULONG Length,
  7. __in_opt PLARGE_INTEGER StartingOffset,
  8. __in  PKEVENT Event,
  9. __out PIO_STATUS_BLOCK IoStatusBlock
  10. );
PIRP
IoBuildSynchronousFsdRequest(__in  ULONG MajorFunction,__in  PDEVICE_OBJECT DeviceObject,__inout_opt PVOID Buffer,__in_opt ULONG Length,__in_opt PLARGE_INTEGER StartingOffset,__in  PKEVENT Event,__out PIO_STATUS_BLOCK IoStatusBlock);

第一个参数MajorFunction:这个参数是创建的IRP的主类型,IoBuldSynchronousFsdRequest函数只支持IRP_MJ_PNP、IRP_MJ_READ、IRP_MJ_WRITE、MJ_FLUSH_BUFFERS和IIRP_MJ_SHUTDOWN。

第二个参数DeviceObject:这个参数是设备对象指针,IRP将会传递给这个设备对象。

第三个参数Buffer:对于IRP_MJ_READ和IRP_MJ_WRITE,Buffer指的是输入和输出缓冲区

第四个参数Length:这个参数是缓冲区的大小

第五个参数StartingOffset:这个参数是偏移量;

第六个参数Event:这个参数是同步事件,这个创建同步类型的IRP的关键,后面会有介绍。

使用IoBuildSynchronousFsdRequest内核函数创建同步类型的IRP,关键在于第六个参数Event 。

在调用IoBuildSynchronousFsdRequest之前,需要准备一个事件,这个事件会和IRP请求关联,当IRP请求被结束时该事件被触发。

IoBuildSynchronousFsdRequest和IoBuildAsynchronousFsdRequest内核函数之间的区别就是是否提供事件。

下面的代码演示了如何使用IoBuildSynchronousFsdRequest内核函数创建同步类型IRP(在代码中的DriverB工程中):

[cpp] view plaincopy print?
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
  2. IN PIRP pIrp)
  3. {
  4. KdPrint(("DriverB:Enter B HelloDDKRead\n"));
  5. NTSTATUS ntStatus = STATUS_SUCCESS;
  6. UNICODE_STRING DeviceName;
  7. RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );
  8. PDEVICE_OBJECT DeviceObject = NULL;
  9. PFILE_OBJECT FileObject = NULL;
  10. //得到设备对象句柄,计数器加1
  11. //如果是第一次调用IoGetDeviceObjectPointer,会打开设备,相当于调用ZwCreateFile
  12. ntStatus = IoGetDeviceObjectPointer(&DeviceName,FILE_ALL_ACCESS,&FileObject,&DeviceObject);
  13. KdPrint(("DriverB:FileObject:%x\n",FileObject));
  14. KdPrint(("DriverB:DeviceObject:%x\n",DeviceObject));
  15. // 判断是否成功打开设备
  16. if (!NT_SUCCESS(ntStatus))
  17. {
  18. KdPrint(("DriverB:IoGetDeviceObjectPointer() 0x%x\n", ntStatus ));
  19. ntStatus = STATUS_UNSUCCESSFUL;
  20. // 设置IRP的完成状态
  21. pIrp->IoStatus.Status = ntStatus;
  22. // 设置IRP操作的字节数
  23. pIrp->IoStatus.Information = 0;  // bytes xfered
  24. // 将IRP请求结束
  25. IoCompleteRequest( pIrp, IO_NO_INCREMENT );
  26. KdPrint(("DriverB:Leave B HelloDDKRead\n"));
  27. return ntStatus;
  28. }
  29. KEVENT event;
  30. // 初始化一个同步对象
  31. KeInitializeEvent(&event,NotificationEvent,FALSE);
  32. IO_STATUS_BLOCK status_block;
  33. // 将32位整数转为64位的整数
  34. LARGE_INTEGER offsert = RtlConvertLongToLargeInteger(0);
  35. //创建同步IRP
  36. PIRP pNewIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,
  37. DeviceObject,
  38. NULL,0,
  39. &offsert,&event,&status_block);
  40. KdPrint(("DriverB:pNewIrp:%x\n",pNewIrp));
  41. // 得到下一层的I/O堆栈
  42. PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(pNewIrp);
  43. // 设置I/O堆栈的文件对象指针
  44. stack->FileObject = FileObject;
  45. //调用DriverA,会一直调用到DriverA的派遣函数
  46. NTSTATUS status = IoCallDriver(DeviceObject,pNewIrp);
  47. // 判断操作是否被挂起
  48. if (status == STATUS_PENDING)
  49. {
  50. //如果DriverA的派遣函数没有完成IRP,则等待IRP完成
  51. status = KeWaitForSingleObject(
  52. &event,
  53. Executive,
  54. KernelMode,
  55. FALSE, // Not alertable
  56. NULL);
  57. status = status_block.Status;
  58. }
  59. //将引用计数减1,如果此时计数器减为0,
  60. //则将关闭设备,相当于调用ZwClose
  61. ObDereferenceObject( FileObject );
  62. ntStatus = STATUS_SUCCESS;
  63. // 设置IRP的完成状态
  64. pIrp->IoStatus.Status = ntStatus;
  65. // 设置IRP的操作字节数
  66. pIrp->IoStatus.Information = 0;  // bytes xfered
  67. IoCompleteRequest( pIrp, IO_NO_INCREMENT );
  68. KdPrint(("DriverB:Leave B HelloDDKRead\n"));
  69. return ntStatus;
  70. }
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
{KdPrint(("DriverB:Enter B HelloDDKRead\n"));NTSTATUS ntStatus = STATUS_SUCCESS;UNICODE_STRING DeviceName;RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );PDEVICE_OBJECT DeviceObject = NULL;PFILE_OBJECT FileObject = NULL;//得到设备对象句柄,计数器加1//如果是第一次调用IoGetDeviceObjectPointer,会打开设备,相当于调用ZwCreateFilentStatus = IoGetDeviceObjectPointer(&DeviceName,FILE_ALL_ACCESS,&FileObject,&DeviceObject);KdPrint(("DriverB:FileObject:%x\n",FileObject));KdPrint(("DriverB:DeviceObject:%x\n",DeviceObject));// 判断是否成功打开设备if (!NT_SUCCESS(ntStatus)){KdPrint(("DriverB:IoGetDeviceObjectPointer() 0x%x\n", ntStatus ));ntStatus = STATUS_UNSUCCESSFUL;// 设置IRP的完成状态pIrp->IoStatus.Status = ntStatus;// 设置IRP操作的字节数pIrp->IoStatus.Information = 0;    // bytes xfered// 将IRP请求结束IoCompleteRequest( pIrp, IO_NO_INCREMENT );KdPrint(("DriverB:Leave B HelloDDKRead\n"));return ntStatus;}KEVENT event;// 初始化一个同步对象KeInitializeEvent(&event,NotificationEvent,FALSE);IO_STATUS_BLOCK status_block;// 将32位整数转为64位的整数LARGE_INTEGER offsert = RtlConvertLongToLargeInteger(0);//创建同步IRPPIRP pNewIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,DeviceObject,NULL,0,&offsert,&event,&status_block);KdPrint(("DriverB:pNewIrp:%x\n",pNewIrp));// 得到下一层的I/O堆栈PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(pNewIrp);// 设置I/O堆栈的文件对象指针stack->FileObject = FileObject;//调用DriverA,会一直调用到DriverA的派遣函数NTSTATUS status = IoCallDriver(DeviceObject,pNewIrp);// 判断操作是否被挂起if (status == STATUS_PENDING) {//如果DriverA的派遣函数没有完成IRP,则等待IRP完成status = KeWaitForSingleObject(&event,Executive,KernelMode,FALSE, // Not alertableNULL);status = status_block.Status;}//将引用计数减1,如果此时计数器减为0,//则将关闭设备,相当于调用ZwCloseObDereferenceObject( FileObject );ntStatus = STATUS_SUCCESS;// 设置IRP的完成状态pIrp->IoStatus.Status = ntStatus;// 设置IRP的操作字节数pIrp->IoStatus.Information = 0;  // bytes xferedIoCompleteRequest( pIrp, IO_NO_INCREMENT );KdPrint(("DriverB:Leave B HelloDDKRead\n"));return ntStatus;
}

测试方法:

(1) 标准驱动DriverA的设计与文章(Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式))一样,请参考代码。

(2) DriverB 的设计请参考代码。

(3) 安装HelloDDKA.sys、HelloDDKB.sys(安装方法与Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式)一样)

通过DebugView看到的打印信息如下:

1、4  用IoBuildAsynchronousFsdRequest创建IRP

这个内核函数比IoBuildSynchronousFsdRequest内核函数少一个事件参数。

1、5 用IoAllocate创建IRP

创建IRP的相关内容相关推荐

  1. 【SeeMusic】创建 SeeMusic 工程并编辑相关内容 ( 创建工程 | 导入 MIDI 文件 | 导入音频 | 导入视频 )

    SeeMusic 系列文章目录 [SeeMusic]下载安装并注册 SeeMusic 软件 [SeeMusic]创建 SeeMusic 工程并编辑相关内容 ( 创建工程 | 导入 MIDI 文件 | ...

  2. 基于KNN的相关内容推荐

    如果做网站的内容运营,相关内容推荐可以帮助用户更快地寻找和发现感兴趣的信息,从而提升网站内容浏览的流畅性,进而提升网站的价值转化.相关内容 推荐最常见的两块就是"关联推荐"和&qu ...

  3. Android开发环境——模拟器AVD相关内容汇总

    Android开发环境将分为SDK相关内容.Eclipse ADT相关内容.模拟器AVD相关内容.调试器DDMS相关内容.日志LogCat相关内容.连接驱动ADB相关内容.内存泄露检测工具MAT相关内 ...

  4. android Wifi开发相关内容

    今天,简单讲讲android里如何使用WifiManager. 之前,我看代码时,看到了wifi相关的代码,发现自己对于这个内容的使用还很不熟悉,所以在网上查找资料,最终解决了问题.这里记录一下. 移 ...

  5. 多进程相关内容(IPC)

    多进程相关内容 1.守护进程(daemon) ​ 主进程创建守护进程: 1.守护进程会在主进程代码执行结束后就终止 ​ 2.守护进程内无法再开启子进程,否则抛出异常 注意:进程之间是相互独立的,主进程 ...

  6. pytorch 梯度计算相关内容总结

    一.梯度计算准备工作 调用backward()函数前,叶子/非叶子节点的grad属性均为none,无论是否设置了requires_grad=True(叶子节点),或者调用了retain_grad()( ...

  7. typescript---配置类相关内容(十五)

    配置类相关内容 tsconfig.json文件 使用rollup打包TS文件 安装依赖 步骤 安装依赖 创建package.json文件 创建目录和配置文件 创建ts配置文件 使用webpack打包T ...

  8. python对文件的操作都有什么_python中文件操作的相关内容总结(附示例)

    本篇文章给大家带来的内容是关于python中文件操作的相关内容总结(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 1. 文件操作介绍 说到操作文件我们肯定会想到流,文件的操 ...

  9. 设计模式相关内容介绍

    1.设计模式相关内容介绍 1.1. 设计模式概述 创建型模式--------买建筑材料 用于描述------怎样创建对象,它的主要特点是----------将对象的创建与使用分离,这样可以降低系统的耦 ...

最新文章

  1. QIIME 2教程. 15样品分类和回归q2-sample-classifier(2021.2)
  2. linux添加审计账户_眼镜蛇W眼镜蛇白盒品白源代码审计工具 白帽子版
  3. iOS使用AVCaptureSession自定义相机
  4. 关于 IntelliJ IDEA 的Maven 版本修改
  5. 学习URLRewriter.dll的使用
  6. jvisualvm命令 Java Virtual Machine Monitoring Troubleshooting
  7. Mongo数据库慢查询功能
  8. flask 蓝本(blueprint)
  9. 微信为什么收不到服务器的红包,收不到别人的微信红包是怎么回事?该怎么办?...
  10. php-opencv身份证识别,python opencv实现证件照换底功能
  11. java全文检索word中的内容_搜索引擎时对WORD,EXCEL,PDF,POWERPOINT文件全文检索的总结...
  12. 小众播客,音乐平台的新战场?
  13. 用Python把文字转换成语音,抠脚大汉秒变撒娇萌妹,想想不寒而栗
  14. 世界杯中隐藏的IoT物联网黑科技
  15. Intellij搭建spark开发环境
  16. 倍福触摸屏维修倍福工控机维修CP3916-0010详解
  17. matlab 电动力学,MATLAB在电动力学教学中的应用研究
  18. 逆天了!“波士顿动力机器人”离上战场又近了一步
  19. 每天读一点好玩心理学--记忆
  20. PPT插入SWF的方法

热门文章

  1. 智库献策大数据时代食品安全
  2. 简单的扫描枪模拟程序
  3. 【转】本人常用资源整理(ing...)
  4. 使用ExcelJs导出表格设置样式、添加边框
  5. unityshader 晶格化消散
  6. 解决Win10 丢失蓝牙功能的问题
  7. android百度定位方式,Android 百度定位SDK
  8. 编程路上的重要概念记录
  9. 鼠标在用了一段之后偶然发现以给特别慢的速度移动鼠标,会在移动到可点击的选项按钮附近明显的卡住问题。
  10. 拼团系统开发的亮点与核心功能