2017年11月29日水曜日

Visual Studio 2015 forWDでSimConnect

 SimConnectはウィンドウハンドルが必要なので、コンソールアプリは作れない、orかなり面倒。Formで作るべし。

 フォームのプロジェクトを作ったら、ソリューションエクスプローラーからプロジェクトのプロパティを開いて、"アプリケーション"タブにある、対象のフレームワークを.Net Framework 2.0にする。"ビルド"タブにある、プラットフォームターゲットをAny CPUからx86に変更する。
 いくつかの参照はリンクが切れているので、警告マークが出た参照は削除しておく。
 Form1.csとProgram.csのusingも、リンク切れを削除しておく。

 C:\Program Files (x86)\Microsoft Games\Microsoft Flight Simulator X SDK\SDK\Core Utilities Kit\SimConnect SDK\lib\managed\Microsoft.FlightSimulator.SimConnect.dllを参照に追加する。

 Microsoft.FlightSimulator.SimConnectとSystem.Runtime.InteropServicesのusingを追加する。


 ソースコードは長い(400行以上ある)ので、続きでどうぞ。

 とりあえず、FSXの情報を取り出すサンプルが2種類と、FSXにコマンドを送るサンプルが1個と、オマケにもう1種類。

 情報を取り出すのは、ギアハンドルが操作されたらFormに表示する、というサンプルと、常に位置情報を取り出して表示する、というサンプルの2種類。
 コマンドを送るのは、フォームからの操作でギアハンドルの位置を変える操作を行う。
 オマケは、SendInputでWin Alt PrintScreenを押す、という動作。

 ちなみにプリントスクリーンだけで120行くらいあるので、SimConnectだけでは300行くらい。
 キャプチャはGeForce ExperienceのAlt F1だと楽なんだけど、WinゲームバーだとWinキーが必要で、これはSendKeys.Sendでうまく送れないので、Win32APIを叩く必要がある。


 SimConnectでできることを網羅した、というには程遠いが、ある程度のことはこのサンプルを改造するだけでできるはず。

 値の名前とか単位とかはLockheed MartinのWebページが詳しいのでそちらを参照のこと。ただしFSXとP3Dでは使える要素に違いがあると思うので、そのあたりはMicrosoftのドキュメントも参照。



public partial class Form1 : Form
{
    private const int WM_USER_SIMCONNECT = 0x0402;
    private const int WS_EX_NOACTIVATE = 0x8000000;
    private const string app_name = "SimConnect Tutorial";
    readonly Enum SIMCONNECT_UNUSED = (dummy_enum)0xFFFF;

    private SimConnect simconnect;

    public Form1()
    {
        InitializeComponent();

        Simconnect_OnRecvQuit(simconnect, null);

        Load += delegate (Object sender, EventArgs e)
        {
            // デザイナでもTopMostをtrueにしておくこと
            TopMost = true;
        };
    }

    private void button1_Click(Object sender, EventArgs e)
    {
        if (simconnect == null)
        {
            try
            {
                simconnect = new SimConnect(app_name, Handle, WM_USER_SIMCONNECT, null, 0);
                textBox1.Text = "Connecting...";

                simconnect.OnRecvOpen += Simconnect_OnRecvOpen;
                simconnect.OnRecvQuit += Simconnect_OnRecvQuit;
                simconnect.OnRecvSimobjectData += Simconnect_OnRecvSimobjectData;
                simconnect.OnRecvEvent += Simconnect_OnRecvEvent;
            }
            catch (COMException)
            {
                textBox1.Text = "Unable to Connect";
            }
        }
        else
        {
            Simconnect_OnRecvQuit(simconnect, null);
        }
    }

