Visual C++
 

DirectX input para Visual C++

Fundamentos

Visual C++ ofrece una amplia gama de posibilidades respecto a la incorporación de DirectX, en mayor parte porque detrás de estos está implicada la misma empresa.

Podemos realizar gráficos con DirectDraw, controlar dispositivos con DirectInput e incluso implementar sonidos con DirectSound e incluso manipulación de redes mediante DirectPlay.

Existen una gran variedad de ejemplos en la web e incluso en los archivos de instalación de las librerías que son de libre distribución. El paquete completo con ejemplos y librerías se llaman con el nombre de DirectX SDK la cual tiene varias versiones, en nuestro caso particular usamos las librerias Microsoft DitectX SDK Update ( October 2004 ) aunque actualmente va por la versión 2007.

Lo primero que tenemos que hacer para implementar un dispositivo es instalar las SDK en nuestro ordenador.

Una vez instalado veremos que tenemos a nuestra disposición múltiples librerías y herramientas para trabajar, así como un help para DirectX en C++ el cual es muy útil a la hora de buscar información.

Una vez instalado el siguiente paso es enlazar nuestro compilador a esta enorme cantidad de librerías e includes, para ello haremos lo siguiente.

Primero, como es lógico, crearemos un nuevo proyecto, luego vamos a la barra de herramientas y pinchamos sobre Tools para más tarde acceder al submenú Options, apareciendo el siguiente diálogo.

A continuación pinchamos sobre la pestaña Directories, donde veremos que en el comboBox con título Show directories for aparecen 4 opciones: Executable files, Include files, Library files y Source files.

Accedemos primeramente a Include files para más tarde pulsar abajo de este sobre un botón en forma de cuadrado punteado con una especie de luz en la esquina. Aparecerá abajo un recuadro donde tendremos que explorar por nuestro ordenador para darle la ruta de los includes que hemos instalado con las SDK, en mi caso en:

C:\Archivos de programa\Microsoft DirectX 9.0 SDK (October 2004)\Include

Luego seleccionaremos nuestra ruta para llevarla hasta arriba del todo, situándola en primera línea, esto se hace para que acceda a esta ruta antes que a las demás, este paso es importante sino puede que haya errores de compilación.

Haremos lo mismo para Library files y con Executable files con las rutas correspondientes:

C:\Archivos de programa\Microsoft DirectX 9.0 SDK (October 2004)\Lib para Library files.

C:\Archivos de programa\Microsoft DirectX 9.0 SDK (October 2004)\Utilities para Executable files.

Hasta aquí hemos enlazado nuestro compilador con las librerías y demás componentes de DirectX SDK, ahora nos falta incluir las librerías que vamos a usar en nuestro programa, para ello nos situamos sobre la barra de herramientas y pinchamos sobre Project y a continuación al submenú Settings.

Pincharemos con el ratón sobre la pestaña link, incluiremos en el TextBox con título Object/library modules las siguientes librerias:

input.lib dxguid.lib winmm.lib dxerr8.lib ddraw.lib dinput8.lib

Quedando como resultado lo siguiente.

Ya tenemos completamente enlazado nuestro programa a las DirectX, sólo falta probar que no hay errores de enlace, para probarlo sólo tenemos que compilar, linkar y ejecutar nuestro programa, si no hay errores de compilación es que vamos por el buen camino.

Ahora nos toca tirar muchas líneas de código para implementar el dispositivo. Vamos a explicar brevemente las partes importantes del código para que todos podamos entender como se realiza la adquisición de datos del Joystick.

El programa se estructura en 3 partes fundamentales:

1.- Instanciación del dispositivo

2.- Adquisición de datos del dispositivo

3.-Realimentación o callbacks

1.-Instanciación del dispositivo.

Para ello primero incluiremos las librerías DirectX. Nos iremos al archivo de cabezera StdAfx.h e incluiremos las siguientes líneas de código al final del cierre de las librerías afx.

