20190804のC#に関する記事は8件です。

C#でIMEの変換モードを監視・変更する

ときどきIMEが意図せずに全角英数モードになってたりして鬱陶しい(※)ので、
強制的にひらがなモードに変更するコードを書いてみた。
(このコードで問題ないかは自信ない)

// ※:無変換キーは押してないはずなんだけど・・・

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace TestIme
{
    public class Form1 : Form
    {
        public Form1()
        {
            Timer timer = new Timer();
            timer.Interval = 1000;
            timer.Tick += timer1_Tick;
            timer.Start();
        }

        [DllImport("User32.dll")]
        static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("imm32.dll")]
        static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);
        [DllImport("user32.dll")]
        static extern bool GetGUIThreadInfo(uint dwthreadid, ref GUITHREADINFO lpguithreadinfo);

        [StructLayout(LayoutKind.Sequential)]
        public struct GUITHREADINFO
        {
            public int cbSize;
            public int flags;
            public IntPtr hwndActive;
            public IntPtr hwndFocus;
            public IntPtr hwndCapture;
            public IntPtr hwndMenuOwner;
            public IntPtr hwndMoveSize;
            public IntPtr hwndCaret;
            public System.Drawing.Rectangle rcCaret;
        }

        const int WM_IME_CONTROL = 0x283;
        const int IMC_GETCONVERSIONMODE = 1;
        const int IMC_SETCONVERSIONMODE = 2;
        const int IMC_GETOPENSTATUS = 5;
        const int IMC_SETOPENSTATUS = 6;

        const int IME_CMODE_NATIVE    =  1;
        const int IME_CMODE_KATAKANA  =  2;
        const int IME_CMODE_FULLSHAPE =  8;
        const int IME_CMODE_ROMAN     = 16;

        const int CMode_HankakuKana = IME_CMODE_ROMAN | IME_CMODE_KATAKANA | IME_CMODE_NATIVE;
        const int CMode_ZenkakuEisu = IME_CMODE_ROMAN | IME_CMODE_FULLSHAPE;
        const int CMode_Hiragana    = IME_CMODE_ROMAN | IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE;
        const int CMode_ZenkakuKana = IME_CMODE_ROMAN | IME_CMODE_FULLSHAPE | IME_CMODE_KATAKANA | IME_CMODE_NATIVE;
        // 実験してみた結果
        // 19 :カ 半角カナ                     0001 0011
        // 24 :A 全角英数                     0001 1000
        // 25 :あ ひらがな(漢字変換モード)   0001 1001
        // 27 :   全角カナ                     0001 1011

        // 半角カナ/全角英数/カタカナ モードを強制的に「ひらがな」モードに変更する
        private void timer1_Tick(object sender, EventArgs e)
        {
            //IME状態の取得
            GUITHREADINFO gti = new GUITHREADINFO();
            gti.cbSize = Marshal.SizeOf(gti);

            if ( !GetGUIThreadInfo(0, ref gti) ) {
                Console.WriteLine("GetGUIThreadInfo failed");
                throw new System.ComponentModel.Win32Exception();
            }
            IntPtr imwd = ImmGetDefaultIMEWnd(gti.hwndFocus);

            int  imeConvMode = SendMessage(imwd, WM_IME_CONTROL, (IntPtr)IMC_GETCONVERSIONMODE, IntPtr.Zero);
            bool imeEnabled = (SendMessage(imwd, WM_IME_CONTROL, (IntPtr)IMC_GETOPENSTATUS, IntPtr.Zero) != 0);

            Console.WriteLine(imeEnabled.ToString() + " status code:"+imeConvMode.ToString());

            if ( imeEnabled ) {
                switch ( imeConvMode ) {
                case CMode_Hiragana:
                    /* Nothing to do */
                    break;
                case CMode_HankakuKana: /* through */
                case CMode_ZenkakuEisu: /* through */
                case CMode_ZenkakuKana:
                    SendMessage(imwd, WM_IME_CONTROL, (IntPtr)IMC_SETCONVERSIONMODE, (IntPtr)CMode_Hiragana); // ひらがなモードに設定
                    break;
                default:
                    /* Nothing to do */
                    /* 環境によっては上のcaseをやめてここに飛ばしたほうがよいかも */
                    break;
                }
            }/* else 無変換(半角英数) */
        }

        [STAThread]
        static void Main(){
            Application.Run(new Form1());
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フィボナッチを使ったポジション計算

S__35250192.jpg

フィボナッチを使ったトレード用 計算ツール

FXでトレードする際の利確・損切ライン、利益・損失額の計算ができる簡単なプログラムを作ってみました。
この作成の目的は動作するものを作ることだけではなく、オブジェクト指向な書き方に挑戦することが目的です。

 見た目

image.png

 コード内容

フォーム

Form1.cs
namespace Fibonacci_Form
{
    public partial class Form1 : Form
    {
        private IntPtr HHook;

        #region フォーム初期化
        public Form1()
        {
            InitializeComponent();
        }
        #endregion

        #region 横線描画
        // 画面中央に引く横線を作成するメソッド
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            // グラフィックオブジェクトの作成
            Graphics g = this.CreateGraphics();

            // Penを作成
            Pen grayPen = new Pen(Color.Gray, 2);

            // lineの始点と終点を設定
            Point start_point = new Point(25, 250);
            Point end_point = new Point(480, 250);

            // lineを描画
            g.DrawLine(grayPen, start_point, end_point);

            // Penを解放する
            grayPen.Dispose();

            // Graphicsを解放する
            g.Dispose();
        }
        #endregion

        #region 買ボタン押下
        // 買ボタン押下時
        private void Buy_btn_Click(object sender, EventArgs e)
        {

            try
            {
                Property p = new Property();

                // ロット数
                p.Lot = int.Parse(lot_text.Text);
                // 通貨数
                p.Tsuka = int.Parse(tsuka_combo.Text);
                // 0%座標
                p.Zero = double.Parse(zero_text.Text);
                // 100%座標
                p.Hundred = double.Parse(hundred_text.Text);
                // エントリーポイント
                p.Entry = entry_combo.Text;


                // 結果を入れるリスト
                List<string> sList = new List<string>();
                Calc c = new Calc();
                sList = c.CalcAskResult(p.Lot, p.Tsuka, p.Zero, p.Hundred, p.Entry);

                // 結果を表示
                lost_num.Text = sList[0];
                win_num.Text = sList[1];
                loss_amount.Text = "-" + sList[2] + " 円";
                profit_amount.Text = sList[3] + " 円";

            } catch
            {
                SetHook(this);
                MessageBox.Show("値を正しく入力してください",
                                "エラー",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Error);

                lost_num.Text = null;
                win_num.Text = null;
                loss_amount.Text = null;
                profit_amount.Text = null;
            }
        }
        #endregion

        #region 売ボタン押下
        #endregion

        #region メッセージボックスを中央に表示するための処理(外部引用)
        #endregion
    }
}

 気を付けたこと

  • フォームから取って来た値を変数に入れるのではなく、プロパティを使用する
  • 誤った値が入った場合・0%と100%が逆の状態の場合はエラーを表示する
  • タブ順を指定した
  • イベントが呼ばれた時に必要な処理、もしくはフォームに出力すること以外はここでは行わないようにする

 プロパティ

