先日の記事で、Razerデバイスを制御するソフトウェアをC++で作りました。
実を言うと、私の専門はjava/C#/VB/PHPでC++は正直そこまでなんですよね。
(RubyやらPythonやらも触れはしてるけど専門とは言えないレベル)
なので、今後C++でメンテしていくのは結構面倒臭い(ポインタ制御とか…)ので、
このプログラムをC#に移植したいと思います。
とはいえ、使用しているライブラリ自体はC++/CLIですので、
まずはこのライブラリを呼び出す部分を作らなければなりません。
ライブラリのヘッダファイル(Interception.h)を元に、C#でDllImportしていきます。
Interception.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
using InterceptionContext = System.IntPtr;
using InterceptionDevice = System.Int32;
using InterceptionPrecedence = System.Int32;
using InterceptionFilter = System.UInt16;
namespace MyDeviceController.Library
{
internal static class Interception
{
public const int INTERCEPTION_MAX_KEYBOARD = 10;
public const int INTERCEPTION_MAX_MOUSE = 10;
public const int INTERCEPTION_MAX_DEVICE = ((INTERCEPTION_MAX_KEYBOARD) + (INTERCEPTION_MAX_MOUSE));
public static int GetKeyBoardNum(int index) => (index + 1);
public static int GetMouseNum(int index) => ((INTERCEPTION_MAX_KEYBOARD) + (index) + 1);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int InterceptionPredicate(InterceptionDevice device);
[DllImport("Interception", EntryPoint = "interception_create_context", CallingConvention = CallingConvention.Cdecl)]
public static extern InterceptionContext Interception_create_context();
[DllImport("Interception", EntryPoint = "interception_destroy_context", CallingConvention = CallingConvention.Cdecl)]
public static extern void Interception_destroy_context(InterceptionContext context);
[DllImport("Interception", EntryPoint = "interception_get_precedence", CallingConvention = CallingConvention.Cdecl)]
public static extern InterceptionPrecedence Interception_get_precedence(InterceptionContext context, InterceptionDevice device);
[DllImport("Interception", EntryPoint = "interception_set_precedence", CallingConvention = CallingConvention.Cdecl)]
public static extern void Interception_set_precedence(InterceptionContext context, InterceptionDevice device, InterceptionPrecedence precedence);
[DllImport("Interception", EntryPoint = "interception_get_filter", CallingConvention = CallingConvention.Cdecl)]
public static extern InterceptionFilter Interception_get_filter(InterceptionContext context, InterceptionDevice device);
[DllImport("Interception", EntryPoint = "interception_set_filter", CallingConvention = CallingConvention.Cdecl)]
public static extern void Interception_set_filter(InterceptionContext context, InterceptionPredicate predicate, InterceptionFilter filter);
[DllImport("Interception", EntryPoint = "interception_wait", CallingConvention = CallingConvention.Cdecl)]
public static extern InterceptionDevice Interception_wait(InterceptionContext context);
[DllImport("Interception", EntryPoint = "interception_wait_with_timeout", CallingConvention = CallingConvention.Cdecl)]
public static extern InterceptionDevice Interception_wait_with_timeout(InterceptionContext context, ulong milliseconds);
[DllImport("Interception", EntryPoint = "interception_send", CallingConvention = CallingConvention.Cdecl)]
public static extern int Interception_send(InterceptionContext context, InterceptionDevice device, ref InterceptionStroke stroke, uint nstroke);
[DllImport("Interception", EntryPoint = "interception_receive", CallingConvention = CallingConvention.Cdecl)]
public static extern int Interception_receive(InterceptionContext context, InterceptionDevice device, ref InterceptionStroke stroke, uint nstroke);
[DllImport("Interception", EntryPoint = "interception_get_hardware_id", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern uint Interception_get_hardware_id(InterceptionContext context, InterceptionDevice device, StringBuilder hardware_id_buffer, uint buffer_size);
[DllImport("Interception", EntryPoint = "interception_is_invalid", CallingConvention = CallingConvention.Cdecl)]
public static extern int Interception_is_invalid(InterceptionDevice device);
[DllImport("Interception", EntryPoint = "interception_is_keyboard", CallingConvention = CallingConvention.Cdecl)]
public static extern int Interception_is_keyboard(InterceptionDevice device);
[DllImport("Interception", EntryPoint = "interception_is_mouse", CallingConvention = CallingConvention.Cdecl)]
public static extern int Interception_is_mouse(InterceptionDevice device);
}
[StructLayout(LayoutKind.Explicit)]
public struct InterceptionStroke{
[FieldOffset(0)] public InterceptionMouseStroke Mouse;
[FieldOffset(0)] public InterceptionKeyStroke Key;
}
[StructLayout(LayoutKind.Sequential)]
public struct InterceptionMouseStroke
{
public ushort State;
public ushort Flags;
public short Rolling;
public int X;
public int Y;
public uint Information;
}
[StructLayout(LayoutKind.Sequential)]
public struct InterceptionKeyStroke
{
public ushort Code;
public ushort State;
public uint Information;
}
[Flags]
public enum KeyState : ushort
{
KeyDown = 0x00,
KeyUp = 0x01,
KeyE0 = 0x02,
KeyE1 = 0x04,
KeySetLED = 0x08,
KeyShadow = 0x10,
KeyVKPacket = 0x20
};
[Flags]
public enum KeyStateFilter : ushort
{
None = 0x0000,
All = 0xFFFF,
KeyDown = KeyState.KeyUp,
KeyUp = KeyState.KeyUp << 1,
KeyE0 = KeyState.KeyE0 << 1,
KeyE1 = KeyState.KeyE1 << 1,
KeySetLED = KeyState.KeySetLED << 1,
KeyShadow = KeyState.KeyShadow << 1,
KeyVKPacket = KeyState.KeyVKPacket << 1
};
[Flags]
public enum MouseState : ushort
{
LeftButtonDown = 0x001,
LeftButtonUp = 0x002,
RightButtonDown = 0x004,
RightButtonUp = 0x008,
MiddleButtonDown = 0x010,
MiddleButtonUp = 0x020,
Button1Down = LeftButtonDown,
Button1Up = LeftButtonUp,
Button2Down = RightButtonDown,
Button2Up = RightButtonUp,
Button3Down = MiddleButtonDown,
Button3Up = MiddleButtonUp,
Button4Down = 0x040,
Button4Up = 0x080,
Button5Down = 0x100,
Button5Up = 0x200,
MouseWheel = 0x400,
MouseHWheel = 0x800
};
[Flags]
public enum MouseStateFilter : ushort
{
None = 0x0000,
All = 0xFFFF,
LeftButtonDown = MouseState.LeftButtonDown,
LeftButtonUp = MouseState.LeftButtonUp,
RightButtonDown = MouseState.RightButtonDown,
RightButtonUp = MouseState.RightButtonUp,
MiddleButtonDown = MouseState.MiddleButtonDown,
MiddleButtonUp = MouseState.MiddleButtonUp,
Button1Down = MouseState.Button1Down,
Button1Up = MouseState.Button1Up,
Button2Down = MouseState.Button2Down,
Button2Up = MouseState.Button2Up,
Button3Down = MouseState.Button3Down,
Button3Up = MouseState.Button3Up,
Button4Down = MouseState.Button4Down,
Button4Up = MouseState.Button4Up,
Button5Down = MouseState.Button5Down,
Button5Up = MouseState.Button5Up,
MouseWheel = MouseState.MouseWheel,
MouseHWheel = MouseState.MouseHWheel,
MouseMove = 0x1000,
};
[Flags]
public enum MouseFlags : ushort
{
MoveRelative = 0x000,
MoveAbsolute = 0x001,
VirtualDesktop = 0x002,
AttributesChanged = 0x004,
MoveNoCoalesce = 0x008,
TermsvrSrcShadow = 0x100
};
}
これで、DllImport部分が完成したので、あとは呼出元もC++からC#へ移植
using System.Text;
using MyDeviceController.Library;
using InterceptionDevice = System.Int32;
using InterceptionPrecedence = System.Int32;
using InterceptionFilter = System.UInt32;
using InterceptionContext = System.IntPtr;
using static MyDeviceController.Library.Interception;
namespace MyDeviceController
{
class RazerObserver
{
public bool IsStop { get; set; } = false;
private const string KEYBD_HID = "HID\\VID_1532&PID_0244&REV_0200&MI_01&Col01";
private const string KEYBD_PAD = "HID\\VID_1532&PID_0244&REV_0200&MI_00";
private const string MOUSE_HID = "HID\\VID_1532&PID_0244&REV_0200&MI_02";
public void MonitorInput()
{
var context = Interception_create_context();
if (context == null) return;
try
{
Interception_set_filter(context, Interception_is_keyboard,
(ushort)(KeyStateFilter.KeyDown |
KeyStateFilter.KeyUp |
KeyStateFilter.KeyE0 |
KeyStateFilter.KeyE1));
Interception_set_filter(context, Interception_is_mouse,
(ushort)(MouseStateFilter.MouseWheel |
MouseStateFilter.MiddleButtonDown |
MouseStateFilter.MouseWheel));
InterceptionDevice keyboard = INTERCEPTION_MAX_DEVICE, pad = INTERCEPTION_MAX_DEVICE, mouse = INTERCEPTION_MAX_DEVICE;
/* Search KeyBoard Device */
for (int i = 0; i < INTERCEPTION_MAX_KEYBOARD; i++)
{
InterceptionDevice d = GetKeyBoardNum(i);
StringBuilder sb = new StringBuilder(500);
if (Interception_get_hardware_id(context, d, sb, (uint)sb.Capacity) > 0)
{
switch(sb.ToString())
{
case KEYBD_HID:
keyboard = d;
break;
case KEYBD_PAD:
pad = d;
break;
default:
break;
}
}
}
/* Search Mouse Device */
for (int i = 0; i < INTERCEPTION_MAX_MOUSE; i++)
{
InterceptionDevice d = GetMouseNum(i);
StringBuilder sb = new StringBuilder(500);
if (Interception_get_hardware_id(context, d, sb, (uint)sb.Capacity) > 0 && (sb.ToString() == MOUSE_HID))
{
mouse = d;
break;
}
}
if (keyboard == INTERCEPTION_MAX_DEVICE
|| pad == INTERCEPTION_MAX_DEVICE
|| mouse == INTERCEPTION_MAX_DEVICE) return;
InterceptionDevice device;
InterceptionStroke stroke = new InterceptionStroke();
while (!IsStop && Interception_receive(context, device = Interception_wait(context), ref stroke, 1) > 0)
{
if (device == keyboard)
{
InterceptionKeyStroke s = stroke.Key;
bool isKeySend = true;
switch (s.Code)
{
case 2:
s.Code = 0x01; //Esc
break;
case 3:
s.Code = 0x08; //7
break;
case 4:
s.Code = 0x09; //8
break;
case 5:
s.Code = 0x02; //1
break;
case 6:
s.Code = 0x03; //2
break;
case 15:
break;
case 16:
break;
case 17:
break;
case 18:
s.Code = 0x04; //3
break;
case 19:
s.Code = 0x05; //4
break;
case 58:
isKeySend = false;
s.Code = 0x2A; this.SendKey(context, device, stroke, s, 1); //Shift
s.Code = 0x21; this.SendKey(context, device, stroke, s, 1); //F
break;
case 30:
break;
case 31:
break;
case 32:
break;
case 33:
s.Code = 0x06; //5
break;
case 42:
s.Code = 0x2C; //Z
break;
case 44:
isKeySend = false; //無効化
break;
case 45:
isKeySend = false; //無効化
break;
case 46:
s.Code = 0x2D;
break;
case 57:
break;
case 56:
s.Code = 0x07; //6
break;
case 75:
break;
case 77:
break;
case 72:
break;
case 80:
break;
default:
break;
}
if (isKeySend)
this.SendKey(context, device, stroke, s, 1);
}
else this.SendKey(context, device, stroke, 1);
}
}
finally
{
Interception_destroy_context(context);
}
}
public void SendKey(InterceptionContext context, InterceptionDevice device, InterceptionStroke stroke, InterceptionKeyStroke keyStroke, InterceptionFilter nstroke)
{
stroke.Key = keyStroke;
Interception_send(context, device, ref stroke, nstroke);
}
public void SendKey(InterceptionContext context, InterceptionDevice device, InterceptionStroke stroke, InterceptionMouseStroke mouseStroke, InterceptionFilter nstroke)
{
stroke.Mouse = mouseStroke;
Interception_send(context, device, ref stroke, nstroke);
}
public void SendKey(InterceptionContext context, InterceptionDevice device, InterceptionStroke stroke, InterceptionFilter nstroke)
{
Interception_send(context, device, ref stroke, nstroke);
}
}
}
あとは、これをマルチメソッドで呼び出してあげればOK.
今回は.NET Coreで作成したので、自作のNotifyIconコントロールを表示するように設定。
App.xaml.cs
namespace MyDeviceController
{
///
/// Interaction logic for App.xaml
///
public partial class App : Application
{
private NotifyIcon notifyIcon = new NotifyIcon();
private RazerObserver razerObserver;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnExplicitShutdown;
razerObserver = new RazerObserver();
var task = Task.Run(() => razerObserver.MonitorInput());
}
protected override void OnExit(ExitEventArgs e)
{
razerObserver.IsStop = true;
notifyIcon.Dispose();
base.OnExit(e);
}
}
}
これで問題なく動作。
メモリ管理は.NET側で管理してくれるので、今後汎用的にしていく上でC#の方が楽ですね。
今回は単純な移植ですが、今後は設定画面等追加して、
もう少し使い勝手を向上させていこうと思います。
最早FF14のブログではなく技術者ブログになりつつある件。