.....
.....
.....
#endif // _AFX_NO_AFXCMN_SUPPORT // Linea de código existente

#define DIRECTINPUT_VERSION 0x0800 //Linea a incluir
#include <dinput.h> //Linea a incluir

//{{AFX_INSERT_LOCATION}} // Linea de código existente
.....
.....
.....

Vamos a crear las constantes e instanciaciones de nuestro dispositivo, para ello vamos al fichero de cabezera del diálogo principal, en mi caso llamado DirectXInputSDKDlg.h e incluimos los define safe delete y safe release.

.....
.....
#pragma once
#endif // _MSC_VER > 1000

//-----------------------------------------------------------------------------
// Defines, constants, and global variables
//-----------------------------------------------------------------------------
#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } //Linea a incluir
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //Linea a incluir

// CDirectXInputSDKDlg dialog

class CDirectXInputSDKDlg : public CDialog
.....
.....

Ahora nos situamos sobre el archivo principal DirectXInputSDK.cpp, donde incluiremos la instanciación de los diferentes objetos de nuestro dispositivo entre las siguientes líneas de código ya existentes.

.....
.....
CDirectXInputSDKApp::CDirectXInputSDKApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}


// The one and only CDirectXInputSDKApp object
CDirectXInputSDKApp theApp;
struct EFFECTS_NODE
{
LPDIRECTINPUTEFFECT pDIEffect;
DWORD dwPlayRepeatCount;
EFFECTS_NODE* pNext;
};
LPDIRECTINPUT8 _pDI=NULL;
LPDIRECTINPUTDEVICE8 _pJoystick=NULL;
EFFECTS_NODE g_EffectsList;
LPDIRECTINPUTEFFECT g_pEffect = NULL;


// CDirectXInputSDKApp initialization
BOOL CDirectXInputSDKApp::InitInstance()
{
AfxEnableControlContainer();
.....
.....

Ahora vamos a "importar" dichas instancias a nuestro diálogo, accedemos al fichero DirectXInputSDKDlg.cpp e incluimos.

.....
.....
static char THIS_FILE[] = __FILE__;
#endif

extern LPDIRECTINPUT8 _pDI;
extern LPDIRECTINPUTDEVICE8 _pJoystick;
extern LPDIRECTINPUTEFFECT g_pEffect;

struct EFFECTS_NODE
{
LPDIRECTINPUTEFFECT pDIEffect;
DWORD dwPlayRepeatCount;
EFFECTS_NODE* pNext;
};

extern EFFECTS_NODE g_EffectsList;

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
.....
.....

Volveremos a compilar, linkar y ejecutar para ver si encontramos algún error.

2.- Adquisición de datos del dispositivo.

La adquisición de datos se hace a través del método pulling como cualquier otro dispositivo HID externo. Consiste en preguntar la situación del dispositivo cada cierto tiempo. Primero inicializaremos el dispositivo para luego adquirir sus datos. Para inicializarlo usaremos la funcion InitDirectInput donde inicializaremos el dispositivo y su FFB. Al iniciar el dialogo lo primero que haremos será llamar a la función InitDirectInput para ver si tenemos conectado algún dispositivo y luego activamos un temporizador que se active cada 250 ms, este timer nos llevará a la función DefWindowProc, la cual la podemos activar desde el class Wizard (ctrl + w).

LRESULT CDirectXInputSDKDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
//Este el el temporizador del dispositivo (Pulling)
HRESULT hr;
switch( message )
{
case WM_ACTIVATE:
if( WA_INACTIVE != wParam && _pJoystick )
{
// Make sure the device is acquired, if we are gaining focus.
_pJoystick->Acquire(); }//Acquire es un método de DXinput para recojer los datos
return TRUE;

case WM_TIMER:
// Update the input device every timer message
if( FAILED( UpdateInputState((HWND)this->m_hWnd) ) ) //Función donde se representan los datos del dispositivo
{
KillTimer(0);
AfxMessageBox( _T("Error Reading Input State.The sample will now exit.DirectInput Sample"),
MB_ICONERROR | MB_OK );
EndDialog(TRUE );
}

return TRUE;

case WM_DESTROY:
// Cleanup everything
KillTimer( 0 );
FreeDirectInput();
return TRUE;
}

return CDialog::DefWindowProc(message, wParam, lParam);
}

