好记性不如烂笔头。上次简单研究了一下VRPN的代码结构,梳理了一下创建自定义VRPN设备的总体流程。本来打算研究一下自定义tracker类设备,把optitrack添加进去,但是临时需要获取飞行手柄的数据,所以就先拿手柄进行测试,尝试完整添加一个自定义的设备进去,这篇博客记录了整个添加的过程。

本次添加的飞行手柄型号为Saitek X52,包含一个油门、一个操纵杆,还有一个脚踏,其中操纵杆和油门是级联在一起通过一个USB接到电脑上,脚踏式独立一个USB接到电脑上,因此对于计算机而言,连接的设备只有两个,一个是油门和操纵杆的组合,一个是脚踏。

1.获取设备数据

为了在VRPN中添加自定义设备,首先需要自己用代码获取到对应的数据。对于本次需要添加的设备,使用DirectxInput8获取(网上相关的博客很多就不详细介绍了),这部分我在网上找了一段代码改了一下(源代码只读取了一个设备,我这有两个,主要是增加了连接多个设备的内容),直接上代码,解释都在注释里。

1.1 CJoystick.h代码:

#pragma once
#pragma once#include "dinput.h"
#include "dinputd.h"
#include <string>
using namespace std;#define DIRECTINPUT_VERSION 0x0800
#define DI8DEVCLASS_GAMECTRL 4 //扫描游戏控制器#pragma comment(lib,"dxguid.lib")
#pragma comment(lib,"dinput8.lib")/********************************************************************\
功能描述:游戏手柄控制类
\********************************************************************/
class CJoystick
{
public:CJoystick(int DeviceID);~CJoystick(void);
public:string                   GetLastErrMsg();BOOL                    Init(LONG nMin = -1024, LONG nMax = 1024); //初始化函数DIJOYSTATE* PollDevice(); // 轮循设备函数,在轮循过程中读取设备状态//枚举设备static BOOL CALLBACK   DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi, VOID* pvRef); //枚举对象static BOOL CALLBACK   EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext);
private://一般的成员变量HINSTANCE              m_hInstance;    // 实例句柄LPDIRECTINPUT8           m_lpDI;         // DI8接口指针LPDIRECTINPUTDEVICE8  m_lpDIDevice;   // DIDevice8接口指针DIJOYSTATE              m_diJs;         //存储Joystick状态信息GUID                    JoystickGUID;   //GUID,设备唯一编码,通过这个值连接设备int                     DeviceID;//想要连接的设备编号,即等待连接的设备序列中的第几个设备,在构造函数中确定LONG                  m_nMax;         //最小值LONG                   m_nMin;         //最大值string                 m_errmsg;
};

1.2 CJoystick.cpp代码:

#include "CJoystick.h"#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }int DeviceNum = 0;//在设备序列中排在目标设备前的设备数
int DeviceFound = 0;//在枚举中找到的设备数目
struct DI_ENUM_CONTEXT
{DIJOYCONFIG* pPreferredJoyCfg;bool bPreferredJoyCfgValid;
};CJoystick::CJoystick(int DeviceID)
{m_lpDIDevice = NULL;m_lpDI = NULL;this->DeviceID = DeviceID;m_hInstance = GetModuleHandle(NULL); //获取实例句柄m_nMin = -1024;m_nMax = +1024;
}CJoystick::~CJoystick(void)
{//释放DI和DIDevice对象if (m_lpDIDevice){m_lpDIDevice->Unacquire();m_lpDIDevice->Release();m_lpDIDevice = NULL;}if (m_lpDI){m_lpDI->Release();m_lpDI = NULL;}
}
//初始化
BOOL CJoystick::Init(LONG nMin, LONG nMax)
{HRESULT hr;//m_hWnd = hWnd ;DeviceNum = DeviceID;DeviceFound = 0;m_nMin = nMin;m_nMax = nMax;//1.建立DI8接口if (NULL == m_lpDI){hr = DirectInput8Create(m_hInstance,DIRECTINPUT_VERSION,(REFIID)IID_IDirectInput8,(void**)&m_lpDI, //接口取值NULL);if FAILED(hr){m_errmsg = "Create 失败 - in CDIJoystick::Initialise";return false;}}DIJOYCONFIG PreferredJoyCfg = { 0 };DI_ENUM_CONTEXT enumContext;enumContext.pPreferredJoyCfg = &PreferredJoyCfg;enumContext.bPreferredJoyCfgValid = true;//是否为标准手柄,正常是需要判断的,在这里我们只用来接收标准手柄,所以就直接设置了IDirectInputJoyConfig8* pJoyConfig = NULL;if (FAILED(hr = m_lpDI->QueryInterface(IID_IDirectInputJoyConfig8, (void**)&pJoyConfig))){m_errmsg = "获取接口失败";//MessageBox(NULL, L"joystick 获取接口失败 失败", L"worning", MB_OK);return false;}PreferredJoyCfg.dwSize = sizeof(PreferredJoyCfg);if (SUCCEEDED(pJoyConfig->GetConfig(0, &PreferredJoyCfg, DIJC_GUIDINSTANCE))) // This function is expected to fail if no joystick is attached//enumContext.bPreferredJoyCfgValid = false;SAFE_RELEASE(pJoyConfig);//2.枚举设备hr = m_lpDI->EnumDevices(DI8DEVCLASS_GAMECTRL, //扫描游戏控制器DIEnumDevicesCallback, //回调函数,连接多个设备的代码在这个回调函数里改&enumContext,//将枚举的设备信息写入上下文DIEDFL_ATTACHEDONLY); //扫描安装好的和连接好的设备//printf("Device Found %d\n", DeviceFound);//printf("DeviceID %d\n\n", this->DeviceID);if (DeviceFound < this->DeviceID) {//需要连接的设备编号设置过大,未能找到足够的设备m_errmsg = "无法检测到此设备";return false;}if FAILED(hr){//OutputDebugString("枚举设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举设备失败";//MessageBox(NULL, L"joystick 枚举设备失败", L"worning", MB_OK);return false;}//3.创建DI8设备if (!m_lpDIDevice){hr = m_lpDI->CreateDevice(enumContext.pPreferredJoyCfg->guidInstance, &m_lpDIDevice, NULL);if FAILED(hr){//OutputDebugString("创建设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "创建设备失败";//MessageBox(NULL, L"joystick 创建设备失败", L"worning", MB_OK);return false;}}//设置协作等级—— 前台模式 | 独占模式  (后台/非独占)/*DISCL_BACKGROUND|*///hr = m_lpDIDevice ->SetCooperativeLevel(m_hWnd,DISCL_FOREGROUND|DISCL_EXCLUSIVE);//if FAILED(hr) //{//  //OutputDebugString("设置协作等级失败 - in CDIJoystick::Initialise\n");// m_errmsg="设置协作等级失败";//   MessageBox(NULL, L"joystick 设置协作等级失败", L"worning", MB_OK);//    if (m_hWnd==NULL)//       MessageBox(NULL, L"窗口句柄为空", L"worning", MB_OK);//   return false; //}//4.设置数据格式hr = m_lpDIDevice->SetDataFormat(&c_dfDIJoystick);if FAILED(hr){//OutputDebugString("设置数据格式失败 - in CDIJoystick::Initialise\n");m_errmsg = "设置数据格式失败";//MessageBox(NULL, L"joystick 设置数据格式失败", L"worning", MB_OK);return false;}hr = m_lpDIDevice->Acquire();//5.枚举对象hr = m_lpDIDevice->EnumObjects(EnumObjectsCallback, (VOID*)this, DIDFT_ALL);if FAILED(hr){//OutputDebugString("枚举对象失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举对象失败";//MessageBox(NULL, L"joystick 枚举对象失败", L"worning", MB_OK);return false;}return true;
}BOOL CALLBACK CJoystick::DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi, VOID* pvRef)
{DI_ENUM_CONTEXT* pEnumContext = (DI_ENUM_CONTEXT*)pvRef;if (pEnumContext->bPreferredJoyCfgValid) {pEnumContext->pPreferredJoyCfg->guidInstance = lpddi->guidInstance;    //记录当前设备信息,用来创建设备接口DeviceFound++;}if (DeviceNum > 1) {      DeviceNum --;return DIENUM_CONTINUE;//返回值为DIENUM_CONTINUE时,会继续执行一遍这个cllback函数,//如果仍有设备在序列中,则会读取下一个设备的信息,//上下文(enumContext)中的当前记录的信息则会被覆盖成新设备的信息,//如果没有新的可读设备,则会自动返回DIENUM_STOP,停止枚举。}else {        return DIENUM_STOP;//停止枚举,最终记录的设备信息为停止枚举时记录的设备。}}BOOL CALLBACK CJoystick::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext)
{HRESULT hr;CJoystick* js = (CJoystick*)pContext; //首先取得JS对象指针//设置游戏杆输入特性if (pdidoi->dwType & DIDFT_AXIS) //如果枚举的对象为轴{DIPROPRANGE diprg; //设置轴范围结构diprg.diph.dwSize = sizeof(DIPROPRANGE);diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);diprg.diph.dwHow = DIPH_BYID;diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴diprg.lMin = js->m_nMin; //最小值diprg.lMax = js->m_nMax; //最大值// 设置轴范围 hr = js->m_lpDIDevice->SetProperty(DIPROP_RANGE, &diprg.diph);if (FAILED(hr)){//OutputDebugString("设置轴范围失败 - in CDIJoystick::EnumObjectsCallback\n");js->m_errmsg = "设置轴范围失败 - in CDIJoystick::EnumObjectsCallback";return DIENUM_STOP;}//设置死区属性,如果你使用的是电平式的游戏手柄,需要注释掉一下部分/*DIPROPDWORD dipdw; //死区结构dipdw.diph.dwSize = sizeof( dipdw );dipdw.diph.dwHeaderSize = sizeof( dipdw.diph );diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴dipdw.diph.dwHow = DIPH_DEVICE;dipdw.dwData = 1000; //10%的死区hr = js->m_lpDIDevice->SetProperty(DIPROP_DEADZONE, &dipdw.diph);if( FAILED(hr)){OutputDebugString("设置死区失败 - in CDIJoystick::EnumObjectsCallback\n");return DIENUM_STOP;}*/}return DIENUM_CONTINUE;
}//获取游戏手柄(定时调用)
DIJOYSTATE* CJoystick::PollDevice()
{HRESULT hr;//DIJOYSTATE *pdjs = NULL;if (NULL == m_lpDI || NULL == m_lpDIDevice) //未获得设备return NULL;hr = m_lpDIDevice->Poll(); // 轮循设备读取当前状态if (FAILED(hr)){// 输入流中断,不能通过轮循获得任何状态值。// 所以不需要任何重置,只要再次获得设备就行。hr = m_lpDIDevice->Acquire();while (hr == DIERR_INPUTLOST){static int iCount = 0;//if (iCount>30) exit(-1); //累积30次获取设备失败,退出程序。if (iCount > 30)return NULL;iCount++;//OutputDebugString("丢失设备,轮循失败 - in CJoystick::PollDevice\n");m_errmsg = "丢失设备,轮循失败 - in CJoystick::PollDevice";hr = m_lpDIDevice->Acquire();if (SUCCEEDED(hr)) iCount = 0;} // hr也许为其他的错误.//return &m_diJs; return NULL;}// 获得输入状态,存储到成员变量 m_diJs 中if (FAILED(hr = m_lpDIDevice->GetDeviceState(sizeof(DIJOYSTATE), &m_diJs)))return NULL; // 在轮循过程中设备将为 已获得 状态return &m_diJs;
}string  CJoystick::GetLastErrMsg()
{return m_errmsg;
}

1.3 主函数

#include <iostream>
#include <stdio.h>
#include "CJoystick.h"
#include <vector>
int main()
{std::vector<CJoystick>joysticks;//根据需求创建多个joystick对象for (int i = 0; i < 2; i++) {joysticks.push_back(CJoystick(i+1));//创建两个实例  }for (int i = 0; i < 2; i++) {//对两个实例进行初始化。//注意,测试的时候发现当创建多个实例时,不能采取对每个设备顺序执行创建实例+初始化的方式//必须将所有设备先创建出来,再统一执行初始化函数joysticks[i].Init();}if (joysticks.size() == 0) {std::cout << "No Device Found";return -1;}//创建设备状态存储变量DIJOYSTATE* pjs = joysticks[0].PollDevice();DIJOYSTATE* pjs2 = joysticks[1].PollDevice();std::cout << "try to find device\n";while (1) {//读取数据 pjs = joysticks[0].PollDevice();pjs2 = joysticks[1].PollDevice();if (pjs){//获取按键信息for (int i = 0; i < 32; i++){if (pjs2->rgbButtons[i] & 0x80){//此时的i值就是按下的按键值cout << "botton"<< i <<"triggered"<<endl;}}//获取模拟量信息cout << "Lx: " <<pjs->lX<<endl;cout << "LY: " << pjs->lY << endl;if (pjs2->lRz > 200) {cout << "Lx: " << pjs2->lRz << endl;}}}
}

1.4 DirectxInput8 Joystick数据结构

在函数里,我使用了DIJOYSTATE作为存储数据容器的数据类型,这个数据类型包含了如下的定义:

typedef struct DIJOYSTATE {LONG    lX;                     /* x-axis position              */LONG    lY;                     /* y-axis position              */LONG    lZ;                     /* z-axis position              */LONG    lRx;                    /* x-axis rotation              */LONG    lRy;                    /* y-axis rotation              */LONG    lRz;                    /* z-axis rotation              */LONG    rglSlider[2];           /* extra axes positions         */DWORD   rgdwPOV[4];             /* POV directions               */BYTE    rgbButtons[32];         /* 32 buttons                   */
} DIJOYSTATE, *LPDIJOYSTATE;

有32个按钮和若干模拟量,可以自行获取。

如果需要更多的数据,DXInput8还提供了DIJOYSTATE2的数据结构,但是我不知道怎么去Poll出来,有需要的可以研究一下,DIJOYSTATE2结构如下:

typedef struct DIJOYSTATE2 {LONG    lX;                     /* x-axis position              */LONG    lY;                     /* y-axis position              */LONG    lZ;                     /* z-axis position              */LONG    lRx;                    /* x-axis rotation              */LONG    lRy;                    /* y-axis rotation              */LONG    lRz;                    /* z-axis rotation              */LONG    rglSlider[2];           /* extra axes positions         */DWORD   rgdwPOV[4];             /* POV directions               */BYTE    rgbButtons[128];        /* 128 buttons                  */LONG    lVX;                    /* x-axis velocity              */LONG    lVY;                    /* y-axis velocity              */LONG    lVZ;                    /* z-axis velocity              */LONG    lVRx;                   /* x-axis angular velocity      */LONG    lVRy;                   /* y-axis angular velocity      */LONG    lVRz;                   /* z-axis angular velocity      */LONG    rglVSlider[2];          /* extra axes velocities        */LONG    lAX;                    /* x-axis acceleration          */LONG    lAY;                    /* y-axis acceleration          */LONG    lAZ;                    /* z-axis acceleration          */LONG    lARx;                   /* x-axis angular acceleration  */LONG    lARy;                   /* y-axis angular acceleration  */LONG    lARz;                   /* z-axis angular acceleration  */LONG    rglASlider[2];          /* extra axes accelerations     */LONG    lFX;                    /* x-axis force                 */LONG    lFY;                    /* y-axis force                 */LONG    lFZ;                    /* z-axis force                 */LONG    lFRx;                   /* x-axis torque                */LONG    lFRy;                   /* y-axis torque                */LONG    lFRz;                   /* z-axis torque                */LONG    rglFSlider[2];          /* extra axes forces            */
} DIJOYSTATE2, *LPDIJOYSTATE2;

2. VRPN添加设备

在成功获取设备信息后,我们现在就可以将代码移植到VRPN中。上一篇中我添加了一个叫MyMouse的设备,因为懒得再从新添加一遍各种文件和代码,所以直接在MyMouse中修改。不同于Mouse这种不需要参数的设备,我们需要指定连接设备的数目,所以需要利用Config文件中的参数来传递需要连接设备数目的参数值。示例配置如下,MyMouse0为设备实例的名称,2代表我们需要连接两个设备。

vrpn_MyMouse MyMouse0 2

2.1 vrpn_Generic_server_object代码修改

这部分代码的修改主要集中在setup_MyMouse函数的修改。主要是读取并解析Config文件中的设置参数,并将其传递到设备的构造函数中,修改后的代码如下:

int vrpn_Generic_Server_Object::setup_MyMouse(char *&pch, char *line,FILE * /*config_file*/)
{char s2[LINESIZE];int count = 0;//Number of joystickVRPN_CONFIG_NEXT();// Get the arguments (class, Mymouse_name)int ParamCount = sscanf(pch, "%511s%d", s2, &count);if (ParamCount < 1) {fprintf(stderr, "Bad vrpn_MyMouse line: %s\n", line);    return -1;}if (count < 1) {printf("Device Number Error");return -1;}// Open the boxif (verbose) {printf("Opening vrpn_MyMouse: %s\n", s2);}try {_devices->add(new vrpn_MyMouse(s2, connection,count));//在vrpn服务中初始化并添加设备实例}catch (...) {fprintf(stderr, "could not create vrpn_MyMouse\n");
#ifdef linuxfprintf(stderr, "- Is the GPM server running?\n");fprintf(stderr,"- Are you running on a linux console (not an xterm)?\n");
#endifreturn -1;}return 0;
}

2.2 Vrpn_MyMouse.h

剩下就是修改Vrpn_MyMouse的代码了,先是头文件。

#ifndef VRPN_MYMOUSE_H
#define VRPN_MYMOUSE_H///
#include "vrpn_Analog.h"                // for vrpn_Analog
#include "vrpn_Button.h"                // for vrpn_Button_Filter
#include "vrpn_Configure.h"             // for VRPN_API
#include "vrpn_Connection.h"            // for vrpn_CONNECTION_LOW_LATENCY, etc
#include "vrpn_Shared.h"                // for timeval
#include "vrpn_Types.h"                 // for vrpn_uint32
#include "vrpn_Text.h"
#include "dinput.h"
#include "dinputd.h"
#include <string>
#include <vector>#define DIRECTINPUT_VERSION 0x0800
#define DI8DEVCLASS_GAMECTRL 4 //扫描游戏控制器#pragma comment(lib, "dxguid.lib")//这俩lib要加到工程目录里
#pragma comment(lib, "dinput8.lib")class CJoystick {
public:CJoystick(int DeviceID);//构造函数要改一下,加一个设备连接数目的变量~CJoystick(void);public:std::string GetLastErrMsg();BOOL Init(LONG nMin = -1024, LONG nMax = 1024); //初始化函数DIJOYSTATE* PollDevice(); // 轮循设备函数,在轮循过程中读取设备状态//枚举设备static BOOL CALLBACK DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi,VOID* pvRef); //枚举对象static BOOL CALLBACKEnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext);private://一般的成员变量HINSTANCE m_hInstance;             // 实例句柄LPDIRECTINPUT8 m_lpDI;             // DI8接口指针LPDIRECTINPUTDEVICE8 m_lpDIDevice; // DIDevice8接口指针DIJOYSTATE m_diJs;                 //存储Joystick状态信息GUID JoystickGUID;                 // GUIDint DeviceID;LONG m_nMax; //最小值LONG m_nMin; //最大值std::string m_errmsg;
};class VRPN_API vrpn_MyMouse :public vrpn_Analog,public vrpn_Button_Filter,public vrpn_Text_Sender
{
public:vrpn_MyMouse( const char* name, vrpn_Connection* cxn,int DeviceCount );virtual ~vrpn_MyMouse();virtual void mainloop();class GpmOpenFailure {};    // thrown when can't open GPM serverprotected:  // methods/// Try to read reports from the device./// Returns 1 if msg received, or 0 if none received.virtual int get_report();/// send report iff changedvirtual void report_changes( vrpn_uint32 class_of_service= vrpn_CONNECTION_LOW_LATENCY );/// send report whether or not changedvirtual void report( vrpn_uint32 class_of_service= vrpn_CONNECTION_LOW_LATENCY );protected:  // datastruct timeval timestamp;    // time of last report from devicestd::vector<CJoystick> joysticks; // DXinput数据接口std::vector<DIJOYSTATE*> pjs;int DeviceNumToConnect = 0;private:  // disable unwanted default methodsvrpn_MyMouse();vrpn_MyMouse(const vrpn_MyMouse&);const vrpn_MyMouse& operator=(const vrpn_MyMouse&);
};#endif

2.3 Vrpn_MyMouse.C

#include <stdio.h>                      // for NULL, fprintf, printf, etc
#include <string.h>                     // for strncpy#include "vrpn_BaseClass.h"             // for ::vrpn_TEXT_ERROR
#include "vrpn_MyMouse.h"
#include "vrpn_Serial.h"                // for vrpn_open_commport, etc#if defined(linux) && defined(VRPN_USE_GPM_MOUSE)
#include <gpm.h>                        // for Gpm_Event, Gpm_Connect, etc
#endif#if !( defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS) )
#  include <sys/select.h>                 // for select, FD_ISSET, FD_SET, etc
#endif#ifdef    _WIN32
#include <windows.h>#pragma comment (lib, "user32.lib")// Fix sent in by Andrei State to make this compile under Visual Studio 6.0.
// If you need this, you also have to copy multimon.h from the DirectX or
// another Windows SDK into a place where the compiler can find it.
#ifndef SM_XVIRTUALSCREEN
#define COMPILE_MULTIMON_STUBS
#include "multimon.h"
#endif#endif//变量定义
#define SAFE_RELEASE(p)                                                        \{                                                                          \if (p) {                                                               \(p)->Release();                                                    \(p) = NULL;                                                        \}                                                                      \}int DeviceNum = 0;
int DeviceFound = 0;
struct DI_ENUM_CONTEXT {DIJOYCONFIG* pPreferredJoyCfg;bool bPreferredJoyCfgValid;
};
///
//DeviceCount:需要连接的设备数目,从configure文件中读取获得,在vrpn_Generic_server.object.c的setup函数中处理
vrpn_MyMouse::vrpn_MyMouse( const char* name, vrpn_Connection * cxn,int DeviceCount ) :vrpn_Analog( name, cxn ),vrpn_Button_Filter( name, cxn ), vrpn_Text_Sender(name, cxn)
{int i;// initialize the vrpn_Analog//定义模拟量输出为12个通道,按顺序对应DirectInput的lx,ly,lz,lrx,lry,lrz,rglSlider[2],rgdwPov[4]vrpn_Analog::num_channel = 12 * DeviceCount;for( i = 0; i < vrpn_Analog::num_channel; i++) {vrpn_Analog::channel[i] = vrpn_Analog::last[i] = 0;}//将数值初始化,last应该是记录的上一次的数值,用来和当前数据对比计算变化,vrpn_print_device应该是用了这俩数判断数据是否发生变化,发生变化后将新的数据打印出来。// initialize the vrpn_Button_Filter//定义三个按钮,对应DirectInput定义的32个值vrpn_Button_Filter::num_buttons = 32 * DeviceCount;for( i = 0; i < vrpn_Button_Filter::num_buttons; i++) {vrpn_Button_Filter::buttons[i] = vrpn_Button_Filter::lastbuttons[i] = 0;}//和上边一样DeviceNumToConnect = DeviceCount;// initialize the joystick classfor (i = 0; i < DeviceCount; i++) {joysticks.push_back(CJoystick(i + 1));}for (i = 0; i < DeviceCount; i++) {if (!joysticks[i].Init()) {printf("Error in initializing, maybe the Device Number set in Configure file doesn't match the real device number, pls check");exit(-1);}pjs.push_back(joysticks[i].PollDevice());}}///vrpn_MyMouse::~vrpn_MyMouse()
{}///
//循环函数,服务起来后就是不断执行里面的两个函数
void vrpn_MyMouse::mainloop()
{get_report();//获取设备数据,自定义设备时需要修改的代码!!!!!!server_mainloop();//响应客户端查询请求,告知客户端服务器仍在线,在mainloop中必须添加
}///
//获取设备数据的代码
int vrpn_MyMouse::get_report()
{   int deviceC = 0;
#if defined(_WIN32)    for (int i = 0; i < DeviceNumToConnect; i++) {      pjs[i] = joysticks[i].PollDevice();if (pjs[i]) {deviceC = 32 * i;for (int j = 0; j < 32; j++) {if (pjs[i]->rgbButtons[j] & 0x80) {//此时的i值就是按下的按键值vrpn_Button::buttons[deviceC+j] = 1;}else {vrpn_Button::buttons[deviceC + j] = 0;}}deviceC = 12 * i;vrpn_Analog::channel[deviceC + 0] = (vrpn_float64)(pjs[i]->lX);vrpn_Analog::channel[deviceC + 1] = (vrpn_float64)(pjs[i]->lY);vrpn_Analog::channel[deviceC + 2] = (vrpn_float64)(pjs[i]->lZ);vrpn_Analog::channel[deviceC + 3] = (vrpn_float64)(pjs[i]->lRx);vrpn_Analog::channel[deviceC + 4] = (vrpn_float64)(pjs[i]->lRy);vrpn_Analog::channel[deviceC + 5] = (vrpn_float64)(pjs[i]->lRz);vrpn_Analog::channel[deviceC + 6] = (vrpn_float64)(pjs[i]->rglSlider[0]);vrpn_Analog::channel[deviceC + 7] = (vrpn_float64)(pjs[i]->rglSlider[1]);vrpn_Analog::channel[deviceC + 8] = (vrpn_float64)(pjs[i]->rgdwPOV[0]);vrpn_Analog::channel[deviceC + 9] = (vrpn_float64)(pjs[i]->rgdwPOV[1]);vrpn_Analog::channel[deviceC + 10] = (vrpn_float64)(pjs[i]->rgdwPOV[2]);vrpn_Analog::channel[deviceC + 11] = (vrpn_float64)(pjs[i]->rgdwPOV[3]);}}vrpn_gettimeofday( &timestamp, NULL );//获取时间戳report_changes();return 1;
#elsereturn 0;
#endif
}///
//当数据变化时发送
void vrpn_MyMouse::report_changes( vrpn_uint32 class_of_service )
{vrpn_Analog::timestamp = timestamp;vrpn_Button_Filter::timestamp = timestamp;vrpn_Analog::report_changes( class_of_service );//变化时输出,用这个就只有移动鼠标时接到输出vrpn_Button_Filter::report_changes();//vrpn_Text_Sender::send_message("hello", vrpn_TEXT_NORMAL);
}///
//不论数据是否变化都发送
void vrpn_MyMouse::report( vrpn_uint32 class_of_service )
{vrpn_Analog::timestamp = timestamp;vrpn_Button_Filter::timestamp = timestamp;vrpn_Analog::report( class_of_service );//不论变化与否都输出,用这个就会一直接收到鼠标的位置信息vrpn_Button_Filter::report_changes();
}//Joystick连接相关函数(Direct Input)
CJoystick::CJoystick(int DeviceID)
{m_lpDIDevice = NULL;m_lpDI = NULL;this->DeviceID = DeviceID;m_hInstance = GetModuleHandle(NULL); //获取实例句柄m_nMin = -1024;m_nMax = +1024;
}CJoystick::~CJoystick(void)
{//释放DI和DIDevice对象if (m_lpDIDevice) {m_lpDIDevice->Unacquire();m_lpDIDevice->Release();m_lpDIDevice = NULL;}if (m_lpDI) {m_lpDI->Release();m_lpDI = NULL;}
}
//初始化
BOOL CJoystick::Init(LONG nMin, LONG nMax)
{HRESULT hr;// m_hWnd = hWnd ;DeviceNum = DeviceID;DeviceFound = 0;m_nMin = nMin;m_nMax = nMax;// 1.建立DI8接口if (NULL == m_lpDI) {hr = DirectInput8Create(m_hInstance, DIRECTINPUT_VERSION,(REFIID)IID_IDirectInput8,(void**)&m_lpDI, //接口取值NULL);if FAILED (hr) {// OutputDebugString("Create 失败 - in CDIJoystick::Initialise\n");m_errmsg = "Create 失败 - in CDIJoystick::Initialise";// MessageBox(NULL, L"joystick Create 失败", L"worning", MB_OK);return false;}}DIJOYCONFIG PreferredJoyCfg = {0};DI_ENUM_CONTEXT enumContext;enumContext.pPreferredJoyCfg = &PreferredJoyCfg;enumContext.bPreferredJoyCfgValid = true; //是否为标准手柄IDirectInputJoyConfig8* pJoyConfig = NULL;if (FAILED(hr = m_lpDI->QueryInterface(IID_IDirectInputJoyConfig8,(void**)&pJoyConfig))) {m_errmsg = "获取接口失败";// MessageBox(NULL, L"joystick 获取接口失败 失败", L"worning", MB_OK);return false;}PreferredJoyCfg.dwSize = sizeof(PreferredJoyCfg);if (SUCCEEDED(pJoyConfig->GetConfig(0, &PreferredJoyCfg,DIJC_GUIDINSTANCE))) // This function is expected to fail if no// joystick is attached// enumContext.bPreferredJoyCfgValid = false;SAFE_RELEASE(pJoyConfig);// 2.枚举设备hr = m_lpDI->EnumDevices(DI8DEVCLASS_GAMECTRL,  //扫描游戏控制器DIEnumDevicesCallback, //回调函数&enumContext,DIEDFL_ATTACHEDONLY); //扫描安装好的和连接好的设备//printf("Device Found %d\n", DeviceFound);//printf("DeviceID %d\n\n", this->DeviceID);if (DeviceFound < this->DeviceID) {m_errmsg = "无法检测到此设备";return false;}if FAILED (hr) {// OutputDebugString("枚举设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举设备失败";// MessageBox(NULL, L"joystick 枚举设备失败", L"worning", MB_OK);return false;}// 3.创建DI8设备if (!m_lpDIDevice) {// enumContext.pPreferredJoyCfg->guidInstance.Data4[1] = 2;hr = m_lpDI->CreateDevice(enumContext.pPreferredJoyCfg->guidInstance,&m_lpDIDevice, NULL);// printf("enumContext.pPreferredJoyCfg->guidInstance = %d\n",// enumContext.pPreferredJoyCfg->guidInstance.Data4[1]);if FAILED (hr) {// OutputDebugString("创建设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "创建设备失败";// MessageBox(NULL, L"joystick 创建设备失败", L"worning", MB_OK);return false;}}//设置协作等级—— 前台模式 | 独占模式  (后台/非独占)/*DISCL_BACKGROUND|*/// hr = m_lpDIDevice// ->SetCooperativeLevel(m_hWnd,DISCL_FOREGROUND|DISCL_EXCLUSIVE); if// FAILED(hr)//{//    //OutputDebugString("设置协作等级失败 - in CDIJoystick::Initialise\n");// m_errmsg="设置协作等级失败";//   MessageBox(NULL, L"joystick 设置协作等级失败", L"worning", MB_OK);//    if (m_hWnd==NULL)//       MessageBox(NULL, L"窗口句柄为空", L"worning", MB_OK);//   return false;//}// 4.设置数据格式hr = m_lpDIDevice->SetDataFormat(&c_dfDIJoystick);if FAILED (hr) {// OutputDebugString("设置数据格式失败 - in CDIJoystick::Initialise\n");m_errmsg = "设置数据格式失败";// MessageBox(NULL, L"joystick 设置数据格式失败", L"worning", MB_OK);return false;}hr = m_lpDIDevice->Acquire();// 5.枚举对象hr = m_lpDIDevice->EnumObjects(EnumObjectsCallback, (VOID*)this, DIDFT_ALL);if FAILED (hr) {// OutputDebugString("枚举对象失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举对象失败";// MessageBox(NULL, L"joystick 枚举对象失败", L"worning", MB_OK);return false;}return true;
}BOOL CALLBACK CJoystick::DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi,VOID* pvRef)
{DI_ENUM_CONTEXT* pEnumContext = (DI_ENUM_CONTEXT*)pvRef;if (pEnumContext->bPreferredJoyCfgValid) {pEnumContext->pPreferredJoyCfg->guidInstance = lpddi->guidInstance;DeviceFound++;}if (DeviceNum > 1) {DeviceNum--;return DIENUM_CONTINUE;}else {return DIENUM_STOP;}
}BOOL CALLBACK CJoystick::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext)
{HRESULT hr;CJoystick* js = (CJoystick*)pContext; //首先取得JS对象指针//设置游戏杆输入特性if (pdidoi->dwType & DIDFT_AXIS) //如果枚举的对象为轴{DIPROPRANGE diprg; //设置轴范围结构diprg.diph.dwSize = sizeof(DIPROPRANGE);diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);diprg.diph.dwHow = DIPH_BYID;diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴diprg.lMin = js->m_nMin;           //最小值diprg.lMax = js->m_nMax;           //最大值// 设置轴范围hr = js->m_lpDIDevice->SetProperty(DIPROP_RANGE, &diprg.diph);if (FAILED(hr)) {// OutputDebugString("设置轴范围失败 - in// CDIJoystick::EnumObjectsCallback\n");js->m_errmsg ="设置轴范围失败 - in CDIJoystick::EnumObjectsCallback";return DIENUM_STOP;}//设置死区属性,如果你使用的是电平式的游戏手柄,需要注释掉一下部分/*DIPROPDWORD dipdw; //死区结构dipdw.diph.dwSize = sizeof( dipdw );dipdw.diph.dwHeaderSize = sizeof( dipdw.diph );diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴dipdw.diph.dwHow = DIPH_DEVICE;dipdw.dwData = 1000; //10%的死区hr = js->m_lpDIDevice->SetProperty(DIPROP_DEADZONE, &dipdw.diph);if( FAILED(hr)){OutputDebugString("设置死区失败 - inCDIJoystick::EnumObjectsCallback\n"); return DIENUM_STOP;}*/}return DIENUM_CONTINUE;
}//获取游戏手柄(定时调用)
DIJOYSTATE* CJoystick::PollDevice()
{HRESULT hr;// DIJOYSTATE *pdjs = NULL;if (NULL == m_lpDI || NULL == m_lpDIDevice) //未获得设备return NULL;hr = m_lpDIDevice->Poll(); // 轮循设备读取当前状态if (FAILED(hr)) {// 输入流中断,不能通过轮循获得任何状态值。// 所以不需要任何重置,只要再次获得设备就行。hr = m_lpDIDevice->Acquire();while (hr == DIERR_INPUTLOST) {static int iCount = 0;// if (iCount>30) exit(-1); //累积30次获取设备失败,退出程序。if (iCount > 30) return NULL;iCount++;// OutputDebugString("丢失设备,轮循失败 - in// CJoystick::PollDevice\n");m_errmsg = "丢失设备,轮循失败 - in CJoystick::PollDevice";hr = m_lpDIDevice->Acquire();if (SUCCEEDED(hr)) iCount = 0;} // hr也许为其他的错误.// return &m_diJs;return NULL;}// 获得输入状态,存储到成员变量 m_diJs 中if (FAILED(hr = m_lpDIDevice->GetDeviceState(sizeof(DIJOYSTATE), &m_diJs)))return NULL; // 在轮循过程中设备将为 已获得 状态return &m_diJs;
}std::string CJoystick::GetLastErrMsg() { return m_errmsg; }

