USB转串口驱动代码分析

2023-11-13

 

 

1、USB插入时,创建设备

 DriverObject->DriverExtension->AddDevice = USB2COM_PnPAddDevice;

步一、调用USB2COM_CreateDeviceObject创建功能设备对象(FDO)

(1) IoCreateDevice系统API的原理为:

NTKERNELAPI
NTSTATUS
IoCreateDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN ULONG DeviceExtensionSize,
    IN PUNICODE_STRING DeviceName OPTIONAL,
    IN DEVICE_TYPE DeviceType,
    IN ULONG DeviceCharacteristics,
    IN BOOLEAN Reserved,
    OUT PDEVICE_OBJECT *DeviceObject
    );


在之前真实的USB驱动中我们是这样创建的:

ntStatus = IoCreateDevice(
                    DriverObject,                   // our driver object
                    sizeof(DEVICE_EXTENSION),       // extension size for us
                    NULL,                           // name for this device
                    FILE_DEVICE_UNKNOWN,
                    FILE_AUTOGENERATED_DEVICE_NAME, // device characteristics
                    FALSE,                          // Not exclusive
                    &deviceObject);                 // Our device object


就是第三个参数为NULL, 第四个参数为FILE_DEVICE_UNKNOWN,意味着我们驱动想附加的设备是空的,且未知。

由于我们是创建虚拟串口驱动,因此调用IoCreateDevice创建时在指定串口设备的名字,且指定设备类型

ntStatus = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION),
                           &deviceObjName, FILE_DEVICE_SERIAL_PORT,
                           FILE_DEVICE_SECURE_OPEN, TRUE, DeviceObject);

(2)为自定义的扩展设备中的设备名字段指定设备名

// deviceExtension->DeviceName为UNICODE_STRING类型
RtlZeroMemory(&deviceExtension->DeviceName, sizeof(UNICODE_STRING));
deviceExtension->DeviceName.MaximumLength = deviceObjName.Length + sizeof(WCHAR);
// Buffer重新分配
deviceExtension->DeviceName.Buffer = USB2COM_ExAllocatePool(NonPagedPool, deviceObjName.Length + sizeof(WCHAR));
RtlZeroMemory(deviceExtension->DeviceName.Buffer,
		deviceObjName.Length+sizeof(WCHAR));
RtlAppendUnicodeStringToString(&deviceExtension->DeviceName, &deviceObjName);


(3)初始化事件、串口控件对象、关键代码段、自旋锁、读写队列链表。

 // this event is triggered when there is no pending io of any kind and device is removed
KeInitializeEvent(&deviceExtension->RemoveEvent, NotificationEvent, FALSE);

// this event is triggered when self-requested power irps complete
KeInitializeEvent(&deviceExtension->SelfRequestedPowerIrpEvent, NotificationEvent, FALSE);

// this event is triggered when there is no pending io  (pending io count == 1 )
KeInitializeEvent(&deviceExtension->NoPendingIoEvent, NotificationEvent, FALSE);

// spinlock used to protect inc/dec iocount logic
KeInitializeSpinLock (&deviceExtension->IoCountSpinLock);
	
	deviceExtension->BaudRate = 19200;
/* Set line control */
deviceExtension->SerialLineControl.StopBits = STOP_BIT_1;
deviceExtension->SerialLineControl.Parity = NO_PARITY;
deviceExtension->SerialLineControl.WordLength = 8;

deviceExtension->SpecialChars.XonChar = SERIAL_DEF_XON;
deviceExtension->SpecialChars.XoffChar = SERIAL_DEF_XOFF;

deviceExtension->HandFlow.ControlHandShake = SERIAL_DTR_CONTROL;
deviceExtension->HandFlow.FlowReplace      = SERIAL_RTS_CONTROL;
deviceExtension->HandFlow.XoffLimit    = 300;
deviceExtension->HandFlow.XonLimit     = 100;
InitializeCircularBuffer(&deviceExtension->InputBuffer, 512);
InitializeCircularBuffer(&deviceExtension->OutputBuffer, 512);
KeInitializeSpinLock(&deviceExtension->InputBufferLock);
KeInitializeSpinLock(&deviceExtension->OutputBufferLock);
InitializeListHead(&deviceExtension->ReadQueue);
KeInitializeSpinLock(&deviceExtension->ReadQueueSpinLock);
InitializeListHead(&deviceExtension->WriteQueue);
KeInitializeSpinLock(&deviceExtension->WriteQueueSpinLock);
InitializeListHead(&deviceExtension->PurgeQueue);
KeInitializeSpinLock(&deviceExtension->PurgeQueueSpinLock);


 