Función donde se inicia el dialogo.

BOOL CDirectXInputSDKDlg::OnInitDialog()
{
CDialog::OnInitDialog();
.....
.....
// TODO: Add extra initialization here
Inici

if( FAILED( InitDirectInput((HWND)this->m_hWnd) ) )
{
AfxMessageBox(_T("Error Initializing DirectInput.DirectInput Sample"), MB_ICONERROR | MB_OK );
return FALSE;
}

this->SetTimer( 0, 250, NULL );//Timer del poll del Joystick, inicialización del timer

return TRUE; // return TRUE unless you set the focus to a control
}

Función donde se inicia el dispositivo.

HRESULT CDirectXInputSDKDlg::InitDirectInput( HWND hDlg )
{
HRESULT hr;

// Setup the g_EffectsList circular linked list
ZeroMemory( &g_EffectsList, sizeof( EFFECTS_NODE ) );
g_EffectsList.pNext = &g_EffectsList;

// Register with the DirectInput subsystem and get a pointer
// to a IDirectInput interface we can use.
// Create a DInput object
//Inicializamos el dispositivo con las DirectX
if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION,
IID_IDirectInput8, (VOID**)&_pDI, NULL ) ) )
return hr;

//Inicializamos el dispositivo a un gamectrl con FFB donde EnemFFDevicesCallbacks es su función de interrupción
// Get the first enumerated force feedback device
if( FAILED( hr = _pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumFFDevicesCallback, NULL,
DIEDFL_ATTACHEDONLY ) ) )
return hr;

// Make sure we got a Joystick
if( NULL == _pJoystick )
{
if ((AfxMessageBox(_T("El Joystick no funciona o no está conectado. ¿Desea cerrar la aplicación y conectar el Joystick?.\n\nIMPORTANTE:\nSi continua, las funciones del Joystick no funcionarán aunque aparezcan activadas"),
MB_ICONERROR | MB_OKCANCEL))==TRUE)
{
EndDialog( 0 );
}
return S_OK;
}

// Set the data format to "simple Joystick" - a predefined data format
//
// A data format specifies which controls on a device we are interested in,
// and how they should be reported. This tells DInput that we will be
// passing a DIJOYSTATE2 structure to IDirectInputDevice::GetDeviceState().

if( FAILED( hr = _pJoystick->SetDataFormat( &c_dfDIJoystick2 ) ) )
return hr;

// Set the cooperative level to let DInput know how this device should
// interact with the system and with other DInput applications.
if( FAILED( hr = _pJoystick->SetCooperativeLevel( hDlg, DISCL_EXCLUSIVE |
DISCL_FOREGROUND ) ) )
return hr;

// Auto centrado del dispositivo
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = FALSE;

//Esto es del FFB
if ( FAILED( hr = _pJoystick->EnumObjects( EnumAxesCallback,
(VOID*)&g_dwNumForceFeedbackAxis, DIDFT_AXIS ) ) )
return hr;
return S_OK;
}

Ahora necesitamos actualizar y presentar nuestros datos por pantalla, para ello la función encargada será la siguiente.

