(十六)、IRP的同步与异步

一、IRP的同步完成与异步完成

0

1、IoMarkIrpPending

0
0
IoMarkIrpPending函数的作用就是告诉IoManager不要回收资源
 

2、FILE_FLAG_OVERLAPPED

CreateFile的倒数第二个成员
0
 
 
三环代码
// syn_asy三环代码.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <stdio.h> #include <Windows.h> using namespace std; int main() { HANDLE hDevice = CreateFile(TEXT("\\\\.\\MyDDKLink"), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("打开设备失败 %d\n",GetLastError()); return -1; } DWORD dwret; OVERLAPPED ol = { NULL }; for (int i = 0; i < 100; i++) { ReadFile(hDevice, NULL, 0, &dwret, &ol); } CloseHandle(hDevice);//在内核会触发 MJ_cleanuP 与Close派遣函数// getchar(); return 0; }
0环代码
#include <ntddk.h> LIST_ENTRY ListHead; VOID Unload(PDRIVER_OBJECT driver) { UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); IoDeleteSymbolicLink(&SymbolicLinkName); IoDeleteDevice(driver->DeviceObject); DbgPrint("Driver Unload\n"); } NTSTATUS Create(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("Create 成功\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS Read(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入read\n"); IoMarkIrpPending(Irp); InsertHeadList(&ListHead, &Irp->Tail.Overlay.ListEntry);//将Irp插入链表// //Irp->IoStatus.Status = STATUS_SUCCESS; //Irp->IoStatus.Information = 0; //IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开读函数\n"); return STATUS_PENDING;; } NTSTATUS CleanUp(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入CleanUp\n"); while (!IsListEmpty(&ListHead)) { PLIST_ENTRY pEntry = RemoveHeadList(&ListHead); PIRP pendingIrp = CONTAINING_RECORD(pEntry, IRP, Tail.Overlay.ListEntry);//根据结构体某一成员的地址获取结构体的基地址// pendingIrp->IoStatus.Status = STATUS_SUCCESS; pendingIrp->IoStatus.Information = 0; IoCompleteRequest(pendingIrp, IO_NO_INCREMENT); DbgPrint("完成Irp操作\n"); } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开CleanUp\n"); return STATUS_SUCCESS; } NTSTATUS Close(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入Close\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开Close\n"); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT driver) { UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDDKName"); UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); PDEVICE_OBJECT DeviceObject; DbgPrint("Driver Load\n"); NTSTATUS status; status = IoCreateDevice(driver, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateDevice error\n"); return status; } status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateSymbolicLink error\n"); IoDeleteDevice(DeviceObject); return status; } InitializeListHead(&ListHead); driver->MajorFunction[IRP_MJ_CREATE] = Create; driver->MajorFunction[IRP_MJ_READ] = Read; driver->MajorFunction[IRP_MJ_CLEANUP] = CleanUp; driver->MajorFunction[IRP_MJ_CLOSE] = Close; DeviceObject->Flags |= DO_BUFFERED_IO; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; driver->DriverUnload = Unload; return STATUS_SUCCESS; }
 
0
在该实验中Read派遣函数的IRP被放入到链表中,同时挂起,当派遣函数CleanUp运行的时候才被重新运行
 

