内核事件KEVENT(同步)
转载请您注明出处:http://www.cnblogs.com/lsh123/p/7358702.html
一.驱动程序与驱动程序的事件交互 IoCreateNotificationEvent ———> IoCreateNotificationEvent
在内核驱动中可以通过给某个内核对象创建一个命名对象,然后在另一个驱动中通过名字来获取这个对象,然后操作它来实现两个驱动之间的内核对象的通讯,针对事件对象来说,要实现两个驱动交互事件对象,通过这样几步:
1. 在驱动Server中调用IoCreateNotificationEvent或者IoCreateSynchronizationEvent来创建一个通知事件对象或者同步事件对象
2. 在驱动Client中调用
IoCreateNotificationEvent或者IoCreateSynchronizationEvent获取已经有名字的内核对象的句柄 ,设置事件的激发状态
(3. 在驱动B中调用ObReferenceObjectByHandle根据上面两个函数返回的句柄来获取A中的事件对象,并操作它)
源代码:
Server.c
#include <ntifs.h>
#define EVENT_NAME L"\\BaseNamedObjects\\ServerKernelEvent" void ThreadProcedure(PVOID ParameterData);
VOID DriverUnload(PDRIVER_OBJECT DriverObject); PKEVENT __Event;
HANDLE __EventHandle; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING EventName;
PDEVICE_OBJECT DeviceObject = NULL;
HANDLE ThreadHandle = NULL;
CLIENT_ID ClientID = { 0 }; DriverObject->DriverUnload = DriverUnload; RtlInitUnicodeString(&EventName, EVENT_NAME);
DriverObject->DriverUnload = DriverUnload; __Event = IoCreateNotificationEvent(
&EventName, //自定义事件名
&__EventHandle); //返回的事件句柄
KeResetEvent(__Event); Status = PsCreateSystemThread(&ThreadHandle, 0, NULL, NtCurrentProcess(), &ClientID,
(PKSTART_ROUTINE)ThreadProcedure,NULL); return Status;
} void ThreadProcedure(PVOID ParameterData)
{
NTSTATUS Status;
KeWaitForSingleObject(__Event, Executive, KernelMode, FALSE, NULL);
DbgPrint("ThreadProcedure() Exit\r\n");
PsTerminateSystemThread(STATUS_SUCCESS);
} VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n");
if (__EventHandle != NULL)
{
KeClearEvent(__Event); ZwClose(__EventHandle); __EventHandle = NULL;
__Event = NULL;
} }
Client.c
#include <ntifs.h> #define EVENT_NAME L"\\BaseNamedObjects\\ServerKernelEvent"
VOID DriverUnload(PDRIVER_OBJECT DriverObject); PKEVENT __Event;
HANDLE __EventHandle;
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING EventName;
PDEVICE_OBJECT DeviceObject = NULL;
DriverObject->DriverUnload = DriverUnload;
RtlInitUnicodeString(&EventName, EVENT_NAME);
__Event = IoCreateNotificationEvent(&EventName, &__EventHandle); //获取已经有名字的内核对象的句柄
KeSetEvent(__Event, IO_NO_INCREMENT, FALSE); //设置激发态
return Status;
} VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n");
if (__EventHandle != NULL)
{
KeClearEvent(__Event);
ZwClose(__EventHandle);
__EventHandle = NULL;
__Event = NULL;
} }
二.驱动程序与应用程序的事件交互(驱动程序创建事件——>应用程序设置事件)IoCreateNotificationEvent ——> OpenEvent
应用程序中创建的事件和在内核模式下创建的事件对象,本质上是同一个东西。在用户模式下,它用句柄代表,在内核模式下,它用KEVENT数据结构代表。
在应用程序中,所有内核对象都不会被用户看到,用户看到的只是代表内核对象的对象句柄。
__Event = IoCreateNotificationEvent(&EventName, &__EventHandle); //DriverEntry 进程回调通知
EventHandle = OpenEvent(
SYNCHRONIZE, //请求访问权限
FALSE, // 不继承
L"Global\\Ring0KernelEvent"); //事件对象名称
1.驱动程序IoCreateNotificationEvent创建事件
2.应用程序OpenEvent得到事件句柄
Ring3.cpp
// Ring3(设置).cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std; int main()
{ HANDLE EventHandle = NULL; while (TRUE)
{
EventHandle = OpenEvent(
SYNCHRONIZE, //请求访问权限
FALSE, // 不继承
L"Global\\Ring0KernelEvent"); //事件对象名称 if (EventHandle == NULL)
{
continue;
} break;
} cout << "Ring3等待" << endl;
while (TRUE)
{ int Index = WaitForSingleObject(EventHandle, 3000); Index = Index - WAIT_OBJECT_0; if (Index == WAIT_TIMEOUT)
{ //注意这里当驱动卸载并关闭事件时事件对象是不能够得到及时的销毁 因为应用层占用了该对象
//所以我们长时间等待不到授信 就关闭并重新打开
if (EventHandle != NULL)
{
CloseHandle(EventHandle);
EventHandle = NULL;
EventHandle = OpenEvent(SYNCHRONIZE, FALSE, L"Global\\Ring0KernelEvent"); if (EventHandle == NULL)
{
cout << "对象已经不存在" << endl;
break;
}
} continue;
} if (Index == 0) //有信号状态
{
cout << "Ring0触发Ring3" << endl;
} if (Index == WAIT_FAILED)
{
break;
} Sleep(1);
} cout << "Input AnyKey To Exit" << endl; getchar();
if (EventHandle != NULL)
{
CloseHandle(EventHandle);
EventHandle = NULL; }
return 0;
}
Ring0.c
#include <ntifs.h> #define EVENT_NAME L"\\BaseNamedObjects\\Ring0KernelEvent"
VOID DriverUnload(PDRIVER_OBJECT DriverObject); PKEVENT __Event;
HANDLE __EventHandle; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING EventName; RtlInitUnicodeString(&EventName, EVENT_NAME);
DriverObject->DriverUnload = DriverUnload; __Event = IoCreateNotificationEvent(&EventName, &__EventHandle); //DriverEntry 进程回调通知 return Status;
} VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n"); if (__EventHandle != NULL)
{
KeClearEvent(__Event); ZwClose(__EventHandle); __EventHandle = NULL;
__Event = NULL;
} }
三.应用程序与驱动程序的事件交互(应用程序创建事件——>驱动程序设置事件) DeviceIoControl ——> ObReferenceObjectByHandle
要将用户模式下创建的事件传递给驱动程序,可以用DeviceIoControl API函数。DDK提供了内核函数将句柄转化为指针,该函数如下:
NTSTATUS
ObReferenceObjectByHandle(
IN HANDLE Handle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_TYPE ObjectType OPTIONAL,
IN KPROCESSOR_MODE AccessMode,
OUT PVOID *Object,
OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL
);
ObReferenceObjectByHandle函数在得到指针的同时,会为对象的指针维护一个计数。每次调用ObReferenceObjectByHandle函数时会使计数加1.因此为了计数平衡,在使用完 ObReferenceObjectByHandle函数后,需要调用如下函数:
VOID
ObDereferenceObject(
IN PVOID Object
);
ObDereferenceObject函数使计数减一。
1.应用程序通过符号链接名由CreateFile函数得到设备句柄
HANDLE DeviceHandle = CreateFile(DeviceLinkName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
2.应用程序CreateEvent创建事件
//创建自动重置的,初始为未激发的事件对象
EventHandle[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
3.应用程序通过DeviceIoControl 函数将用户模式下创建的事件传递给驱动程序
//调用DeviceIoControl把事件句柄传进内核
IsOk = DeviceIoControl(DeviceHandle, CTL_EVENT,
EventHandle,
sizeof(HANDLE) * 2,
NULL,
0,
&ReturnLength,
NULL);
//调用DeviceIoControl,通知驱动程序设置事件激发状态
IsOk = DeviceIoControl(DeviceHandle, CTL_SET_EVENT,
NULL,
0,
NULL,
0,
&ReturnLength,
NULL);
4.驱动程序通过ObReferenceObjectByHandle将句柄转化为PKEVENT指针
/把句柄转化为KEvent结构
Status = ObReferenceObjectByHandle(
(HANDLE)EventHandle[i], //Irp->AssociatedIrp.SystemBuffer 句柄
SYNCHRONIZE, //权限
*ExEventObjectType, //对象类型,对象类型
KernelMode, //访问模式分KernelMode
&__KernelEvent[i], //指向映射句柄对象的指针
NULL);
Ring3.cpp
#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std; #define CTL_EVENT \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS) #define CTL_SET_EVENT \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x831,METHOD_BUFFERED,FILE_ANY_ACCESS) HANDLE SeOpenDeviceObject(WCHAR* DeviceLinkName);
DWORD WINAPI ThreadProcedure(LPVOID ParameterData);
int main()
{
HANDLE DeviceHandle = SeOpenDeviceObject(L"\\\\.\\Ring0DeviceLinkName");
if (DeviceHandle == NULL)
{
return 0;
} ULONG i = 0;
HANDLE EventHandle[3] = { 0 };
for (i = 0; i < 3; i++)
{
//创建自动重置的,初始为未激发的事件对象
EventHandle[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
}
BOOL IsOk = 0;
DWORD ReturnLength = 0;
//调用DeviceIoControl把事件句柄传进内核
IsOk = DeviceIoControl(DeviceHandle, CTL_EVENT,
EventHandle,
sizeof(HANDLE) * 2,
NULL,
0,
&ReturnLength,
NULL); if (IsOk == FALSE)
{
goto Exit;
} HANDLE ThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProcedure,
(PVOID)EventHandle, 0, NULL);
//调用DeviceIoControl,通知驱动程序设置事件激发状态
IsOk = DeviceIoControl(DeviceHandle, CTL_SET_EVENT,
NULL,
0,
NULL,
0,
&ReturnLength,
NULL); if (IsOk == FALSE)
{
cout << "Send IoCode Error" << endl;
SetEvent(EventHandle[2]);
WaitForSingleObject(ThreadHandle, INFINITE);
goto Exit;
} WaitForSingleObject(ThreadHandle, INFINITE); Exit:
{
for (i = 0; i < 3; i++)
{
if (EventHandle[i] != NULL)
{
CloseHandle(EventHandle[i]);
EventHandle[i] = NULL;
}
}
if (ThreadHandle != NULL)
{
CloseHandle(ThreadHandle);
ThreadHandle = NULL;
}
if (DeviceHandle != NULL)
{
CloseHandle(DeviceHandle);
DeviceHandle = NULL;
}
} printf("卸载驱动后 Input AnyKey To Exit\r\n");
getchar();
getchar(); return 0; } DWORD WINAPI ThreadProcedure(LPVOID ParameterData)
{ cout << "Ring3等待" << endl;
//等待三个之中有激发状态的信号
DWORD Index = WaitForMultipleObjects(3, (HANDLE*)ParameterData, FALSE, INFINITE);
Index = Index - WAIT_OBJECT_0;
if (Index == 2) //0 1 2
{
printf("ThreadProcedure() Exit\r\n");
return 0;
}
cout << "Ring0触发Ring3" << endl;
cout << "输入任意键Ring3触发Ring0" << endl; getchar();
getchar(); SetEvent(((HANDLE*)ParameterData)[1]); //Ring0中KeWaitForSingleObject响应
printf("ThreadProcedure() Exit\r\n");
return 0;
} HANDLE SeOpenDeviceObject(WCHAR* DeviceLinkName)
{
HANDLE DeviceHandle = CreateFile(DeviceLinkName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL); if (DeviceHandle == INVALID_HANDLE_VALUE)
{
return NULL;
} return DeviceHandle; }
Ring0.c
#include <ntifs.h> #define CTL_EVENT \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define CTL_SET_EVENT \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x831,METHOD_BUFFERED,FILE_ANY_ACCESS) #define DEVICE_OBJECT_NAME L"\\Device\\Ring0DeviceObjectName"
//设备与设备之间通信
#define DEVICE_LINK_NAME L"\\DosDevices\\Ring0DeviceLinkName"
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp);
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS Ring3EventHandleToRing0KernelEvent(HANDLE* EventHandle, ULONG_PTR EventHandleCount); PKEVENT __KernelEvent[20] = { 0 };
ULONG_PTR __KernelEventCount = 0; extern
POBJECT_TYPE* ExEventObjectType; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
UNREFERENCED_PARAMETER(RegisterPath);
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING DeviceObjectName;
UNICODE_STRING DeviceLinkName;
ULONG i;
DriverObject->DriverUnload = DriverUnload; //创建设备对象名称
RtlInitUnicodeString(&DeviceObjectName, DEVICE_OBJECT_NAME); //创建设备对象
Status = IoCreateDevice(DriverObject, NULL,
&DeviceObjectName,
FILE_DEVICE_UNKNOWN,
0, FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
//创建设备连接名称
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME); //将设备连接名称与设备名称关联
Status = IoCreateSymbolicLink(&DeviceLinkName, &DeviceObjectName); if (!NT_SUCCESS(Status))
{
IoDeleteDevice(DeviceObject);
return Status;
}
//设计符合我们代码的派遣历程
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = PassThroughDispatch; //函数指针
}
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch; return STATUS_SUCCESS;
} NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{ NTSTATUS Status = STATUS_UNSUCCESSFUL;
ULONG_PTR Information = 0;
PVOID InputData = NULL;
ULONG InputDataLength = 0;
PVOID OutputData = NULL;
ULONG OutputDataLength = 0;
ULONG IoControlCode = 0;
PEPROCESS EProcess = NULL;
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp); //Irp堆栈
IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
InputData = Irp->AssociatedIrp.SystemBuffer;
OutputData = Irp->AssociatedIrp.SystemBuffer;
InputDataLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
switch (IoControlCode)
{
case CTL_EVENT:
{ if (InputData != NULL&&InputDataLength == sizeof(HANDLE)*2)
{ Status = Ring3EventHandleToRing0KernelEvent((HANDLE*)InputData, InputDataLength / sizeof(HANDLE)); } Information = 0; break; } case CTL_SET_EVENT:
{ DbgPrint("Ring0触发Ring3\r\n");
KeSetEvent(__KernelEvent[0], IO_NO_INCREMENT, FALSE); //Ring3层线程中WaitForMultipleObjects响应 DbgPrint("Ring0等待\r\n");
Status = KeWaitForSingleObject(__KernelEvent[1],
Executive, KernelMode, FALSE, NULL); //注意这里的最后一个参数NULL 是永久等待 DbgPrint("Ring3触发Ring0\r\n"); Information = 0;
break; } default:
{ Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0; break;
}
} Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = Information;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
} NTSTATUS Ring3EventHandleToRing0KernelEvent(HANDLE* EventHandle, ULONG_PTR EventHandleCount)
{
NTSTATUS Status = STATUS_SUCCESS;
PULONG_PTR HandleArray = NULL;
ULONG i = 0; if (EventHandle==NULL)
{
return STATUS_UNSUCCESSFUL;
}
__KernelEventCount = EventHandleCount;
for (i = 0; i < EventHandleCount; i++)
{
//把句柄转化为KEvent结构
Status = ObReferenceObjectByHandle(
(HANDLE)EventHandle[i], //Irp->AssociatedIrp.SystemBuffer 句柄
SYNCHRONIZE, //权限
*ExEventObjectType, //对象类型,对象类型
KernelMode, //访问模式分KernelMode
&__KernelEvent[i], //指向映射句柄对象的指针
NULL);
if (!NT_SUCCESS(Status))
{
break;
}
} if (Status != STATUS_SUCCESS)
{
for (i = 0; i < EventHandleCount; i++)
{
if (__KernelEvent[i] != NULL)
{
//递减计数
ObDereferenceObject(__KernelEvent[i]); __KernelEvent[i] = NULL;
}
}
}
return Status;
} NTSTATUS PassThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS; //LastError()
Irp->IoStatus.Information = 0; //ReturnLength
IoCompleteRequest(Irp, IO_NO_INCREMENT); //将Irp返回给Io管理器
return STATUS_SUCCESS;
} VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
int i = 0;
UNICODE_STRING DeviceLinkName;
PDEVICE_OBJECT v1 = NULL;
PDEVICE_OBJECT DeleteDeviceObject = NULL; RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
IoDeleteSymbolicLink(&DeviceLinkName); DeleteDeviceObject = DriverObject->DeviceObject;
while (DeleteDeviceObject != NULL)
{
v1 = DeleteDeviceObject->NextDevice;
IoDeleteDevice(DeleteDeviceObject);
DeleteDeviceObject = v1;
}
for (i = 0; i < __KernelEventCount; i++)
{
if (__KernelEvent[i] != NULL)
{
ObDereferenceObject(__KernelEvent[i]); __KernelEvent[i] = NULL;
}
}
}
内核事件KEVENT(同步)的更多相关文章
- Windows API学习---线程与内核对象的同步
前言 若干种内核对象,包括进程,线程和作业.可以将所有这些内核对象用于同步目的.对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中.这种状态的切换是由Microsoft为 ...
- C#异步编程(三)内核模式线程同步
其实,在开发过程中,无论是用户模式的同步构造还是内核模式,都应该尽量避免.因为线程同步都会造成阻塞,这就影响了我们的并发量,也影响整个应用的效率.不过有些情况,我们不得不进行线程同步. 内核模式 wi ...
- Windows核心编程 第九章 线程与内核对象的同步(上)
第9章 线程与内核对象的同步 上一章介绍了如何使用允许线程保留在用户方式中的机制来实现线程同步的方法.用户方式同步的优点是它的同步速度非常快.如果强调线程的运行速度,那么首先应该确定用户方式的线程同步 ...
- Windows核心编程 第九章 线程与内核对象的同步(下)
9.4 等待定时器内核对象 等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象.它们通常用来在某个时间执行某个操作. 若要创建等待定时器,只需要调用C r e a t e Wa i ...
- 在Spring中使用异步事件实现同步事务
结合Scala+Spring,我们将采取一个很简单的场景:下订单,然后发送一封电子邮件. 编制一个服务: @Serviceclass OrderService @Autowired() (orderD ...
- ie低版本内核事件兼容问题(事件绑定,绑定事件自动执行,文档模式问题)
问题情况 搜狗等,兼容模式下,以前前端写的点击事件的代码没有, 后来一看是因为兼容模式为9,导致点击事件失效 解决办法,步骤 1,处理绑定事件兼容问题 ie低版本绑定事件只支持attactevent, ...
- C# 委托 、事件、同步、异步知识点归纳
一.委托 基本用法: 1.声明一个委托类型.委托就像是‘类'一样,声明了一种委托之后就可以创建多个具有此种特征的委托.(特征,指的是返回值.参数类型) public delegate void Som ...
- linx 内核 并发与同步 1
内核并发来源: 1.硬件中断和异常:中断服务程序和被中断的进程可能发生并发访问资源 2.软中断和tasklet,软中断和taklet随时都可能倍调度执行,从而打断当前正在执行 进程的上下文. 3.内核 ...
- python-Event事件线程同步和互斥
#!/usr/bin/python #coding=utf-8 #用于线程间通信,通过事件标识控制 import threading from time import sleep,ctime def ...
随机推荐
- 奖券数目|2015年蓝桥杯B组题解析第一题-fishers
奖券数目 有些人很迷信数字,比如带"4"的数字,认为和"死"谐音,就觉得不吉利. 虽然这些说法纯属无稽之谈,但有时还要迎合大众的需求.某抽奖活动的奖券号码是5位 ...
- shell:遍历目录和子目录的所有文件及匹配文件内容到日志
过滤文件内网 #!/bin/bash function getdir(){ ` do dir_or_file=$"/"$element if [ -d $dir_or_file ] ...
- Ubuntu16.04 无法连接WiFi
在安装完 ns-3.25 之后,着手开始准备 Eclipse 的安装,打开了 Firefox游览器 准备上网的时候,发现网络没有正常连接. 刚刚开始怀疑的是,并没有连接上网络. 于是打开了终端,pin ...
- js分号的重要性
js中语句末尾可以不加分号, 很多时候在做练习或写几个页面时,我都是不会加的.虽然知道加了会好一点.但就是觉得很敲一句就要多按一次分号键(;)来加分号,而不加也不怎么样,然后就不想加了. 也听说在对j ...
- UVa 1626 括号序列(矩阵连乘)
https://vjudge.net/problem/UVA-1626 题意: 输入一个由 "(" . ")" . "[" . " ...
- UVa 1590 IP网络(简单位运算)
Description Alex is administrator of IP networks. His clients have a bunch of individual IP addres ...
- 使用rviz 查看远程主机
一.安装好ros环境 https://www.cnblogs.com/sea-stream/p/9809590.html 二.配置参数 vim ~/.bashrc #输入内容 export ROS_H ...
- quality center 支持的平台
- Selenium 对窗口对HTML的操作举例
- Unity搭建简单的图片服务器
具体要实现的目标是:将图片手动拷贝到服务器,然后在Unity中点击按钮将服务器中的图片加载到Unity中. 首先简答解释下 WAMP(Windows + Apache + Mysql + PHP),一 ...