PPJoyで、Virtual portを使って仮想ジョイスティックを作成し、
その仮想ジョイスティックをアプリケーションから操作してみた。まだ実験メモ。
作ったのはC#.net 2005かも。
あくまでも実験段階なのでコードが間違ってる可能性が
非常に高いです。
まずPPJOYでVirtual joystickを作ってみた。
DLLインポートとか
using System.Runtime.InteropServices;
デバイスオープンの為の列挙型を4個ほど。
private enum DesiredAccess : uint
{
GENERIC_READ = 0x80000000,
GENERIC_WRITE = 0x40000000,
GENERIC_EXECUTE = 0x20000000
}
private enum ShareMode : uint
{
FILE_SHARE_READ = 0x00000001,
FILE_SHARE_WRITE = 0x00000002,
FILE_SHARE_DELETE = 0x00000004
}
private enum CreationDisposition : uint
{
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXISTING = 5
}
private enum FlagsAndAttributes : uint
{
FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000,
FILE_ATTRIBUTE_HIDDEN = 0x00000002,
FILE_ATTRIBUTE_NORMAL = 0x00000080,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
FILE_ATTRIBUTE_OFFLINE = 0x00001000,
FILE_ATTRIBUTE_READONLY = 0x00000001,
FILE_ATTRIBUTE_SYSTEM = 0x00000004,
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
}
デバイスオープン用の関数をば。
//ジョイスティックオープン用
[DllImport(\"kernel32.dll\", CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(
string lpFileName,
DesiredAccess dwDesiredAccess,
ShareMode dwShareMode,
int lpSecurityAttributes,
CreationDisposition dwCreationDisposition,
FlagsAndAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile
);
そしてデータを送る為の関数。
因みにDeviceIoControlの3番目の引数は、
Byte配列のポインタらしき物を送るらしい。
で、C#で配列を含む構造体をByte配列にするのが面倒だったから
JOYSTICK_STATE構造体を作ってそのまま投げてみた。 ※ボタンは上手く行くがなんとも調子が悪い気がする
//データを送る
[DllImport(\"kernel32\", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeviceIoControl(
IntPtr hDevice,
uint dwIoControlCode,
ref JOYSTICK_STATE lpInBuffer,
uint uInBufferSize,
ref byte[] lpOutBuffer,
uint uOutBufferSize,
ref int lpBytesReturned,
int Overlapped
);
後、CTL_CODEという謎のビット演算する関数が必要らしい。
private uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access)
{
return ((DeviceType << 16) | (Access << 14) | (Function << 2) | Method);
}
先ほどのジョイスティック構造体の中身
Pack = 1を書き忘れて時間をとても残念に消費してしまったよ。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct JOYSTICK_STATE
{
[MarshalAs(UnmanagedType.U4, SizeConst = 1)]
public uint Signature;
[MarshalAs(UnmanagedType.U1, SizeConst = 1)]
public char NumAnalog;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public int[] Analog;
[MarshalAs(UnmanagedType.U1, SizeConst = 1)]
public char NumDigital;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public char[] Digital;
}
さてこっから実際にジョイスティックに対して処理をしていく。
ジョイスティックデバイスをオープン。\\\\.\\PPJoyIOCTL1っていう名前のジョイスティックらしい。
IntPtr hPtr = CreateFile(\"\\\\\\\\.\\\\PPJoyIOCTL1\",
DesiredAccess.GENERIC_WRITE,
ShareMode.FILE_SHARE_WRITE,
0,
CreationDisposition.OPEN_EXISTING,
0,
IntPtr.Zero);
Signatureのコードとか、ボタンのAnalog, Digitalの数はPPJoyのサンプルコードから拝借。
joyState.Digital[0] = (char)1; ってなってるのは、ボタン1を押してる状態ってこと。
0は離した状態かな。
Analogの方はまだよく分からない。ちょうど中心は1~128の間の64かと思ったんだけど、
これだと何か変な方向へ動く。
もしかしたら-127~128かもしれないし、まだ実験してないからなんとも。
JOYSTICK_STATE joyState = new JOYSTICK_STATE();
joyState.Signature = (uint)0x53544143;
joyState.NumAnalog = (char)8;
joyState.NumDigital = (char)16;
joyState.Analog = new int[8];
joyState.Analog[0] = 64;
joyState.Analog[1] = 64;
joyState.Analog[2] = 64;
joyState.Analog[3] = 64;
joyState.Analog[4] = 64;
joyState.Analog[5] = 64;
joyState.Analog[6] = 64;
joyState.Analog[7] = 64;
joyState.Digital = new char[16];
joyState.Digital[0] = (char)1;
joyState.Digital[1] = (char)0;
joyState.Digital[2] = (char)0;
joyState.Digital[3] = (char)0;
joyState.Digital[4] = (char)0;
joyState.Digital[5] = (char)0;
joyState.Digital[6] = (char)0;
joyState.Digital[7] = (char)0;
joyState.Digital[8] = (char)0;
joyState.Digital[9] = (char)0;
joyState.Digital[10] = (char)0;
joyState.Digital[11] = (char)0;
joyState.Digital[12] = (char)0;
joyState.Digital[13] = (char)0;
joyState.Digital[14] = (char)0;
joyState.Digital[15] = (char)0;
ここもサンプルコードから拝借。なぜFILE_DEVICE_UNKNOWNでいいのかは分からないよ。
//FILE_DEVICE_UNKNOWN = 0x22, index = 0, METHOD_BUFFERED = 0, FILE_ANY_ACCEESS = 0
uint ioctlState = CTL_CODE(0x00000022, 0, 0, 0);
実際にデバイスにデータを送る段階。
配列含んだ構造体のサイズを一発で取得する方法が思いつかなかったので、律儀に足してみた。
誰かいい方法教えてください。
int RetSize = 0;
byte[] outBuffer = null;
int joySize = Marshal.SizeOf(joyState.Signature);
joySize += Marshal.SizeOf(joyState.NumAnalog);
joySize += Marshal.SizeOf(joyState.Analog[0]) * 8;
joySize += Marshal.SizeOf(joyState.NumDigital);
joySize += Marshal.SizeOf(joyState.Digital[0]) * 16;
DeviceIoControl(hPtr, ioctlState, ref joyState, (uint)joySize, ref outBuffer, 0, ref RetSize, 0);
うまく行けば、ゲームパッドのプロパティの所でボタン1が押されてると思う。
とりあえずメモまで。
※2011/09/26 Blog再構築でコメント消えてしまいましたが、過去に以下のコメントをいただいておりました。
> Analogの方はまだよく分からない。ちょうど中心は1~128の間の64かと思ったんだけど、
これだと何か変な方向へ動く。
そこの値は15bit rangeですから、無符号で0~32K-1の間では。すなわち、16Kでcenterだと思います。 / やねうらお