二、取消IRP

 
0
IoSetCancelRoutine
0
VOID
(*PDRIVER_CANCEL)(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
代码(这是一份错误的代码,怎么运行怎么蓝屏,我不是很理解)
0环代码
#include <ntddk.h> LIST_ENTRY ListHead; VOID Unload(PDRIVER_OBJECT driver) { UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); IoDeleteSymbolicLink(&SymbolicLinkName); IoDeleteDevice(driver->DeviceObject); DbgPrint("Driver Unload\n"); } NTSTATUS Create(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("Create 成功\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } VOID CancelRoutine( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { DbgPrint("进入取消函数\n"); Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开取消函数\n"); } NTSTATUS Read(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入read\n"); IoSetCancelRoutine(Irp, CancelRoutine); IoMarkIrpPending(Irp); //InsertHeadList(&ListHead, &Irp->Tail.Overlay.ListEntry);//将Irp插入链表// //Irp->IoStatus.Status = STATUS_SUCCESS; //Irp->IoStatus.Information = 0; //IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开读函数\n"); return STATUS_PENDING; } NTSTATUS CleanUp(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入CleanUp\n"); while (!IsListEmpty(&ListHead)) { PLIST_ENTRY pEntry = RemoveHeadList(&ListHead); PIRP pendingIrp = CONTAINING_RECORD(pEntry, IRP, Tail.Overlay.ListEntry);//根据结构体某一成员的地址获取结构体的基地址// pendingIrp->IoStatus.Status = STATUS_SUCCESS; pendingIrp->IoStatus.Information = 0; IoCompleteRequest(pendingIrp, IO_NO_INCREMENT); DbgPrint("完成Irp操作\n"); } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开CleanUp\n"); return STATUS_SUCCESS; } NTSTATUS Close(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入Close\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开Close\n"); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT driver) { NTSTATUS status; UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDDKName"); UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); PDEVICE_OBJECT DeviceObject; DbgPrint("Driver Load\n"); status = IoCreateDevice(driver, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateDevice error\n"); return status; } status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateSymbolicLink error\n"); IoDeleteDevice(DeviceObject); return status; } InitializeListHead(&ListHead); driver->MajorFunction[IRP_MJ_CREATE] = Create; driver->MajorFunction[IRP_MJ_READ] = Read; driver->MajorFunction[IRP_MJ_CLEANUP] = CleanUp; driver->MajorFunction[IRP_MJ_CLOSE] = Close; DeviceObject->Flags |= DO_BUFFERED_IO; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; driver->DriverUnload = Unload; return STATUS_SUCCESS; }
 

三、StartIo

StartIo主要任务就是将并行的Irp进行串行处理
IoStartPacket
0
 
0
 
 
DRIVER_STARTIO DriverStartio;
void DriverStartio(
[in, out] _DEVICE_OBJECT *DeviceObject,
[in, out] _IRP *Irp
)
{...}
 
 
IoStartNextPacket
0
 
取消函数会首先获取一个自旋锁,如果当前Irp不等于提供的Irp,就需要我们释放系统发取消自旋锁,参考:
0环代码
#include <ntddk.h> LIST_ENTRY ListHead; VOID Unload(PDRIVER_OBJECT driver) { UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); IoDeleteSymbolicLink(&SymbolicLinkName); IoDeleteDevice(driver->DeviceObject); DbgPrint("Driver Unload\n"); } NTSTATUS Create(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("Create 成功\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } void DriverStartio( struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp ) { LARGE_INTEGER timeout = RtlConvertLongToLargeInteger(-10 * 100 * 1000); DbgPrint("进入StartIo\n"); KeDelayExecutionThread(KernelMode, FALSE, &timeout); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); IoStartNextPacket(DeviceObject, TRUE); DbgPrint("离开StartIo\n"); } VOID CancelRoutine( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { DbgPrint("进入取消函数\n"); Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开取消函数\n"); } NTSTATUS Read(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入read\n"); //IoSetCancelRoutine(Irp, CancelRoutine); IoMarkIrpPending(Irp); IoStartPacket(DeviceObject, Irp, NULL, CancelRoutine);//将并行Irp串行处理// 将100个Irp自动插入一个链表中,串形处理// //InsertHeadList(&ListHead, &Irp->Tail.Overlay.ListEntry);//将Irp插入链表// //Irp->IoStatus.Status = STATUS_SUCCESS; //Irp->IoStatus.Information = 0; //IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开读函数\n"); return STATUS_PENDING; } NTSTATUS CleanUp(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入CleanUp\n"); while (!IsListEmpty(&ListHead)) { PLIST_ENTRY pEntry = RemoveHeadList(&ListHead); PIRP pendingIrp = CONTAINING_RECORD(pEntry, IRP, Tail.Overlay.ListEntry);//根据结构体某一成员的地址获取结构体的基地址// pendingIrp->IoStatus.Status = STATUS_SUCCESS; pendingIrp->IoStatus.Information = 0; IoCompleteRequest(pendingIrp, IO_NO_INCREMENT); DbgPrint("完成Irp操作\n"); } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开CleanUp\n"); return STATUS_SUCCESS; } NTSTATUS Close(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入Close\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开Close\n"); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT driver) { NTSTATUS status; UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDDKName"); UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); PDEVICE_OBJECT DeviceObject; DbgPrint("Driver Load\n"); status = IoCreateDevice(driver, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateDevice error\n"); return status; } status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateSymbolicLink error\n"); IoDeleteDevice(DeviceObject); return status; } InitializeListHead(&ListHead); driver->DriverStartIo = DriverStartio; driver->MajorFunction[IRP_MJ_CREATE] = Create; driver->MajorFunction[IRP_MJ_READ] = Read; driver->MajorFunction[IRP_MJ_CLEANUP] = CleanUp; driver->MajorFunction[IRP_MJ_CLOSE] = Close; DeviceObject->Flags |= DO_BUFFERED_IO; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; driver->DriverUnload = Unload; return STATUS_SUCCESS; }
3环代码
// syn_asy三环代码.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <stdio.h> #include <Windows.h> using namespace std; int main() { HANDLE hDevice = CreateFile(TEXT("\\\\.\\MyDDKLink"), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("打开设备失败 %d\n", GetLastError()); return -1; } DWORD dwret; OVERLAPPED ol = { NULL }; for (int i = 0; i < 100; i++) { ReadFile(hDevice, NULL, 0, &dwret, &ol); } CloseHandle(hDevice);//在内核会触发 MJ_cleanuP 与Close派遣函数// getchar(); return 0; }
 
0
 
 

四、自定义StartIo

迷迷糊糊的,毛啊   //代码好像写的有点问题。。。
#include <ntddk.h> LIST_ENTRY ListHead; KDEVICE_QUEUE DeviceQueue; VOID Unload(PDRIVER_OBJECT driver) { UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); IoDeleteSymbolicLink(&SymbolicLinkName); IoDeleteDevice(driver->DeviceObject); DbgPrint("Driver Unload\n"); } NTSTATUS Create(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("Create 成功\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } void DriverStartio( struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp ) { LARGE_INTEGER timeout = RtlConvertLongToLargeInteger(-10 * 100 * 1000); if (DeviceObject->CurrentIrp != Irp) { IoReleaseCancelSpinLock(Irp->Cancel); return; } DbgPrint("进入StartIo\n"); KeDelayExecutionThread(KernelMode, FALSE, &timeout); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); IoStartNextPacket(DeviceObject, TRUE); DbgPrint("离开StartIo\n"); } VOID CancelRoutine( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { DbgPrint("进入取消函数\n"); Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开取消函数\n"); } VOID MyStartIo(PDEVICE_OBJECT DeviceObject, PIRP Irp) { LARGE_INTEGER timeout = RtlConvertLongToLargeInteger(-10 * 3000 * 100); PIRP CurrentIrp = Irp; PKDEVICE_QUEUE_ENTRY QueueEntry = NULL; DbgPrint("进入MyStartIo\n"); while (TRUE) { CurrentIrp->IoStatus.Status = STATUS_SUCCESS; CurrentIrp->IoStatus.Information = 0; IoCompleteRequest(CurrentIrp, IO_NO_INCREMENT); QueueEntry = KeRemoveDeviceQueue(&DeviceQueue); if (QueueEntry == NULL) { break; } CurrentIrp = CONTAINING_RECORD(QueueEntry, IRP, Tail.Overlay.DeviceQueueEntry); KeDelayExecutionThread(KernelMode, FALSE, &timeout); } DbgPrint("离开MyStartIo\n"); } NTSTATUS Read(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入read\n"); //IoSetCancelRoutine(Irp, CancelRoutine); IoMarkIrpPending(Irp); if (!KeInsertDeviceQueue(&DeviceQueue, &Irp->Tail.Overlay.DeviceQueueEntry)) { MyStartIo(DeviceObject,Irp); } //IoStartPacket(DeviceObject, Irp, NULL, CancelRoutine);//将并行Irp串行处理// 将100个Irp自动插入一个链表中,串形处理// //InsertHeadList(&ListHead, &Irp->Tail.Overlay.ListEntry);//将Irp插入链表// //Irp->IoStatus.Status = STATUS_SUCCESS; //Irp->IoStatus.Information = 0; //IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开读函数\n"); return STATUS_PENDING; } NTSTATUS CleanUp(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入CleanUp\n"); while (!IsListEmpty(&ListHead)) { PLIST_ENTRY pEntry = RemoveHeadList(&ListHead); PIRP pendingIrp = CONTAINING_RECORD(pEntry, IRP, Tail.Overlay.ListEntry);//根据结构体某一成员的地址获取结构体的基地址// pendingIrp->IoStatus.Status = STATUS_SUCCESS; pendingIrp->IoStatus.Information = 0; IoCompleteRequest(pendingIrp, IO_NO_INCREMENT); DbgPrint("完成Irp操作\n"); } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开CleanUp\n"); return STATUS_SUCCESS; } NTSTATUS Close(PDEVICE_OBJECT DeviceObject, PIRP Irp) { DbgPrint("进入Close\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); DbgPrint("离开Close\n"); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT driver) { NTSTATUS status; UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDDKName"); UNICODE_STRING SymbolicLinkName = RTL_CONSTANT_STRING(L"\\??\\MyDDKLink"); PDEVICE_OBJECT DeviceObject; DbgPrint("Driver Load\n"); status = IoCreateDevice(driver, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateDevice error\n"); return status; } status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateSymbolicLink error\n"); IoDeleteDevice(DeviceObject); return status; } InitializeListHead(&ListHead); KeInitializeDeviceQueue(&DeviceQueue); driver->DriverStartIo = DriverStartio; driver->MajorFunction[IRP_MJ_CREATE] = Create; driver->MajorFunction[IRP_MJ_READ] = Read; driver->MajorFunction[IRP_MJ_CLEANUP] = CleanUp; driver->MajorFunction[IRP_MJ_CLOSE] = Close; DeviceObject->Flags |= DO_BUFFERED_IO; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; driver->DriverUnload = Unload; return STATUS_SUCCESS; }

 

// syn_asy三环代码.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <stdio.h> #include <Windows.h> using namespace std; DWORD WINAPI ThreadProc(PVOID Context) { DWORD dwRet; HANDLE hDevice = *(PHANDLE)Context; OVERLAPPED ol = { NULL }; ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); ReadFile(hDevice, NULL, 0, &dwRet, &ol); //Sleep(3000); //SetEvent(ol.hEvent); WaitForSingleObject(hDevice, INFINITE); printf("完成!!!\n"); return 0; } int main() { HANDLE hDevice = CreateFile(TEXT("\\\\.\\MyDDKLink"), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("打开设备失败 %d\n", GetLastError()); return -1; } for (int i = 0; i < 100; i++) { CreateThread(NULL, 0, ThreadProc, &hDevice, 0, NULL); } while (true) { Sleep(2000); } CloseHandle(hDevice);//在内核会触发 MJ_cleanuP 与Close派遣函数// getchar(); return 0; }

 


__EOF__

本文作者_TLSN
本文链接https://www.cnblogs.com/lordtianqiyi/articles/16135868.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   TLSN  阅读(285)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示