步二、让设备对象支持直接读写IO和并设置DO_POWER_PAGABLE

 设置DO_POWER_PAGABLE的目的是在suspend期间不接收一个IRP_MN_STOP_DEVICE,

在resume时不接收一个IRP_MN_START_DEVICE消息。

 // we support direct io for read/write
        //
        deviceObject->Flags |= DO_DIRECT_IO;


        //Set this flag causes the driver to not receive a IRP_MN_STOP_DEVICE
        //during suspend and also not get an IRP_MN_START_DEVICE during resume.
        //This is neccesary because during the start device call,
        // the GetDescriptors() call  will be failed by the USB stack.
        deviceObject->Flags |= DO_POWER_PAGABLE;

步三、附加FDO到物理设备对象上,并创建SymbolLink,并通过IoRegisterDeviceInterface来设备绑定让设备可见。

deviceExtension->TopOfStackDeviceObject =
            IoAttachDeviceToDeviceStack(deviceObject, PhysicalDeviceObject);


 

status = IoRegisterDeviceInterface(PDevExt->PhysicalDeviceObject, (LPGUID)&GUID_CLASS_COMPORT,
                                      NULL, &PDevExt->DeviceClassSymbolicName);

步四、获得物理设备的性能的一份复制,保存在extension中,以此来获得电源级别

 

 (1)建立IRP来产生一个发往FDO的内部查询请求;

irp = IoAllocateIrp(LowerDeviceObject->StackSize, FALSE);


(2)设置IRP要发往的设备栈Location(是更低层的设备,在这里就是它附加下的USB)的信息,

eg: MajorFunction、MinorFunction、Parameters.DeviceCapabilities.Capabilities

    nextStack = IoGetNextIrpStackLocation(irp);
    nextStack->MajorFunction= IRP_MJ_PNP;
    nextStack->MinorFunction= IRP_MN_QUERY_CAPABILITIES;


在以上代码中的IoGetNextIrpStackLocation是一个宏,它的定义如下:

#define IoGetNextIrpStackLocation( Irp ) (\
    (Irp)->Tail.Overlay.CurrentStackLocation - 1 )

从以上宏可以看出:IRP结构体中存有当前的栈Location CurrentStackLoctation,而我们的IRP要发往的栈Location的获得方法就是原有栈的地址 - 1


 

 (3)设置IRP的完成例程;(在完成全程中就是把事件激活,这样KeWaitForSingleObject就能走下来)