Property.cs
namespace Fibonacci_Form
{
    public class Property
    {
        // 自動実装プロパティ
        public int Lot { get; set; }
        public int Tsuka { get; set; }
        public double Zero { get; set; }
        public double Hundred { get; set; }
        public String Entry { get; set; }

    }
}

 気を付けたこと

  • プロパティの存在の理解…

 計算メソッド

Calc.cs
namespace Fibonacci_Form
{
    public class Calc
    {
        #region 買い計算
        // 損切ライン・利確ライン・損失・利益を計算(買いポジションなので0%をlow、100%をhighとする)
        public List<string> CalcAskResult(int lot, int tsuka, double low, double high, string entry)
        {
            List<string> sList = new List<string>();
            ListMake lm = new ListMake();

            // 0%と100%の大小が逆の場合、空のリストを返す
            if (high <= low)
            {
                return sList;
            }

            double lose_line;
            double win_line;
            double loss;
            double profit;
            double entry_point;

            // 取引金額を計算
            int amount = lot * tsuka;

            if (entry == "100%")
            {
                // 損切ラインを計算
                lose_line = Math.Round((low + (high - low) * 0.764) * 1000) / 1000;
                // 利確ラインを計算
                win_line = Math.Round((low + (high - low) * 1.618) * 1000) / 1000;
                // 損失を計算
                loss = Math.Round(amount * (high - lose_line));
                // 利益を計算
                profit = Math.Round(amount * (win_line - high));

                // ListAdd
                sList = lm.ListAdd(lose_line.ToString(), win_line.ToString(), loss.ToString(), profit.ToString());
            }
            else if (entry == "161.8%")
            {
                // エントリーポイントを計算
                entry_point = Math.Round((low + (high - low) * 1.618) * 1000) / 1000;

                // 損切ラインは100%ラインなので、high

                // 利確ラインを計算
                win_line = Math.Round((low + (high - low) * 2.618) * 1000) / 1000;
                // 損失を計算
                loss = Math.Round(amount * (entry_point - high));
                // 利益を計算
                profit = Math.Round(amount * (win_line - entry_point));

                // ListAdd
                sList = lm.ListAdd(high.ToString(), win_line.ToString(), loss.ToString(), profit.ToString());
            }
            else if (entry == "261.8%")
            {
                // エントリーポイントを計算
                entry_point = Math.Round((low + (high - low) * 2.618) * 1000) / 1000;

                // 損切ラインを計算
                lose_line = Math.Round((low + (high - low) * 1.618) * 1000) / 1000;
                // 利確ラインを計算
                win_line = Math.Round((low + (high - low) * 4.236) * 1000) / 1000;
                // 損失を計算
                loss = Math.Round(amount * (entry_point - lose_line));
                // 利益を計算
                profit = Math.Round(amount * (win_line - entry_point));

                // ListAdd
                sList = lm.ListAdd(lose_line.ToString(), win_line.ToString(), loss.ToString(), profit.ToString());
            }

            return sList;
        }
        #endregion