    private void Simconnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
    {
        textBox1.Text = "Connected to FSX";
        button1.Text = "Disconnect";

        #region

        // SIMCONNECT_DATA_REQUEST_FLAG.CHANGED 変化した時だけデータが来る
        // SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT 常にデータが来る
        // SIMCONNECT_DATA_REQUEST_FLAG.TAGGED 不明

        // SIMCONNECT_PERIOD.SIM_FRAME シミュレーション更新毎にデータが来る
        // SIMCONNECT_PERIOD.VISUAL_FRAME 画面更新毎にデータが来る
        // SIMCONNECT_PERIOD.SECOND 一秒ごとにデータが来る

        // PERIODとDATA_REQUEST_FLAGの組み合わせでデータが来るタイミングが変わる
        // VISUAL_FRAME, DEFAULTの場合は1フレームごとに常にデータが来る
        // SIM_FRAME, CHANGEDの場合はデータが変化したときだけデータが来る
        // SECOND, CHANGEDの場合は1秒毎に値をチェックし、変化していたらデータが来る(データチェックの1秒以内に複数回変化した場合、途中の値は得られない)

        add_simobject(Request_id.gear_handle_position, new[] {
                "gear handle position",
            }, null, SIMCONNECT_PERIOD.SIM_FRAME, SIMCONNECT_DATA_REQUEST_FLAG.CHANGED);

        add_simobject(Request_id.geo_position, new[] {
                "GPS POSITION LAT",
                "GPS POSITION LON",
                "GPS POSITION ALT"
            }, new[] {
                "degrees",
                "degrees",
                "meters",
            }, SIMCONNECT_PERIOD.SIM_FRAME, SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT);

        #endregion

        #region

        add_key_event(Group_id.user_input, Event_id.joystick_0_button_0);
        add_key_event(Group_id.user_input, Event_id.joystick_0_button_4);
        add_key_event(Group_id.user_input, Event_id.shift_and_tab);

        #endregion
    }