(4)把IRP发送下去,并等待完成;

 

 ntStatus = IoCallDriver(LowerDeviceObject,
                            irp);

    USB2COM_KdPrint( DBGLVL_MEDIUM,(" USB2COM_QueryCapabilities() ntStatus from IoCallDriver to PCI = 0x%x\n", ntStatus));

    if (ntStatus == STATUS_PENDING) {
       // wait for irp to complete

       KeWaitForSingleObject(
            &event,
            Suspended,
            KernelMode,
            FALSE,
            NULL);

当完成全程返回时, nextStack->Parameters.DeviceCapabilities.Capabilities = DeviceCapabilities;指针就存着我们的性能信息。


步五、获得USB的版本信息;

直接调用系统API:

USBD_GetUSBDIVersion(&versionInformation);

 

2、处理系统PNP和电源管理请求的派遣函数

 DriverObject->MajorFunction[IRP_MJ_PNP] = USB2COM_ProcessPnPIrp;

在USB2COM_ProcessPnPIrp里case了以下几个消息:

IRP_MN_START_DEVICE 、IRP_MN_QUERY_STOP_DEVICE、IRP_MN_CANCEL_STOP_DEVICE、IRP_MN_STOP_DEVICE

IRP_MN_QUERY_REMOVE_DEVICE、IRP_MN_CANCEL_REMOVE_DEVICE、IRP_MN_SURPRISE_REMOVAL、IRP_MN_REMOVE_DEVICE
文章<<Window XP驱动开发(九) USB WDM驱动开发实例 bulkusb >>中讲到的同类派遣函数,它只case 了以下:

IRP_MN_START_DEVICE、

IRP_MN_STOP_DEVICE 、IRP_MN_EJECT和IRP_MN_SURPRISE_REMOVAL

所以一比较,觉得USB2COM考虑得更全面。

(1)IRP_MN_START_DEVICE

与文章<<Window XP驱动开发(九) USB WDM驱动开发实例 bulkusb >>中的处理基本类似;

(2)IRP_MN_QUERY_STOP_DEVICE

与文章<<Window XP驱动开发(九) USB WDM驱动开发实例 bulkusb >>中的处理基本类似;

(3)IRP_MN_CANCEL_STOP_DEVICE

 

(4)IRP_MN_STOP_DEVICE、

(5)IRP_MN_QUERY_REMOVE_DEVICE、

(6)IRP_MN_CANCEL_REMOVE_DEVICE、

(7)IRP_MN_SURPRISE_REMOVAL、

(8)IRP_MN_REMOVE_DEVICE

 

3、当应用程序CreateFile时,调用USB2COM_Create

 DriverObject->MajorFunction[IRP_MJ_CREATE] = USB2COM_Create;

步一、判断是否能接收一个新的IO请求

如果不能接收一个新的IO请求,那么直接返回。

  if ( !USB2COM_CanAcceptIoRequests( DeviceObject ) ) {
        ntStatus = STATUS_DELETE_PENDING;

		USB2COM_KdPrint( DBGLVL_DEFAULT,("ABORTING USB2COM_Create\n"));
        goto done;
    }

 

在以下的条件不能接收一个新的IO(判断的标志是我们自己标记的):

1) 设备已经被移除了,
2) 从来没有被启动过,,
3) 已经停止了,
4) 有一个移除的请求还没处理,
5) 有一个停止的请求还没处理。

//flag set when processing IRP_MN_REMOVE_DEVICE
    if ( !deviceExtension->DeviceRemoved &&
		 // device must be started( enabled )
		 deviceExtension->DeviceStarted &&
 		 // flag set when driver has answered success to IRP_MN_QUERY_REMOVE_DEVICE
		 !deviceExtension->RemoveDeviceRequested &&
		 // flag set when driver has answered success to IRP_MN_QUERY_STOP_DEVICE
		 !deviceExtension->StopDeviceRequested ){
			fCan = TRUE;
	}

步二:开始从interface中的Pipe[0]中读(说明Interface的获取是在之前就完成的,是在USB2COM_ProcessPnPIrp
                                                               里case IRP_MN_START_DEVICE:完成的)
StartReadIntUrb(
		DeviceObject,
		&interface->Pipes[0]
		);


(1)如果判断现在不能接收IO请求,那么就记录在extension中的IRP置为完成状态,然后返回;

(2)分配IRP、URB空间,并存到Extension->ReadIntUrbs数组中,再把Pipe句柄、Buffer、传送标志填入到URB结构体成员;

irp = IoAllocateIrp(stackSize, FALSE);
		if(irp == NULL) 
		{
	        	return STATUS_INSUFFICIENT_RESOURCES;
	    }
    	urb = USB2COM_ExAllocatePool(NonPagedPool, 
    					sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER));
	    if(urb == NULL)
    	{
    		IoFreeIrp(irp);
		return STATUS_INSUFFICIENT_RESOURCES;
    	}
    	deviceExtension->ReadIntUrbs[i].Irp = irp;
    	deviceExtension->ReadIntUrbs[i].Urb = urb;
    	deviceExtension->ReadIntUrbs[i].deviceObject = DeviceObject;
    	deviceExtension->ReadIntUrbs[i].PipeInfo = PipeInfo;
		InitIntUrb(urb,
		    	PipeInfo->PipeHandle,
		    	deviceExtension->ReadIntUrbs[i].TransferBuffer,
		    	sizeof(deviceExtension->ReadIntUrbs[i].TransferBuffer),
		    	TRUE);

 

InitIntUrb为自己封装的函数,很简单,就是把数据填入到urb结构体中:

VOID
InitIntUrb(
    IN PURB urb,
    IN USBD_PIPE_HANDLE  PipeHandle,
    IN PUCHAR TransferBuffer,
    IN ULONG length,
    IN BOOLEAN Read
    )
{
    USHORT siz = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER);
    if (urb) {
        RtlZeroMemory(urb, siz);

        urb->UrbBulkOrInterruptTransfer.Hdr.Length = (USHORT) siz;
        urb->UrbBulkOrInterruptTransfer.Hdr.Function =
                    URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER;
        urb->UrbBulkOrInterruptTransfer.PipeHandle = PipeHandle;
        urb->UrbBulkOrInterruptTransfer.TransferFlags =
            Read ? USBD_TRANSFER_DIRECTION_IN : 0;
        // short packet is not treated as an error.
        urb->UrbBulkOrInterruptTransfer.TransferFlags |= 
            USBD_SHORT_TRANSFER_OK;            
                
        //
        // not using linked urb's
        //
        urb->UrbBulkOrInterruptTransfer.UrbLink = NULL;
        urb->UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL;
        urb->UrbBulkOrInterruptTransfer.TransferBuffer = TransferBuffer;
        urb->UrbBulkOrInterruptTransfer.TransferBufferLength = length;
    }
}


(3)初始化IRP的栈Location及它的完成例程ReadIntUrbComplete,并把当前ReadIntUrbs的内存作为完成例程的Context;

(4)完成例程ReadIntUrbComplete的处理;

A、判断返回执行后返回的IRP、URB的状态,如果为未连接或取消那么就释放IRP及URB内存,并完成把extension中的IRP置为完成状态,然后直接返回;否则往下执行;

B、把IRP之前绑定的Buffer数据拷到InputBuffer中(现在的Buffer就是我们的结果数据);

KeAcquireSpinLock(&deviceExtension->InputBufferLock, &oldIrql);
			PushCircularBufferEntry(
				&deviceExtension->InputBuffer,
				&pIntUrbs->TransferBuffer[1],
				pIntUrbs->TransferBuffer[0]);
			KeReleaseSpinLock(&deviceExtension->InputBufferLock, oldIrql);

PushCircularBufferEntry为自己封装的函数:

把data内存中的len个数据拷到pBuffer中

NTSTATUS
PushCircularBufferEntry(
	IN PCIRCULAR_BUFFER pBuffer,
	IN PUCHAR data,
	IN ULONG len)
{
	ULONG NextPosition;
	DbgPrint("Serial: PushCircularBufferEntry(data %p, len %d)\n", data, len);
	ASSERT(pBuffer);
	ASSERT(pBuffer->Length);

	if ((data == NULL) || (len == 0))
		return STATUS_INVALID_PARAMETER;
	do{
		NextPosition = (pBuffer->WritePosition + 1) % pBuffer->Length;
		if (NextPosition == pBuffer->ReadPosition)
			return STATUS_BUFFER_TOO_SMALL;
		pBuffer->Buffer[pBuffer->WritePosition] = *data++;
		pBuffer->DataLen++;
		pBuffer->WritePosition = NextPosition;
	}while(--len);
	
	return STATUS_SUCCESS;
}

C、如果extension中有等待的IRP,且等待的事件中有SERIAL_EV_RXCHAR,那么通过SerialCompleteCurrentWait完成等待串口的等待IRP。

C、1   把当前IRP的取消完成例程置为NULL,根据包是否已经被取消标志pIrp->Cancel 及之前的IRP取消例程是否被执行过了,那么调用SerialCancelCurrentWait来取消。

注意SerialCancelCurrentWait中很重要的是要通过调用IoReleaseCancelSpinLock来释放系统的删除自旋锁

void
SerialCancelCurrentWait( PDEVICE_OBJECT DeviceObject, PIRP pIrp )
{
	PIO_STACK_LOCATION irpStack;
	PDEVICE_EXTENSION deviceExtension;
	
	DbgPrint("SerialCancelCurrentWait Enter Irp = %p\n",pIrp);
	ASSERT(pIrp);
	irpStack = IoGetCurrentIrpStackLocation(pIrp);
	deviceExtension = irpStack->DeviceObject->DeviceExtension;
	deviceExtension->CurrentWaitIrp = NULL;
	/*
	*All Cancel routines must follow these guidelines:
	* 1. Call IoReleaseCancelSpinLock to release the system's cancel spin lock
	* 2. ...
	*/
	IoReleaseCancelSpinLock(pIrp->CancelIrql);
	pIrp->IoStatus.Status = STATUS_CANCELLED;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	DbgPrint("SerialCancelCurrentWait Exit\n");
}