        #region 売り計算
        #endregion
    }
}

 気を付けたこと

  • 最初は計算だけでなくリストに追加するところまで書いていたが、ここはあくまで計算だけをするメソッドだと思い、計算結果をリストに追加するメソッドを別に作り、呼ぶようにした
  • 計算結果を正しく出すのに苦労した

 リスト作成

ListMake.cs
namespace Fibonacci_Form
{
    public class ListMake
    {
        public List<string> ListAdd(string a, string b, string c, string d)
        {
            List<string> sList = new List<string>();

            sList.Add(a);
            sList.Add(b);
            sList.Add(c);
            sList.Add(d);

            return sList;
        }
    }
}

 エラー

最後に、不正な動作があった場合の動作
image.png
(画面中央にメッセージを出せたのは、外部からコードを丸々引用したため)
               ↓
image.png
下に表示されていた計算結果がクリアされます。

 今後の計画

自分の取引結果を登録して、履歴を検索して表示できたり、エクセルに出力できるようなシステムを作ろうかと考えています。
データベースとのやり取りや、エクセルが絡むので、まだ書いたことの無いコードを学べると思っています。
また、今回作成したものよりはかなり多いコードの量になると思われるので、ちゃんと内容を切り分けてオブジェクト指向的なコーディングをする勉強になるのかなと思っています。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NETの非同期処理についてまとめてみました

はじめに

半年に1回くらい調べ直してる気がするので、このタイミングでまとめてみました。
特別詳しいわけではないので、認識違いあったらご指摘お願いしますmm

なぜ.NET(ASP.NET)ではリクエストの戻りをTask<T>にするのか

前提

  • 同時に大量のリクエストを受けると、リクエストがIISのワーカースレッドを食いつぶし、処理が止まる。
  • IISのワーカースレッドはマネージドスレッドと呼ばれる。
  • リクエストを受けるのはマネージドスレッド。
  • スレッドはスレッドプールで管理されている。

async/await Task

  • 処理を非同期にすることで重い処理をマネージドスレッドとは別のワーカースレッドで行わせる。マネージドスレッドはそのときスレッドプールに返却される。
  • つまりマネージドスレッドの最大数より多くのリクエストを処理することができる。(ASP.NET4.5では1論理コア当たり最大で100スレッドなので、それより多くのリクエストを処理できるようになるということだろう…)
  • .NETの非同期は歴史上いろんな書き方があるけど、今どきの.NETではasync/awaitで書いておけばOK。
  • Task.Resultはそのメインスレッドをロックして待つことになるので、スレッドプールに返却はされない(はず)

下のほうにもうちょっと詳しく書いてます。

参考:
- .NET で非同期 (Async) がなぜ重要なのか