3. 小结

这么改完,编译一下就能用了,测试的话用vrpn_print_device那个就行,效果跟鼠标的那个一样。

至此完成了设备的添加,而且加上了Config读取的内容。

Vrpn源码浅析(二)-自定义创建JoyStick设备相关推荐

  1. Vrpn源码浅析(三)-添加optitrack追踪设备

    好记性不如烂笔头,之前进行了源码的简单分析并尝试添加了joystick这类包含analog以及button类型数据的设备.这次我们更近一步,尝试添加最为复杂的追踪设备.本次添加的设备为optitrac ...

  2. Spring源码解析二之创建Bean(实例化)

    上一节我们分析到了createBean,而真正创建Bean的过程在doCreateBean过程,我们可以发现Spring的编码风格,do才是真正的过程,不带do的通常是在做在准备过程,并且我们跳过了一 ...

  3. LinkedList类源码浅析(二)

    1.上一节介绍了LinkedList的几个基本的方法,其他方法类似,就不一一介绍: 现在再来看一个删除的方法:remove(Object o) remove方法接受一个Object参数,这里需要对参数 ...

  4. tio-http-server 源码浅析(二)Http请求的处理HttpRequestHandler

    前言 在上一篇<tio-http-server 源码浅析(一)HttpRequestDecoder的实现>简单分析了HttpRequestDecoder的源码,并且已经得到了HttpReq ...

  5. Joolun uniapp商城源码实现商城自定义拖拽装修_Java二开源码

    前一阵子,Joolun uniapp商城源码系统应广大开发者的需求,把开发者们想要的商城自定义拖拽装修给安排上了,拖拽装修也在V1.0.2版本发布的时候正式上线,实现商城首页自定义动态拖拽装修(支持不 ...

  6. Android Loader机制全面详解及源码浅析

    原文出处:csdn@工匠若水,http://blog.csdn.net/yanbober/article/details/48861457 一.概述 在Android中任何耗时的操作都不能放在UI主线 ...

  7. 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 从脚本到主类

    1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [一] 可以去看原文.这里是补充专栏.请看原文 2. 前言 主要针对yarn-per-job模式进行代码分析. ...

  8. 深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析

    本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 前言 前一阵子,写了几篇 Android 启动优化的文章,主要是 ...

  9. Spring-Web(一) RestTemplate使用与源码浅析

    Spring RestTemplate使用与源码浅析 一.RestTemplate 概述 ​ RestTemplate 是 Spring Web 模块封装的一个基于Rest规范提供HTTP请求服务的工 ...

最新文章

  1. Webwork 学习之路(四)Configuration 详解
  2. 点击图片放大至原始图片大小
  3. Monty Hall 问题与贝叶斯定理的理解
  4. swing中在JTextPane中的输入窗口出现乱码的问题
  5. python爬虫JS逆向之人口流动态势大数据
  6. java swing 复选JCheckBox组件美化
  7. eclipse与myeclipse恢复已删除的文件和代码
  8. 数组转ArrayList的正确方式
  9. GD32(6)中文字库
  10. 鸟哥的 linux 的私房菜 基础学习篇,鸟哥的 Linux 私房菜 -- 基础学习篇
  11. Matlab|基于粒子群优化算法及鲁棒MPPT控制器提高光伏并网的效率
  12. 除了秀米,微信排版还有什么好用的? ---短网址
  13. 09.第十章.项目沟通和干系人管理
  14. 这几款可以识别图片文字的app值得一试
  15. 堆转存目录/tmp或日志目录/var/log可用空间小于 10.0 吉字节。
  16. 13.3.8 添加换行符和换页符
  17. JS移动DOM节点,将某节点下所有子节点移动(剪切)到另一个节点下。新手很容易踩的坑!
  18. JAVA光头之路(一)--环境变量
  19. 已解决vue-router4路由报“[Vue Router warn]: No match found for location with path“
  20. OpenCV 学习笔记-day13 像素值统计 统计计算最大最小值,平均值和标准差 (minMaxLoc()和meanStdDev()函数的使用)

热门文章

  1. 如何将谷歌插件crx文件下载到本地?
  2. C语言指针的算术运算
  3. koa-helmet: 设置Http头保障应用程序安全
  4. 最近看的演唱会总结,张信哲,周杰伦,梁静茹
  5. SVD分解正确分解流程
  6. sqli-labs系列——第六关(双引号二次查询注入)
  7. 利用Python map 高阶函数计算长方形面积
  8. 有哪些值得推荐的办公软件下载网站
  9. 嵌入式系统【硬件层、STM32系统结构】
  10. python程序输出田字格_Python用print()函数输出田字格