C、2    如果没有被取消,那么把当前我们的事件返回给应用层,并完成IRP。

	deviceExtension->CurrentWaitIrp = NULL;
	deviceExtension->HistoryMask &= ~events;
	IoReleaseCancelSpinLock(OldIrql);
	pIrp->IoStatus.Information = sizeof(ULONG);
	pIrp->IoStatus.Status = ntStatus;
	*((ULONG *)pIrp->AssociatedIrp.SystemBuffer) = events;
	IoCompleteRequest (pIrp,IO_NO_INCREMENT);

D、完成当前的读IRP;

D、1   如果有当前读的Irp,那么把第(4)B、里得到的结果数据弹出到当前的读IRP中(通过当前读的IRP中的MdlAddress地址访问到内存)。

if(deviceExtension->CurrentReadIrp)
{
	ULONG 		haveLen;
	BOOLEAN		returnWhatsPresent = FALSE;
	
	if(deviceExtension->SerialTimeOuts.ReadIntervalTimeout &&
		( deviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == 0) &&
		( deviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant == 0)
	)
	{
		returnWhatsPresent = TRUE;
	}
	ioBuffer = MmGetSystemAddressForMdlSafe(deviceExtension->CurrentReadIrp->MdlAddress,NormalPagePriority );
	ioLength = MmGetMdlByteCount(deviceExtension->CurrentReadIrp->MdlAddress);
	KeAcquireSpinLock(&deviceExtension->InputBufferLock, &oldIrql);
	haveLen = CircularBufferDataLen(&deviceExtension->InputBuffer);
	if( (ioLength <= haveLen) || (returnWhatsPresent && (haveLen > 0)))
	{
		ioLength = (ioLength < haveLen) ? ioLength : haveLen;
		DbgPrint("Complete CurrentReadIrp ioLength = %d\n",ioLength);
		ntStatus = PopCircularBufferEntry(&deviceExtension->InputBuffer,ioBuffer,ioLength);
		KeReleaseSpinLock(&deviceExtension->InputBufferLock, oldIrql);
		deviceExtension->CurrentReadIrp->IoStatus.Information = ioLength;
		deviceExtension->CurrentReadIrp->IoStatus.Status = ntStatus; 
		IoCompleteRequest(deviceExtension->CurrentReadIrp,IO_NO_INCREMENT);
		deviceExtension->CurrentReadIrp = DequeueReadIrp(deviceExtension);
	}
	else
		KeReleaseSpinLock(&deviceExtension->InputBufferLock, oldIrql);
}

以上代码中MmGetSystemAddressForMdlSafe

// 函数说明:
//     此函数返回MDL映射的地址,如果此MDL还没有被映射,那么它将会被映射
// 参数:
//     MemoryDescriptorList - 指向MDL的指针
//     Priority - 指向一个标志,该标记表明它是如何的重要,以至于这个请求在低的可用PTE条件下也成功了
// 返回值:
//     返回映射页的基地址,这个基地址和MDL的虚拟地址有同样的偏移地址.
//     与MmGetSystemAddressForMdl不同,在失败时它会返回NULL,而不是bugchecking the system.
// 版本说明:
//     此宏在WDM 1.0中不同用,在WDM1.0的驱动中实现此功能是通过提供同步并set/reset MDL_MAPPING_CAN_FAIL bit.
#define MmGetSystemAddressForMdlSafe(MDL, PRIORITY)                    \
     (((MDL)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA |                    \
                        MDL_SOURCE_IS_NONPAGED_POOL)) ?                \
                             ((MDL)->MappedSystemVa) :                 \
                             (MmMapLockedPagesSpecifyCache((MDL),      \
                                                           KernelMode, \
                                                           MmCached,   \
                                                           NULL,       \
                                                           FALSE,      \
                                                           (PRIORITY))))


 

第一个参数是deviceExtension->CurrentReadIrp->MdlAddress,我们从wdm.h可以看到它的定义:

// 定义一个指向这个I/O请求的内存描述符(MDL)的指针,
// 此域仅在I/O是“direct I/O"时被用
PMDL MdlAddress;