同期コンテキスト(SynchronizationContext

  • 非同期処理の後にメインスレッドに戻るための情報を同期コンテキストという。
  • awaitを使用すると同期コンテキストを保持し、その処理を実行したスレッドに戻ってくることができる。
  • 何度もメインスレッドに戻したくない場合はContinueWithとかで数珠つなぎにすると良い。

e.g.

await AsyncMethodA().ContinueWith(_ => HeavyWork());
  • ConfigureAwait(false)を使用することで同期コンテキストを捨てることができる。1度でも捨てると、そのスレッドはメインスレッドに戻ることができない。
  • 同期コンテキストを保持する場合は、呼び出し元のスレッドに戻る前にコンテキストを破棄してしまうと、エラーになる。

CUIアプリケーション

  • 同期コンテキストを持たない

GUIアプリケーション

  • GUIアプリケーションでは画面の描画ができるのはUIスレッドのみ。
  • つまり、処理を別スレッドで非同期で行っても、その結果で画面を更新する場合は、同期コンテキストを使用してUIスレッドに戻らないといけない。

Webアプリケーション(ASP.NET)

非常にわかりやすかったのでこちらから引用。

ASP.NET の SynchronizationContext は何をするのかと言うと、HttpContext.Current を適切に設定するものらしいです。
HttpContext.Current は、リクエスト スレッドが現在処理中のリクエストに関する情報を持っています。
リクエスト スレッドから非同期処理が呼ばれてワーカー スレッドが作られても、ワーカー スレッドも同じリクエストを処理していると言えるわけですから、リクエスト情報をスレッド間で共有しているわけです。

  • ASP.NETにおいてawaitを使って同期コンテキストを保持し、リクエスト情報を橋渡ししている場合、どのワーカースレッドからもリクエストを返すことができるので、マネージドスレッドに戻る必要はない。
  • リクエスト情報の受け渡しはまあまあなオーバーヘッドらしい。

クラスライブラリ

  • クラスライブラリ内で同期コンテキストを拾うと意図せぬタイミングでデットロックが起きるので、awaitとセットでConfigureAwait(false)を使用して同期コンテキストを破棄してあげるのがよい。

参考:
- 小ネタ 同期コンテキストを拾わないTask型
- async/awaitと同時実行制御
- asyncの落とし穴Part2, SynchronizationContextの向こう側

クラスライブラリで非同期メソッド(async)を公開する必要があるか

e.g.

public T Foo(){}
public Task<T> FooAsync() => Task.Run(() => Foo); // 必要?
  • 単純にTask.Run()でラップするだけなら意味がないし、開発工数が増えるだけ。Taskにする分、スループットも低下する。
  • その処理が非同期処理の恩恵を受けているかを、ライブラリを使用する側に解らせるため、意味がある場合は非同期メソッドとして公開する必要がある。
  • 意味がある処理とは、DBアクセスやファイルI/O、APIリクエストなど、メインスレッドをスレッドプールに返却する処理など(と、認識しいます…)。
  • 同期メソッドを非同期的に使用するかはライブラリを使用する側が判断するべき。
  • デッドロックの原因にもなる。

i.e.「非同期APIを同期APIとしてラップ」「同期APIを非同期APIとしてラップ」をやらないことが重要。

参考:
- Should I expose asynchronous wrappers for synchronous methods?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[.NET][消費税] 税込価格から本体価格を逆算する式(端数処理方式別)

税込価格から本体価格を算出する方法は、消費税の端数処理方式によって異なります。
たとえば税率8%で税込価格が110円の場合、切り上げ方式で端数処理した消費税なら本体価格は101円ですが、切り捨て方式で端数処理した消費税なら本体価格は102円です。

以下、端数処理方式別の算出式です。
※rate には、0.05m, 0.08m, 0.10m などの消費税率(正の値)が入ります。

四捨五入方式で端数処理した税込価格 → 本体価格

C#
Math.Ceiling((priceWithTax - 0.5m) / (1m + rate))
VB.NET
Math.Ceiling((priceWithTax - 0.5D) / (1D + rate))

切り捨て方式で端数処理した税込価格 → 本体価格

C#
Math.Ceiling(Math.Abs(priceWithTax) / (1m + rate)) * (priceWithTax >= 0 ? 1 : -1)
VB.NET
Math.Ceiling(Math.Abs(priceWithTax) / (1D + rate)) * If(priceWithTax >= 0, 1, -1)

切り上げ方式で端数処理した税込価格 → 本体価格

C#
Math.Truncate(priceWithTax / (1m + rate))
VB.NET
Math.Truncate(priceWithTax / (1D + rate))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでRhinoInsideを使ってVRアプリを作ってみる

はじめに

 この記事はRhino7(2019/7現在WIP版)に向けて開発されている、RhinoInside(公式サイトはこちら )をつかって、UnityからRhinoの機能を呼び出してVRの中で形をいじることができるアプリの作り方について説明していきます。
 そもそもとはRhinoとは何かというと、フリーフォームNURBSモデリングに特化した商用の製造業向け3次元CADソフトウェア(3Dサーフェスモデラー)で、その中のジオメトリの計算機能をSDKから呼び出してRhino以外のソフトからでも使えるようになったのがRhinoInsideです。

完成品

RhinoInside Unity のサンプルさわるまでのやり方

 まずRhinoInsideをUnityで使えるようにします。やり方は公式のgithubに掲載の通りですが、READMEの内容をざっくり説明します。

必要なもの

  1. GITのクライアント(ダウンロード)
  2. RhinoWIP(ダウンロード)(Rhinoのライセンスを持っていないと使えません)
  3. Unity(ダウンロード)

公式githubを参考にやり方

 公式githubのREADMEではUnityのバージョンは 2018.3ですが、私のUnityの環境は後述のsteamVRの使い方がよくわかなかったので、2017.4です。
 RhinoInsideはgithubにあるので、コマンドラインから
 git clone --recursive https://github.com/mcneel/rhino.inside.git rhino.inside
をして、githubのものをクローンするか、以下のようにgithubからそのままZIPファイルでダウンロードして下さい。

 githubからそのままZIPファイルでダウンロード

 その後、Unityからダウンロードしたフォルダの中のRhino.Inside/Unity/Sample1 のプロジェクトを開きます。サンプルの操作は、githubのREADMEの説明が動画になっているいてわかりやすいと思いますのでそちらをどうぞ。
 UnityのmenubarのSample1からCreateLoftSurfaceをやるとUnityのSceneの画面にLoftされたSurfaceが表示されます。このLoftSurfaceの作成にRhinoInsideが使われています。

VRでアバターを動かす環境整理

 次にVR側の話です。こちらはあきら(@sh_akira)さんのQiita、UniVRM+SteamVR+Final IKで始めるを参考にやりました。詳細はそちらを見てください。

必要なもの

 1. HTC Vive
 2. VR Ready PC
 3. UniVRM
 4. SteamVR plugin 2.0.1(リンク先のあきらさんの記事にあるように私の環境でもうまくいかなかったので、ver2.0.1を使っています)
 5. Final IK (こちらは有料なので、注意)
 6. OVRLipSync(リップシンクはうまくいかなかったので、使ってないです)
 7. AniLipSync-VRM(リップシンクはうまくいかなかったので、使ってないです)
 8. VRMモデル(今回はアリシアソリッドちゃんを使ってます。)

RhinoInsideをVRでいじれるようにしていく

 まず現状のUnityの状態の確認です。上記に2つをやると以下の画像のような感じのSceneになっているはずです。左下があきらさんの記事をもとにやったVRの環境、右上がRhinoInsideのSample1をやって、LoftSurfaceを作ったものになっています。(LoftSurfaceは自分でデバックしやすいように場所を少しうつしています。)

 一通りやった感じ

 後はあきらさんの記事から変えた点について説明していきます。基本的にはそのままで、リップシンクだけエラーがでてうまくいかなかったので、使っていないです。

VRで制御点をいじれるようにする

 ここからVRでインタラクションするための設定についてです。SteamVR plugin にはインタラクションするための機能がついています。例えば物をつかむとか、対象先にテレポートするとかです。
 今回はRhinoのLoftSurfaceの制御点(画面中青い球)をVRからインタラクトすることを考えます。やり方は簡単で、対象とするsphereにAdd Component で Throwable を追加します。

 Throwable を追加

 Throwableを追加すると必要なコンポーネントも同時に追加されます。

 必要なコンポーネントも同時に追加

 Rigidbodyの中で、UseGravityの項目(画像の赤線部)がありますがfalseにしています。Trueにしておくと物理演算で重力適用されるので、Playモードにするとそのまま球が落ちていきます。
 次につかんで離した後の挙動ですが、Throwableの名前の通りデフォルトでは投げる(はなした時の手の速度で飛んでいく)設定になっています。(青下線部)これを選択で「NoChange」にすると話した点で止まるようになります。
 これらの設定をすれば、VR内で設定した球がつかめるようになります。

親子関係の維持

 Sphereをつかめるようになりましたが、このままだとRhinoInsideでうまくジオメトリの計算をしてくれません。
 このSample1で作成されるLoftSurfaceは、親(オブジェクト名LoftSurface)の、子になっているオブジェクト(ここではSphere)を制御点として形状をコントロールしています。しかし今の設定のままでは、Playモード中に物をつかむとこの親子関係が崩れてしまい、つかんだものが親から離れてしまいます。
 そこで、親子関係を維持するコンポーネントを追加します。Assetを右クリックしてCreate→C#Scriptで新たにC#のスクリプトを作成します。

  Create→C#Script

 作成したC#スクリプトは以下です。Unityで作ったC#スクリプトのUpdataのところに以下を追加してください。ただ毎フレームごとに Loft Surface というオブジェクトの子にしているだけです。

Parent.cs
public class Parent : MonoBehaviour {
    void Update () {
        transform.parent = GameObject.Find ("Loft Surface").transform;
    }
}

 これを対象のSphereに追加すれば、つかんで親子関係が外れても、すぐにLoftSurfaceの子に戻ります。とりあえず動くものにしているだけなので、このままだと実は問題がありますが許してください。ちなみに問題点は以下です。

  • 親に対して子として一番下に追加されるだけなので、子の中の順番が維持されない。(制御点の順番にも意味があるのでこの順番がずれると形がおかしくなる)
  • 1フレームごとに呼び出される void Update() の部分にそのまま書いているので、つかんでも1フレームごとに手から離れてしまう。

 解決策はわかっていて、1つ目であれば、親子関係の順番を記録してその順番で子にすればよく、2つ目であればつかんでいる状態を判定してそれがTrueなら親子関係をもどすスクリプトを動かさなければよいだけです。そのうち作ります…
 記事の一番の動画で私が操作しているものは、親子関係で最初から一番下のもの投げているだけなので、サーフェスの形状が崩れず一見手に追従して形状が変化しているように見えてます。

VRアプリとして出力する

 次にアプリとして出力する方法についてです。初めに、以下の作業をする前に、これより上の作業を完了しておいてください。出力用にいくつかのC#のスクリプトをいじったりするので、上の操作が終わっていないとうまく動かなくなる可能性があります。

とりあえずBuildしてみる

 UnityのFileメニューからBuild Settings...を選びBuildを行います。ですがこのままだとエラーで出力がされません。Buildするものの中で、UnityのエディタのUIそのものをいじるものがあるとエラーになります。(出力するアプリはUnityではないので、UnityのUIをいじるものがあるのはおかしい)

UnityEditor関連をなくしていく

 そこで、UnityEditorに関係するものを修正してい行きます。まずは Standard Assets/RhinoInside のUnityのファイルを以下のように Using UnityEditor と [InitializeOnLoad] の二つをコメントアウトします。

Assets/RhinoInside/Unity

Unity.cs
using System;
using System.Reflection;
using System.IO;

using UnityEngine;
// using UnityEditor;

using Rhino;
using Rhino.Runtime.InProcess;

namespace RhinoInside.Unity
{
  // [InitializeOnLoad]
  static class Startup

 同じく Standard Assets/RhinoInside の中にある UI ファイルを Assets の下にEditorというフォルダを作ってそちらに移します。直下の Editor のフォルダにあるものはBuildの際に読み込まれない個所になっているそうです。ちなみにこのUIはUnityのmenubarにGrasshopperを追加したりしているだけで本当にUIを操作しているだけのものです。

 UIファイル移動

 次に実際にLoftSurfaceを操作しているLoftSurfaces.csファイルをいじります。変更点は以下

  1. UnityEditorにかかわるものをコメントアウト
  2. void Start() 内にRhinoを起動させ、ウインドウを最小化させる部分を追加

 変更した個所を抜粋したC#スクリプトは以下です。

LoftSurface.cs(変更箇所その1)
using Rhino;
using Rhino.Runtime.InProcess;

using System;
using System.Reflection;
using System.IO;
using System.Collections;
using System.Collections.Generic;

using UnityEngine;
// using UnityEditor;

namespace RhinoInside.Unity.Sample1
{

  // [InitializeOnLoad]
  // [ExecuteInEditMode]
  public class LoftSurface : MonoBehaviour
  {
    // [MenuItem("RhinoInside/Create Loft Surface")]
    public static void Create()
    {
      var surface = new GameObject("Loft Surface");
      surface.AddComponent<LoftSurface>() ;
    }
LoftSurface.cs(変更箇所その2)
   const int VCount = 3;

    void Start()
    { 
      //ここを追加 ---------------------
      string RhinoSystemDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Rhino WIP", "System");
      bool isLoaded = Environment.GetEnvironmentVariable("PATH").Contains(RhinoSystemDir);
      var PATH = Environment.GetEnvironmentVariable("PATH");
      Environment.SetEnvironmentVariable("PATH", PATH + ";" + RhinoSystemDir);
      GC.SuppressFinalize(new RhinoCore(new string[] { "/scheme=Unity", "/nosplash" }, WindowStyle.Minimized));
      //ここを追加 ---------------------

      gameObject.AddComponent<MeshFilter>();

      var material = new Material(Shader.Find("Standard"))
      {
        color = new Color(1.0f, 0.0f, 0.0f, 1f)
      };

      gameObject.AddComponent<MeshRenderer>().material = material;

 これでBuildすれば UnityEditor に関するエラーが出なくなるはずです。
 無事Buildが完了して出力先の.exeファイルを起動すればRhinoInsideを使ったVRアプリが起動するはずです。

完成!

 冒頭の完成品を再掲です。

  RIUApp.gif

 今後は、中でもうちょっと動けるようにしたりだとか、上記であったつかんだ際の親子関係の問題とかを直していければと思ってます。

ライセンス

この記事内でいじっているRhinoInside関係のコードはMITライセンスです。プラグインについては各ライセンスに従ってください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Solitaire(1)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Solitaire : MonoBehaviour
{

    public static string[] suits = new string[] { "C", "D", "H", "S" };
    public static string[] values = new string[] { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};


    public List<string> deck;

    // Start is called before the first frame update
    void Start()
    {
        PlayCards();
    }

    // Update is called once per frame
    void Update()
    {

    }

    public void PlayCards()
    {
        deck = GenerateDeck();
        Shuffle(deck);

        //test the cards in the deck:
        foreach (string card in deck)
        {
            print(card);
        }
    }

    public static List<string> GenerateDeck()
    {
        List<string> newDeck = new List<string>();
        foreach (string s in suits)
        {
            foreach (string v in values)
            {
                newDeck.Add(s + v);
            }
        }


        return newDeck;

    }


    void Shuffle<T>(List<T> list)
    {
        {
            System.Random random = new System.Random();
            int n = list.Count;
            while (n > 1)
            {
                int k = random.Next(n);
                n--;
                T temp = list[k];
                list[k] = list[n];
                list[n] = temp;
            }
        }
    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FF14 のメモリリーダー"Sharlayan" の動かし方

はじめに

本記事は、FF14のメモリーリーダーSharlayan (GitHubリンク)のサンプルコードを動かすための記事です。
また、FF14の規約がどうとかという話題は一度外に置いておき、あくまで技術的な部分に注目します。

今回は、インベントリデータを使ってサンプルコードを説明していきます。
また、先に言っておくと私も完全な正解を知ってるわけではありません。
こうしたらこういうデータが取れたよ!!という程度のスタンスです。

前提

本記事では当たり前のように内部のアイテムIDを取り扱います。
何らかの方法でIDから名前を引く方法を得ておくと便利だと思います。
たぶんというレベルで言えばFFXIVAPI のAPIにアイテムID投げると帰ってくるJSONでいいことがありそうです。

https://xivapi.com/Item/3?language=ja

  • アイテムIDが3(アイスシャード)の場合
  • JSONはFirefoxで見ると見やすいです

Sharlayanとは

C#製のFF14のメモリを読むライブラリです。
メンテナンスがあまりされていないのか一部のみ動くような状態で、使用者は自前で修正して使っているのではないかな?という感じを受けます。
聞きかじった話では5.0よりライセンスがGPLから別のものに変わったようですが調べてないので、教えていただけると幸いです。

使い方

Sharlayan (GitHubリンク)のReadmeを見ましょう。

・・・と言いたいのですが、古いのかこれ動かないんですよね。なので自分用にサンプルを残しておきます。

第一段階:環境設定

ソースコードを用意してあなたのプロジェクトに組み込む

方法はいくつもあると思いますが、ここではGitHubからソースコードを落としてくることにします。
理由は後でソースを修正したいからです。じゃないとまともに動かない部分がたくさんあります。

あとは適当なプロジェクトで参照出来るようにしましょう。
私はVisual Studio2017でコンソールプロジェクトにSharlayanのソースコードを追加しました。

第二段階:コーディング

FF14のプロセスにアタッチする

文字通り、FF14のプロセスにアタッチします。
大雑把に言うと自作のプログラムからFF14のプロセスにアクセスするための準備ですね。

ReadmeではDirectX9の例もありましたが、ここではDirectX11のサンプルを修正しておきます。
(命名がやっつけ感以外の何物でもないのは見ないふりでお願いします)

using Sharlayan;
using Sharlayan.Core;
using Sharlayan.Models;
using Sharlayan.Models.ReadResults;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            // DX11
            Process[] processes = Process.GetProcessesByName("ffxiv_dx11");
            if (processes.Length > 0)
            {
                string gameLanguage = "Japanese";
                bool useLocalCache = true;
                string patchVersion = "latest";
                Process process = processes[0];
                ProcessModel processModel = new ProcessModel
                {
                    Process = process,
                    IsWin64 = true
                };
                MemoryHandler.Instance.SetProcess(processModel, gameLanguage, patchVersion, useLocalCache , true);

                // 以下、Readerクラスのstaticメソッドを用いてメモリデータを取得する
            }
        }
    }
}

情報を取得する

上記のコードでsetProcessしてアタッチ準備が出来たはずですが、実際は2〜3秒かかります。ここではThread.Sleep(1000)を回してますがメインスレッドが停止してしまうので実際のコードでは色々やったほうがが良いのでしょう。
ここではこれで動くのでとりあえずThreal.Sleep(1000)してます。
もちろん無限ループしたくなければcountでも取るなり何なりして終了させてください。

while(!Reader.CanGetInventory()){
    Thread.Sleep(1000)
}

このループを抜けたらインベントリ情報取得が可能です。
今回はインベントリデータ(キャラの所持品のことですね)を取得します。

InventoryResult readResult = Reader.GetInventory();

キャラの所持品は内部的に4つのデータに分かれています。ゲームをやっている方ならインベントリが4つのタブに分かれていてそれぞれ35個ずつアイテムが配置できるのをご存知かと思います。内部メモリ的にも1つ35件のデータが4つあるようです。

また、4つのどのデータにどのアイテムが入っているかはバラバラのようです。
全てのデータを集めてから集計・ソートする必要がありそうです(未検証)

foreach( InventoryContainer ic in readResult.InventoryContainers){
    foreach(InventoryItem ii in ic.InventoryItems){
        // ii.ID   アイテムID
        // ii.Amount アイテムの個数
        // ii.isHQ   アイテムがHQかどうか(boolean)
        Console.WriteLine("アイテムID:" + ii.ID + ",アイテム個数:" + ii.Amount)
    }
}

これで、プレイヤーが現在取得しているインベントリアイテムが取得出来ます。
InventoryItemクラスには他にも属性があるので、それを参照することでいろいろな情報が取得できるはずです。

・・・はずです。

第三段階:実行

実行できた方は様々なインベントリデータがコンソールに表示されたかと思います。
32bitのプロセスから64bitのプロセスにはアクセスできないよ〜みたいなエラーが出た方は、例えばVisual Studioならプロジェクト?の設定でプロジェクト?のCPUの環境を「Any CPU」ではなくて「x64」にすることで動きました。

ただ、実際にデータを取得して表示された方は出力のアイテムIDがめちゃくちゃだったり所持品以外の意味不明なデータだったりがたくさん混じっていることにお気づきかと思います。

当然これはなんとかしたいですね。

次回:データをより分けて読み解く

というわけで、次回はそのデータを選り分けて欲しいデータを取得する事を試みます。

実際には、インベントリーデータの他にアーマリーチェストデータ、その時点で実際に装備しているアイテムデータ、最後に開いたリテイナーのアイテム情報・装備情報・所持金情報、最後に開いたFCチェストの情報などが入っているようです。

メモリ上のデータ構造は実際には下記のEnumsのタイプごとにデータの構成が異なるようで、INVENTORY_1〜4あたりは件数を絞るだけで見られるデータが得られるのですが、AC_MH(主武器)を始めとしてAC_**(アーマリーチェスト**)の値が実際のデータとずれていたりしてその他は結構ひどい有様です。見ての通り値はbyteで、これをもとにメモリ上のアドレスを生成してデータを参照し、読み取る形になっています。

0x1Aとか0x1Bとか空いてるけどいいの?みたいな疑問もあったりします。

Sharlayan.Core.Enums.Inventory
namespace Sharlayan.Core.Enums {
    public class Inventory {
        public enum Container : byte {
            INVENTORY_1 = 0x0,

            INVENTORY_2 = 0x1,

            INVENTORY_3 = 0x2,

            INVENTORY_4 = 0x3,

            CURRENT_EQ = 0x4,

            EXTRA_EQ = 0x5,

            CRYSTALS = 0x6,

            QUESTS_KI = 0x9,

            HIRE_1 = 0x12,

            HIRE_2 = 0x13,

            HIRE_3 = 0x14,

            HIRE_4 = 0x15,

            HIRE_5 = 0x16,

            HIRE_6 = 0x17,

            HIRE_7 = 0x18,

            AC_MH = 0x1D,

            AC_OH = 0x1E,

            AC_HEAD = 0x1F,

            AC_BODY = 0x20,

            AC_HANDS = 0x21,

            AC_BELT = 0x22,

            AC_LEGS = 0x23,

            AC_FEET = 0x24,

            AC_EARRINGS = 0x25,

            AC_NECK = 0x26,

            AC_WRISTS = 0x27,

            AC_RINGS = 0x28,

            AC_SOULS = 0x29,

            COMPANY_1 = 0x2A,

            COMPANY_2 = 0x2B,

            COMPANY_3 = 0x2C,

            COMPANY_CRYSTALS = 0x2D
        }
    }
}

この辺の設定は謎だらけなのですが、次回の記事は私がちょっとだけ調べたEnumsの設定と取得データのより分け方をご説明します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

private な Generics Type のインスタンス化

はじめに

Unit Test を書く際に private や internal なクラスのインスタンスを作りたくなることがあります。
(internal については自分で書いたライブラリであれば InternalsVisibleTo でテスト用のプロジェクトを指定すればテストが簡単になる)

この時、Generics ではない普通のクラスであれば AssemblyGetType メソッドにクラス名を指定してあげることで型情報を取得することができます。
しかし Generics の場合、型をパラメータとして与えることができるため、型情報を取得するときに名前の指定方法を少し気を付ける必要があるのでその情報をまとめます。

なおこのサンプルで使用する Unit Test のライブラリは xUnit を使用していることとします。

方法

例えば Sample.csproj の Sample.cs に以下のような Sample クラスが定義されていたとします。

Foo.cs
namespace Sample
{
    class Foo<TBar, TBaz>
    {
        public TBar Bar { get; set; }
        public TBaz Baz { get; set; }
    }
}

このクラスをテストプロジェクトである Sample.Tests.csproj でインスタンス化するには以下のようにします。

FooTest.cs
using System:
using System.Reflection;
using Xunit;

namespace Sample.Tests
{
    public class Bar
    {
    }

    public class Baz
    {
    }

    public class FooTest
    {
        [Fact]
        public void Test()
        {
            // Hoge は Sample.csproj で public なクラス
            // テスト対象の Assembly を取得する方法はいくつかあるがライブラリのテストであれば
            // プロジェクトの参照を持っているだろうし public なクラスもあるはずなので GetAssembly で取得するのが楽
            var asm = Assembly.GetAssembly(typeof(Hoge));

            // Generics の場合はパラメータとして与えられた型の個数を「`」の後に指定する
            // そして具体的に Generics のパラメータにどのような型を指定するかは
            // 型の最後に[[{型の名前1}],[{型の名前2}],...,[{型の名前n}]]のように指定する
            var type = asm.GetType("Sample.Foo`2[[Sample.Tests.Bar, Sample.Tests],[Sample.Tests.Bar, Sample.Tests]]");

            // 取得した type を使用して CreateInstance でインスタンスを作成する
            var instance = Activator.CreateInstance(type);

            Assert.NotNull(instance);
        }
    }
}

また、 type を取得するには以下のような方法もあります。

var type = asm.GetType("Sample.Foo`2").MakeGenericType(typeof(Bar), typeof(Baz));

この方法は Generics で指定する型のパラメータがテストプロジェクトからアクセスできる場合に有効です。
Intellisense や IDE のリファクタリング機能が作用するので使用できるのであればこちらのほうがおすすめです。

Foo が Nested Type の場合

Foo が別の Generics Type の Nested Type の場合もあるかもしれません。

namespace Sample
{
    class Qux<TQuux>
    {
        private class Foo<TBar,TBaz>
        {
            public TBar Bar { get; set; }
            public TBaz Baz { get; set; }
            public TQuux Quux { get; set; }
        }
    }
}

この場合に type を取得する場合は以下のようにします。

// Nested Type の場合 Declaring Type の後に「+」を付けてそのあとに名前を指定します
// Generics の具体的な型の指定は Declaring Type も Nested Type もまとめて配列で指定します
var type = asm.GetType("Sample.Qux`1+Foo`2[[Sample.Tests.Quux, Sample.Tests],[Sample.Tests.Bar, Sample.Tests],[Sample.Tests.Baz, Sample.Tests]]");

または

var type = asm.GetType("Sample.Qux`1+Foo`2").MakeGenericType(typeof(Quux), typeof(Bar), typeof(Baz));

このように private な Generics 型でもインスタンス化できるので Unit Test で今までテストができていなかったところもテストできるようになります。

Sample Source

今回のサンプルを GitHub に上げました。
generics-type-instance-creating-sample

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む