    private void Simconnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)
    {
        button1.Text = "Connect";
        textBox1.Text = "Disconnected";
        simconnect?.Dispose();
        simconnect = null;
    }

    private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)
    {
        if (data.dwData.Length == 1)
        {
            Request_id request_id = (Request_id)data.dwRequestID;

            if (false) { }
            else if (data.dwData[0].GetType() == typeof(s_x1_Int32))
            {
                s_x1_Int32 value = (s_x1_Int32)data.dwData[0];

                if (request_id == Request_id.gear_handle_position)
                {
                    gear_handle_position_CheckBox.Checked = value.value1 != 0;
                }
            }
            else if (data.dwData[0].GetType() == typeof(s_x3_double))
            {
                s_x3_double value = (s_x3_double)data.dwData[0];

                if (request_id == Request_id.geo_position)
                {
                    geo_position_TextBox.Text =
                        value.value1.ToString() + "\r\n" +
                        value.value2.ToString() + "\r\n" +
                        value.value3.ToString();
                }
            }
        }
    }

    private void Simconnect_OnRecvEvent(SimConnect sender, SIMCONNECT_RECV_EVENT data)
    {
        // data.dwData:1 press
        // data.dwData:0 release
        // キーボード入力の場合は、キーを押している間は常に連続した0が送られる

        Event_id event_id = (Event_id)data.uEventID;
        Group_id group_id = (Group_id)data.uGroupID;
        bool is_press = data.dwData != 0;

        if (group_id == Group_id.user_input)
        {
            switch (event_id)
            {
                case Event_id.joystick_0_button_0:
                    if (is_press)
                    {
                        gear_handle_position_CheckBox_Click(gear_handle_position_CheckBox, null);
                    }
                    break;
                case Event_id.joystick_0_button_4:
                    if (is_press)
                    {
                        send_win_alt_printscreen();
                    }
                    break;
                case Event_id.shift_and_tab:
                    gear_handle_position_CheckBox_Click(gear_handle_position_CheckBox, null);
                    break;
            }
        }
    }

    private void add_simobject(Request_id request_id, string[] datum_name, string[] units_name, SIMCONNECT_PERIOD period, SIMCONNECT_DATA_REQUEST_FLAG request_flag)
    {
        if (units_name == null)
        {
            units_name = new string[datum_name.Length];
        }

        SIMCONNECT_DATATYPE[] types = null;

        if (false) { }
        else if (typeof(T) == typeof(s_x1_Int32))
        {
            types = new[]
            {
                SIMCONNECT_DATATYPE.INT32,
            };
        }
        else if (typeof(T) == typeof(s_x3_double))
        {
            types = new[]
            {
                SIMCONNECT_DATATYPE.FLOAT64,
                SIMCONNECT_DATATYPE.FLOAT64,
                SIMCONNECT_DATATYPE.FLOAT64,
            };
        }
        else
        {
            throw (new Exception());
        }

        if (types.Length != datum_name.Length || types.Length != units_name.Length)
        {
            throw (new Exception());
        }

        dummy_enum define_id = (dummy_enum)typeof(T).GetHashCode();

        for (int i = 0; i < types.Length; i++)
        {
            simconnect.AddToDataDefinition(define_id, datum_name[i], units_name[i], types[i], 0, SimConnect.SIMCONNECT_UNUSED);
        }

        simconnect.RegisterDataDefineStruct(define_id);
        simconnect.RequestDataOnSimObject(request_id, define_id, SimConnect.SIMCONNECT_OBJECT_ID_USER, period, request_flag, 0, 0, 0);
    }

    private void add_key_event(Group_id group_id, Event_id event_id)
    {
        simconnect.MapClientEventToSimEvent(event_id, null);
        simconnect.AddClientEventToNotificationGroup(group_id, event_id, false);
        simconnect.MapInputEventToClientEvent(group_id, event_id.ToString().Replace("_and_", "+").Replace('_', ':'), event_id, 0, SIMCONNECT_UNUSED, 0, false);
        simconnect.SetInputGroupState(group_id, (uint)SIMCONNECT_STATE.ON);
    }

    protected override void DefWndProc(ref Message m)
    {
        if (m.Msg == WM_USER_SIMCONNECT)
        {
            simconnect?.ReceiveMessage();
        }
        else
        {
            base.DefWndProc(ref m);
        }
    }

    // フォームをクリックしてもアクティブ化しないように
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams p = base.CreateParams;

            if (!DesignMode)
            {
                p.ExStyle |= (WS_EX_NOACTIVATE);
            }

            return (p);
        }
    }

    private void gear_handle_position_CheckBox_Click(Object sender, EventArgs e)
    {
        if (simconnect != null)
        {
            s_x1_Int32 s = new s_x1_Int32() { value1 = !gear_handle_position_CheckBox.Checked ? 1 : 0 };
            simconnect.SetDataOnSimObject((dummy_enum)s.GetType().GetHashCode(), SimConnect.SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_DATA_SET_FLAG.DEFAULT, s);
        }
    }

    public void send_win_alt_printscreen()
    {
        INPUT[] inp = new INPUT[6];

        inp[0].type = INPUT_KEYBOARD;
        inp[0].ki.wVk = (short)Keys.LWin;
        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
        inp[0].ki.dwExtraInfo = 0;
        inp[0].ki.time = 0;

        inp[1].type = INPUT_KEYBOARD;
        inp[1].ki.wVk = (short)0x12;
        inp[1].ki.wScan = (short)MapVirtualKey(inp[1].ki.wVk, 0);
        inp[1].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
        inp[1].ki.dwExtraInfo = 0;
        inp[1].ki.time = 0;

        inp[2].type = INPUT_KEYBOARD;
        inp[2].ki.wVk = (short)Keys.PrintScreen;
        inp[2].ki.wScan = (short)MapVirtualKey(inp[2].ki.wVk, 0);
        inp[2].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
        inp[2].ki.dwExtraInfo = 0;
        inp[2].ki.time = 0;

        inp[3].type = INPUT_KEYBOARD;
        inp[3].ki.wVk = (short)Keys.PrintScreen;
        inp[3].ki.wScan = (short)MapVirtualKey(inp[3].ki.wVk, 0);
        inp[3].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
        inp[3].ki.dwExtraInfo = 0;
        inp[3].ki.time = 0;

        inp[4].type = INPUT_KEYBOARD;
        inp[4].ki.wVk = (short)0x12;
        inp[4].ki.wScan = (short)MapVirtualKey(inp[4].ki.wVk, 0);
        inp[4].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
        inp[4].ki.dwExtraInfo = 0;
        inp[4].ki.time = 0;

        inp[5].type = INPUT_KEYBOARD;
        inp[5].ki.wVk = (short)Keys.LWin;
        inp[5].ki.wScan = (short)MapVirtualKey(inp[5].ki.wVk, 0);
        inp[5].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
        inp[5].ki.dwExtraInfo = 0;
        inp[5].ki.time = 0;

        SendInput(inp.Length, ref inp[0], Marshal.SizeOf(inp[0]));
    }

    #region NonSoft - マウス操作やキーボード操作をシミュレーションするサンプル(C#.NET) (http://nonsoft.la.coocan.jp/SoftSample/CS.NET/SampleSendInput.html)

    // マウスイベント(mouse_eventの引数と同様のデータ)
    [StructLayout(LayoutKind.Sequential)]
    private struct MOUSEINPUT
    {
        public int dx;
        public int dy;
        public int mouseData;
        public int dwFlags;
        public int time;
        public int dwExtraInfo;
    };

    // キーボードイベント(keybd_eventの引数と同様のデータ)
    [StructLayout(LayoutKind.Sequential)]
    private struct KEYBDINPUT
    {
        public short wVk;
        public short wScan;
        public int dwFlags;
        public int time;
        public int dwExtraInfo;
    };

    // ハードウェアイベント
    [StructLayout(LayoutKind.Sequential)]
    private struct HARDWAREINPUT
    {
        public int uMsg;
        public short wParamL;
        public short wParamH;
    };

    [StructLayout(LayoutKind.Explicit)]
    private struct INPUT
    {
        [FieldOffset(0)]
        public int type;
        [FieldOffset(4)]
        public MOUSEINPUT mi;
        [FieldOffset(4)]
        public KEYBDINPUT ki;
        [FieldOffset(4)]
        public HARDWAREINPUT hi;
    };

    [DllImport("user32.dll")]
    private extern static void SendInput(int nInputs, ref INPUT pInputs, int cbsize);

    // 仮想キーコードをスキャンコードに変換
    [DllImport("user32.dll", EntryPoint = "MapVirtualKeyA")]
    private extern static int MapVirtualKey(int wCode, int wMapType);

    private const int INPUT_MOUSE = 0;                  // マウスイベント
    private const int INPUT_KEYBOARD = 1;               // キーボードイベント
    private const int INPUT_HARDWARE = 2;               // ハードウェアイベント

    private const int MOUSEEVENTF_MOVE = 0x1;           // マウスを移動する
    private const int MOUSEEVENTF_ABSOLUTE = 0x8000;    // 絶対座標指定
    private const int MOUSEEVENTF_LEFTDOWN = 0x2;       // 左 ボタンを押す
    private const int MOUSEEVENTF_LEFTUP = 0x4;         // 左 ボタンを離す
    private const int MOUSEEVENTF_RIGHTDOWN = 0x8;      // 右 ボタンを押す
    private const int MOUSEEVENTF_RIGHTUP = 0x10;       // 右 ボタンを離す
    private const int MOUSEEVENTF_MIDDLEDOWN = 0x20;    // 中央ボタンを押す
    private const int MOUSEEVENTF_MIDDLEUP = 0x40;      // 中央ボタンを離す
    private const int MOUSEEVENTF_WHEEL = 0x800;        // ホイールを回転する
    private const int WHEEL_DELTA = 120;                // ホイール回転値

    private const int KEYEVENTF_KEYDOWN = 0x0;          // キーを押す
    private const int KEYEVENTF_KEYUP = 0x2;            // キーを離す
    private const int KEYEVENTF_EXTENDEDKEY = 0x1;      // 拡張コード
    private const int VK_SHIFT = 0x10;                  // SHIFTキー

    #endregion
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct s_x1_Int32
{
    public Int32 value1;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct s_x3_double
{
    public double value1;
    public double value2;
    public double value3;
}

enum Request_id
{
    gear_handle_position,
    geo_position,
}

enum Event_id
{
    joystick_0_button_0,
    joystick_0_button_4,
    shift_and_tab,
}

enum Group_id
{
    user_input,
}

enum dummy_enum { };

0 件のコメント:

コメントを投稿