D、2   判断是否能接收一个新的IO请求

如果不能接收一个新的IO请求或者Pipe[0]关闭了,那么直接返回。


D、3   循环发送IRP,并置完成例程为ReadIntUrbComplete。(这样,就又回到了(4)),何时结束呢?

结束条件:直至设备不能接收一个新的IO请求或未连接、取消。

所以处理当前读IRP是一直进行的。

步三:准备写interface中的Pipe[1] (说明Interface的获取是在之前就完成的)
PrepareWriteIntUrb(
	IN PDEVICE_OBJECT DeviceObject,
	IN PUSBD_PIPE_INFORMATION PipeInfo
	)

A、分配写的Irb 与 Urb空间。

B、保存Pipe[1]地址为deviceExtension->WriteIntUrb.PipeInfo存在extension中。

步四、用应用程序传进来的Pipe名字来查找Pipe;(从extension中保存的PipeInfo数组中获得,说明PipeInfo在之前就获得了)

应用程序的名字从当前IRP中的fileObject得到。

ourPipeInfo = USB2COM_PipeWithName( DeviceObject, &fileObject->FileName );

步五、给应用程序返回步四中查找到的Pipe对应的Pipe信息,且如果设备不是D0状态那么就给设备上电;
for (i=0; i<interface->NumberOfPipes; i++) {

		PipeInfo =  &interface->Pipes[i]; // PUSBD_PIPE_INFORMATION  PipeInfo;

        if ( ourPipeInfo == &deviceExtension->PipeInfo[i] ) {

    		//
			// found a match
			//
			USB2COM_KdPrint( DBGLVL_DEFAULT,("open pipe %d\n", i));
			fileObject->FsContext = PipeInfo;
			ourPipeInfo->fPipeOpened = TRUE; // set flag for opened
			ntStatus = STATUS_SUCCESS;

			deviceExtension->OpenPipeCount++;

			// try to power up device if its not in D0
			actStat = USB2COM_SelfSuspendOrActivate( DeviceObject, FALSE );
			break;
		}
	}


 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