HRESULT CDirectXInputSDKDlg::UpdateInputState( HWND hDlg )
{
HRESULT hr;
DIJOYSTATE2 js; // DInput Joystick state
TCHAR* str;

if( NULL == _pJoystick )
return S_OK;

// Poll the device to read the current state
hr = _pJoystick->Poll();
if( FAILED(hr) )
{
// DInput is telling us that the input stream has been
// interrupted. We aren't tracking any state between polls, so
// we don't have any special reset that needs to be done. We
// just re-acquire and try again.
hr = _pJoystick->Acquire();
while( hr == DIERR_INPUTLOST )
hr = _pJoystick->Acquire();
// hr may be DIERR_OTHERAPPHASPRIO or other errors. This
// may occur when the app is minimized or in the process of
// switching, so just try again later
return S_OK;
}

// Get the input's device state
if( FAILED( hr = _pJoystick->GetDeviceState( sizeof(DIJOYSTATE2), &js ) ) )
return hr; // The device should have been acquired during the Poll()

//Eje X izquierda
wsprintf( strTextXIzq, TEXT("%ld"), js.lX-32768 ); //js es la variable donde se guarda la información del dispositivo
PosXIzq = atoi (strTextXIzq)/128;

//Eje Y Izquierda
wsprintf( strTextYIzq, TEXT("%ld"), js.lY-32768 );
PosYIzq = atoi (strTextYIzq)/128;

//Eje X derecha
wsprintf( strTextXDrc, TEXT("%ld"), js.lRz-32768 );
PosXDrc = atoi (strTextXDrc)/128;

//Eje Y derecha
wsprintf( strTextYDrc, TEXT("%ld"), js.lZ-32768 );
PosYDrc = atoi (strTextYDrc)/128;

m_Slider_H_Izq.SetPos(PosXIzq);
m_Slider_V_Izq.SetPos(-PosYIzq);
m_Slider_H_Drc.SetPos(PosXDrc);
m_Slider_V_Drc.SetPos(-PosYDrc);

// Fill up text with which buttons are pressed
str = strText;
for( int i = 0; i < 128; i++ )
{
if ( js.rgbButtons[i] & 0x80 )
str += wsprintf( str, TEXT("%02d "), i );
}
*str = 0; // Terminate the string

m_TxtBoton.SetWindowText(strText );

UpdateData(FALSE); // Actualizamos el refresco de pantalla

return S_OK;
}

 

3.-Realimentación o callbacks.

Existen varios tipos de realimentaciones por software en nuestro programa, de los cuales unos son necesarios para el dispositivo y otros para la ejecución del FFB. Sólo haremos mención de ellas, para entenderlas es necesario mucho tiempo y paciencia, así que en este caso las trataremos como cajas negras donde al recibir unas entradas, estas dan como respuestas unas salidas, sin importarnos lo que haga internamente, así que hagamos como hacemos en el estudio de automática y tratemos a las callbacks como cajas negras. Aquellos que quieran entenderlas mejor que antes os arméis de paciencia... Estas son los siguientes:

BOOL CALLBACK EnumAndCreateEffectsCallback( LPCDIFILEEFFECT pDIFileEffect, VOID* pvRef )

BOOL CALLBACK EnumFFDevicesCallback( const DIDEVICEINSTANCE* pInst, VOID* pContext )

BOOL CALLBACK EnumAxesCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext )

BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext )

La primera de ellas es una realimentación por SW para el FFB, la segunda es concreta para dispositivos en general, la siguiente es para nuestro Joystick en particular y la última es la realimentación para la adquisición de datos.

Conclusión

Después de todo hemos conseguido realizar un ejemplo de aplicación muy práctico para la robótica, aunque es recomendable bajarse el programa completo y verlo todo poco a poco y con paciencia para ver que hace cada función e incluso ir en modo debug paso a paso, así es la mejor forma de ver que hace el programa y la secuencia que sigue.

Download

Para descargaros los ficheros sólo teneis que hacer un click en los siguientes enlaces.

Ejecutable DirectX Input Visual C++.

Programa completo de DirectX Input Visual C++.

Hacer mención que todo el código es FREEWARE Y SHAREWARE con lo que puede ser usado por cualquiera y cualquier propósito legal, lo único que se pide es que se haga mención del autor del programa por el esfuerzo realizado y por su labor de compartirlo con los demás.

 

 
 



Inicio | Foro | FAQ | MapaWeb | BuscadorWeb