USB转串口驱动代码分析 的相关文章

  • 反射值接口和指针接收器

    在golang的mongodb驱动中有以下代码 case reflect Struct if z ok v Interface Zeroer ok return z IsZero return false Zeroer 接口定义如下 typ
  • 有什么办法可以查看标准输入缓冲区吗?

    我们知道stdin默认情况下是缓冲输入 证明这一点的证据是使用任何 留下数据 的机制stdin 例如scanf int main char c 10 0 scanf 9s c printf s and left is d n c getch
  • C++ 多重继承和 vtable

    因此 回到基础知识 我试图将我的注意力集中在 vtables 和诸如此类的事情上 在下面的例子中 如果我要传递一个B 对于某个函数 该函数如何知道调用该函数的 vtable 中的方法C对象而不是 vtable 中的方法A 是否有两个单独的
  • Typescript 为具有动态和静态键的对象创建接口

    我正在尝试学习打字稿 但在界面方面遇到了障碍 我有一个想要保存的对象token和一个route如下 const obj token thisismytoken path to somewhere 我在这里遇到的问题是 如何生成该对象的接口
  • System.Data.IDbCommand 和异步执行?

    系统 Data SqlClient SqlCommand 有方法 BeginExecuteNonQuery BeginExecuteReader BeginExecuteXmlReader and EndExecuteNonQuery En
  • 如何在 Kotlin 中编写以下代码来实现回调

    我如何像java一样用Kotlin编写 Callback callback new Callback Override public void getCallback ServerResponse serverResponse var ca
  • 检测 iOS 中的旋转变化

    我正在制作一个 iOS 应用程序 需要在旋转时进行一些界面重新排列 我试图通过实施来检测这一点 void orientationChanged NSNotification note 但这会在设备正面朝上或正面朝下时向我发出通知 我想要一种
  • 何时使用字节数组&何时使用字节缓冲区?

    字节数组和字节缓冲区有什么区别 另外 在什么情况下应该优先选择其中之一 我的用例是用 java 开发的 Web 应用程序 实际上有多种处理字节的方法 我同意 选择最好的并不总是那么容易 the byte the java nio ByteB
  • Android AudioTrack 缓冲问题

    好的 我有一个频率发生器 它使用 AudioTrack 将 PCM 数据发送到硬件 这是我使用的代码 private class playSoundTask extends AsyncTask
  • Java 接口中的可选方法

    根据我的理解 如果你在java中实现一个接口 那么该接口中指定的方法必须由实现该接口的子类使用 我注意到在某些接口 例如 Collection 接口 中 有些方法被注释为可选 但这到底意味着什么 它让我有点困惑 因为我认为接口中指定的所有方
  • Golang 函数指针作为结构的一部分

    我有以下代码 type FWriter struct WriteF func p byte n int err error func self FWriter Write p byte n int err error return self
  • IEnumerable 如何在后台工作

    我正在徘徊于更深入的功能IEnumerable
  • 是否有类似 ICollection 的接口,但专为排序集合而设计?

    或者我可以毫无问题地使用 ICollection 吗 我的意思是 我不认为 ICollection 是为排序集合设计的 因为这可能会破坏为排序或未插入 ICollection 对象设计的应用程序 但我不知道 我会说ICollection 接
  • 在 Java 中显式调用默认方法

    Java 8 引入默认方法 http cr openjdk java net dlsmith jsr335 jsr335 0 6 2 H html提供扩展接口的能力 而无需修改现有的实现 我想知道当该方法已被覆盖或由于不同接口中的默认实现冲
  • 如何在 Webpack 5 中为 jsonwebtoken 填充缓冲区

    我正在升级到 Webpack 5 并且 jsonwebtoken 包存在问题 https github com auth0 node jsonwebtoken https github com auth0 node jsonwebtoken
  • 内部接口?

    我对 Java 很陌生 我不明白这个结构是什么 我知道什么是接口以及如何定义 但在这种情况下 我真的不知道 你能说说是关于什么的吗 public interface WebConstants public interface Framewo
  • Dao 和服务接口的需求

    我是Spring Mvc的新手 在很多教程中 我发现有一个像这样的Dao接口 public interface StudentDAO public List
  • Java 8 中接口和抽象类之间的根本区别[重复]

    这个问题在这里已经有答案了 考虑到接口现在可以为其提供的方法提供实现 我无法正确合理地解释接口和抽象类之间的差异 有谁知道如何正确解释其中的差异 我还被告知 从性能角度来看 接口比抽象类更轻量 有人可以证实这一点吗 接口仍然不能有任何状态
  • Node.js 中的缓冲区是什么?

    正如您可以在有关 Buffer 类的 Node js 文档 http nodejs org api buffer html 一个缓冲区 类似于整数数组 但对应于 V8 堆外部的原始内存分配 到目前为止 一切都很好 现在让我困惑的是 从技术上
  • 桌面 webkit 相当于 Android 的 addJavascriptInterface()?

    在研究 Android UI 可能性时 我发现了一种名为 addJavascriptInterface 的方法的文档 该方法允许您将 Android Java 对象上的方法公开给 UI 的 WebView 组件中的 Javascript 这

随机推荐

  • Ubuntu与Windows下的Firefox账号不能同步解决方式【内附Ubuntu桌面图标制作方法】

    Ubuntu下的Firefox是国际版 属于全球服务 而Windows下的Firefox是本地服务的 两个系统下默认的存储服务器不是一个 无法同步 解决办法 卸载掉Ubuntu系统下原来的Firefox 安装Firefox中国版 安装方法
  • Mac如何找到从AppStore下载的正版Xcode安装包

    前言 本文介绍在Mac下如何找到AppStore下载的安装包路径 以及如何提取出来供以后使用 希望对大家有所帮助 前提 想要提取某个安装包 前提是你正在从AppStore安装这个程序 比如你想提取imovie的安装包 前提是你必须正在从Ap
  • Cocos2d-x JSB 自动绑定bindings-generator (以下简称B-G) 使用心得

    文章转载自 http www cocoachina com bbs read php tid 177904 B G 是什么 当使用JSB的时候 如果你想要使用的C 的类或者方法没有在已有JSB中被绑定 这时候 就可以使用B G 它可以生成相
  • 目标检测可视化gt

    xml格式可视化 这里分了两类 1 目标被标注为正矩形 即 xmin ymin xmax ymax 一般的voc数据类型都是这种标注形式 2 目标被标注为具有一定旋转角度的矩形 即 x1 y1 x2 y2 x3 y3 x4 y4 DOTA数
  • 如何将CRM系统上传到服务器,如何将SAP CRM equipment通过中间件上传到ERP系统

    In document Step by step to download equipment from ERP with hierarchy the steps to replicate equipment from ERP to CRM
  • 网页中如何使背景固定位置不变

    网页中如何使背景固定位置不变 在draemweaver中按Ctrl Shift E 新建一CSS样式 new 在background标签里 可以设定背景的各个选项 Background 背景色Background Image 选取你的背景图
  • 用户登录日志表为user_id,log_id,session_id,visit_time

    数据信息 用户登录日志表为user id log id session id visit time 要求 用sql查询近30天每天平均登录用户数量 代码展示 select avg numUser as averageUsers from s
  • MFC干净地删除冗余控件

    1 手动删除 1 首先确定控件有哪些相关的变量和函数 打开 rc2文件在里面删除对应的控件图标 2 打开主对话框头文件 一般类向导生成的函数和变量定义都在头文件的最后一个 public l里 找到生成的变量定义和函数定义将其删除 3 打开对
  • 在ros2下使用ros1_bridge与ros1自定义消息桥接

    在ros2下使用ros1 bridge与ros1自定义消息桥接 示例环境 操作系统 ubuntu 20 04 amd64 ros版本 noetic ros2版本 foxy ros1示例代码 创建ros1的工作空间catkin ws 功能包c
  • AXI4-Stream协议总结

    AXI4 Stream去掉了地址项 允许无限制的数据突发传输规模 一 接口信号描述 信号 源 描述 ACLK 时钟源 全局时钟信号 所有信号在ACLK信号上升沿采样 ARESETn 复位源 全局复位信号 ARESETn低电平有效 TVALI
  • JDBC规范——(8)异常

    当访问一个数据源时发生错误或者警告 JDBC 用 SQLException 这个类及其子类来表示并提供相关的异常信息 8 1 SQLException SQLException 由一下几部分组成 1 描述错误的文本信息 可以通过 SQLEx
  • Aspose功能演示:使用Java以编程方式在 Excel 文件中添加或修改 VBA 宏

    VBA是一种编程语言 用于在 MS Excel 文件中自动执行各种操作 特别是 VBA 宏是用户定义的代码 可加速电子表格操作任务 在本文中 将学习如何以编程方式使用 Excel 文件中的 VBA 宏 最终 将能够 使用 Java 在 Ex
  • 树莓派配置热点(即设置wifi)

    https www raspberrypi org documentation configuration wireless access point routed md 如果官网打不开 看次链接 https qinfeng blog cs
  • rollup常用插件盘点

    1 rollup plugin commonjs 将 CommonJS 模块转换为 ES2015 供 Rollup 处理 rollup plugin commonjs是一个用于将CommonJS模块转换为ES6模块的Rollup插件 它的主
  • Vue组件化学习之scoped

    简介 主要介绍scoped的作用 先弄一个案例 main js 引入vue依赖 import Vue from vue 引入组件App import App from App vue 关闭生产提示 Vue config production
  • pip install -r requirements.txt出现错误解决办法

    pip install r requirements txt安装出现以下错误解决办法如下 PS D YOLO yolov5 master gt pip install r requirements txt Collecting matplo
  • 配置IIS之虚拟目录

    什么是IIS虚拟目录 有什么优点 虚拟目录指在站点下建立一个虚拟子目录 指定一个固定的物理路径做为站点的应用路径 1 虚拟目录与父级站点共用一个应用程序池 例如 站点TestWeb c Inetpub wwwroot TestWeb 下建立
  • 安利一个实用项目--物美智能

    安利一个实用项目 可用于二次开发 官网 项目介绍 项目地址 演示地址
  • Windows 下 Redis 安装与配置 教程

    文章目录 一 Windows 下安装 Redis 的几种方式 1 微软官方维护的 Redis 2 tporadowski 维护的 Redis 3 使用 WSL 安装 Redis 4 使用gcc编译器在Windows下编译redis源码 二
  • USB转串口驱动代码分析

    1 USB插入时 创建设备 DriverObject gt DriverExtension gt AddDevice USB2COM PnPAddDevice 步一 调用USB2COM CreateDeviceObject创建功能设备对象