20191101のC#に関する記事は11件です。

バッチファイルにC#コードを埋め込み実行する

バッチファイル内でpowershell経由でC#コードを実行する記事は既にいくつか存在しますが、1ファイル内でバッチファイル部とC#コード部が綺麗に分かれているものが見つからなかったので、より可読性とメンテナンス性向上を目指しつつ作成してみました。

コード

RunCSCode.bat
@echo off
setlocal
set BAT_PATH=%~f0
powershell -NoProfile -Command "& {$cscode=[regex]::Split([IO.File]::ReadAllText($env:BAT_PATH,[Text.Encoding]::UTF8),':EndBatch')[2]; Add-Type -TypeDefinition $cscode -Language CSharpVersion3; [CSBatch.Program]::Main($Args);}" %*
endlocal
exit /b

:EndBatch

//CSCode
using System;
using System.Runtime.InteropServices;

namespace CSBatch
{
    public class Program
    {
        [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
        private static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);

        public static int Main(string[] args)
        {
            string msg = "";
            for( int i=0; i<args.Length; i++)
            {
                msg += String.Format("args[{0}]={1}\r\n", i, args[i]);
            }
            if( msg=="" )
            {
                msg = "no args.";
            }

            int MB_OK = 0;
            MessageBox(IntPtr.Zero, msg, "CSTestCode", MB_OK);
            return args.Length;
        }
    }
}

実行結果

>RunCSCode.bat あああ いいい ううう
3

実行結果

動作原理

1.通常のバッチファイルとして起動
2.環境変数にバッチファイル自身のパスを格納(直接powershellコマンドに埋め込んでもいいかも)
3.(powershellコマンド) バッチファイル自身をFile.ReadAllTextでutf-8 stringデータとして読み込む
4.(powershellコマンド) Regex.Splitを使用して上記stringデータを:EndBatchラベルで分割し、C#コード部を分離
5.(powershellコマンド) 分離したC#コード部をAdd-Typeで型追加し、C#のメソッドを実行
6. powershellでのC#メソッド実行終了後、exit /b でバッチファイル終了。
(これにより、以降のC#コード部の影響を受けなくなる)

注意)バッチファイル読み込み時にutf-8エンコーディングを指定していますが、BOM無しutf-8でバッチファイルを保存しないと、実行時にゴミが付きます。

MessageBox.Show じゃなくて WinAPI の MessageBox 使っているのは、WinAPIも呼び出せますよっていうのを見せたかっただけです。まあC#なので出来て当たり前ですが…
なんか色々面白い事(危ない事ともいう)出来そうですね。

参考記事

バッチファイルにC#コードを書く
バッチファイルからps1ファイルに記述したC#コードを呼び出して満足した後にこの記事を見て、目から鱗が落ちました。

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

C# イベントハンドラの追加の書きかた変遷

いまさらだけども。

書き方#1

以前はこんな感じで書いていた(気がする)。

イベントハンドラの追加処理
class クラス
{
    メソッド
    {
        
        button1.Click += new EventHandler(button1_Click);
        
    }
}
イベントハンドラのメソッド記載
void button1_Click(object sender, EventArgs e)
{
    
}

書き方#2

new EventHandlerは省略できて、下記のように書ける。

イベントハンドラの追加処理
class クラス
{
    メソッド
    {
        
        button1.Click += button1_Click;
        
    }
}
イベントハンドラのメソッド記載
void button1_Click(object sender, EventArgs e)
{
    
}

書き方#3

ラムダ式で匿名メソッドとして書ける。かなり短くなった。
「処理内容」は、1行で書けないならメソッド化したほうが見やすい気がする。

イベントハンドラの追加処理
class クラス
{
    メソッド
    {
        
        button1.Click += (sender,e)=>{ 処理内容 };
        
    }
}

使いわけ

EventArgs(もしくはその派生クラス)の引数を使いたいときは書き方#2を使いたくなるところ。
同じ.csファイル内では、書き方#2か書き方#3で統一したい。

参考サイト

https://www.ipentec.com/document/csharp-add-event-handler-by-code
https://tnakamura.hatenablog.com/entry/20090309/1236549534

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

起動させたプロセスの終了後のイベント

コード

FFmpegを使うために書いたものですが、普通に使えると思います。

            using (var process = new Process())
            {
                process.EnableRaisingEvents = true; //Exitedイベントを有効に
                process.Exited += new EventHandler(enc_end); //イベントハンドラ
                process.StartInfo = new ProcessStartInfo
                {
                    FileName = System.Windows.Forms.Application.StartupPath + @"\ffmpeg.exe",
                    Arguments = $" -i " + url + " -vcodec copy -acodec copy " + file_name,
                    CreateNoWindow = false,
                    UseShellExecute = false,
                };
                process.Start();
                process.WaitForExit(); //ここが肝心だった。
            }
        }
        private void enc_end(object sender, EventArgs e) //終了後のイベント
        {
            MessageBox.Show("終了しました。");
        }

重要な点

この部分が大事。

process.WaitForExit(); 

この存在を忘れていたため、動作しませんでした。

参考にしたページ

http://gushwell.ldblog.jp/archives/52039841.html

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

C# csc.rspファイルを編集してcsc.exeのデフォルトのオプションを変更しようと思ったらファイルアクセス権が面倒だった話(Visual Studio使わない人向け)

(結論)

csc.rspは編集せずに、バッチファイルつくるのがおすすめ()

はじめに

Windows10環境です。

csc.exeのあるフォルダ1csc.rspというファイルがあり、
これを編集するとオプションを変えられます。
が、そのままだと上書き保存ができません

csc.rspの中身

デフォルトは下記になっているはず。

csc.rsp
# This file contains command-line options that the C#
# command line compiler (CSC) will process as part
# of every compilation, unless the "/noconfig" option
# is specified. 

# Reference the common Framework libraries
/r:Accessibility.dll
/r:Microsoft.CSharp.dll
/r:System.Configuration.dll
/r:System.Configuration.Install.dll
/r:System.Core.dll
/r:System.Data.dll
/r:System.Data.DataSetExtensions.dll
/r:System.Data.Linq.dll
/r:System.Data.OracleClient.dll
/r:System.Deployment.dll
/r:System.Design.dll
/r:System.DirectoryServices.dll
/r:System.dll
/r:System.Drawing.Design.dll
/r:System.Drawing.dll
/r:System.EnterpriseServices.dll
/r:System.Management.dll
/r:System.Messaging.dll
/r:System.Runtime.Remoting.dll
/r:System.Runtime.Serialization.dll
/r:System.Runtime.Serialization.Formatters.Soap.dll
/r:System.Security.dll
/r:System.ServiceModel.dll
/r:System.ServiceModel.Web.dll
/r:System.ServiceProcess.dll
/r:System.Transactions.dll
/r:System.Web.dll
/r:System.Web.Extensions.Design.dll
/r:System.Web.Extensions.dll
/r:System.Web.Mobile.dll
/r:System.Web.RegularExpressions.dll
/r:System.Web.Services.dll
/r:System.Windows.Forms.Dll
/r:System.Workflow.Activities.dll
/r:System.Workflow.ComponentModel.dll
/r:System.Workflow.Runtime.dll
/r:System.Xml.dll
/r:System.Xml.Linq.dll

csc.rspのアクセス権の変更~編集~保存

'1. 下記でファイルの所有権を変更。

cd \Windows\Microsoft.NET\Framework64\v4.0.30319\
takeown /f csc.rsp

'2. エクスプローラでファイルcsc.rspを右クリックし、プロパティを開く。

'3. 「セキュリティ」タブを開く。

'4. 「編集」を押す。(下図参照)
image.png

'5. Administatorsの「許可」に「変更」権限を追加する(「変更」をチェックすると「書き込み」もチェックされる)。(下図参照)
image.png
(※AdministratorsではなくUsersにしておけば以降管理者権限不要のはずだが、不安なのでAdministratorsにしておく。)

'6. メモ帳(notepad.exe)を右クリックで管理者として実行。(メモ帳じゃなくても可)

'7. デバイスへの変更許可が求められるので、承諾する。

'8. csc.rspをstep 6のエディタで編集して上書き保存する。

めんどくさっ

追加したくなりそうなオプション

/nologo ・・・ cscのバージョン表示とかを抑制
/target:winexe ・・・ 生成したexe実行時にコンソールを表示しない(※デバッグ作業中は不便なので微妙)
/platform:x64 もしくは /platform:x86 ・・・ 64bit/32bit指定
/r:DLLのパス ・・・ バッチファイルにしたほうが良い気がする。

csc.rspを編集するデメリット

  • 環境変わったときに戸惑う。 (PC変えたり、ほかの人との環境が変わってしまう。リリース時に事故るリスクがある。)
  • 戻すとき面倒。
  • 変更元をバックアップしてないと戻せなくなるかも。

記事を書いてみたものの、個別にバッチファイル(.bat)作ったほうがよいのではないか・・。

バッチファイル例

compile.bat
csc /r:ほげほげ.dll ^
   /nologo ^
   /target:winexe ^
   %*
  • 行末の ^ は 改行を無視してつなげるためです。(C言語の行末のエスケープ\みたいなもの。)
  • %*は、コマンドラインオプションがそのまま渡ります。(compile.bat /xxx yyy.csとすると、/xxx yyy.csの部分が入ります。)

参考サイト

csc.rsp
https://www.atmarkit.co.jp/fdotnet/dotnettips/179cscrsp/cscrsp.html

ファイル所有者の変更(takeown /f ファイル名)
https://freesoft.tvbok.com/windows7/general/trustedinstaller.html


  1. where csc で調べられます。この辺(C:\Windows\Microsoft.NET\Framework64\v4.0.30319\)にいるはず(環境による)。 

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

WindowsでNFCタグを読み取る

【目的】

Windows端末で社員証などのNFCタグからタグの固有ID(IDm)を取得して、アプリケーションで利用します。
先に公開した「Visual Studio 2019 によるExcelアドインの作成」「Visual Studio 2019 によるExcelアドインの作成 - VBAからアドイン内のメソッドを呼び出す」と併用することで、ExcelからAddIn経由でNFC情報を扱うことも可能になります。

【使用機器】

機器 備考
パソコン Windows 10 Pro
NFCリーダ Sony PaSoRi RC-S380/S
開発環境 VisualStudio 2019/2017

【各種ライブラリなど】

Windows環境で利用できるNFC開発環境を以下にリストアップします。
比較的容易に実装できて、無償で利用できるPSCS(またはPCSC-sharp)の利用をお勧めします。

Personal Computer/Smart Card (winscard.dll)

Windows7以降ではOSに標準で添付 (無償)
DLLImportすれば.Net開発環境(C#、VB)から利用可能 (← 以下にサンプルコードあり)

【winscard.h header - MSDN】

【Advanced Card Systems Ltd.の仕様書】

PCSC-sharp

上記のPCSC(winscard.dll)を.Net環境から使えるようにするラッパー。
ライセンス形態はこちら
PCSC.Iso7816と組み合わせて使う。

【PCSC - Nuget】
【PCSC.Iso7816 - Nuget】

【PCSC-sharp - Github】

Windows.Devices.SmartCards

UWPアプリ用の標準ライブラリ (無償)

【Windows.Devices.SmartCards Namespace - MSDN】

SDK for NFC Starter Kit

SONYが提供しているSDK。
商用利用は有償。

【SDK for NFC Starter Kit - SONY】

nfcpy

Python用のNFCライブラリ。
以前はPython2にしか対応していなかったが、最近Python3に対応したらしい。
Windowsでは別途ドライバの導入作業が必要。(要管理者権限)
ライセンス形態はEUPL1.1

【nfcpy - PyPi】
【nfcpy - Github】

【PCSCによる処理フロー】

基本的には以下の処理フローを踏襲します。

image.png

1. SCardEstablishContext

リソースマネージャに接続してハンドルを取得します。

2. SCardListReaders

PCに接続されているNFCリーダを取得します。(複数可)
取得できなかった場合は接続されるまでループするか、エラーで処理を中止します。

3. SCardConnect

接続されているNFCリーダを指定して、カード(NFCタグ)に接続します。
カードと接続できなかった場合、接続されるまでループするか、エラーで処理を中止します。
なお、NFCリーダ上に読み取れるカードがなかった場合はエラーとなるため、基本的にはループしてカードと接続できるまで待ちます。

4. SCardTransmit

接続したカードにコマンドを送信し、結果を受信します。
ここでIDmを取得したり、カードに保存されている情報を読取り/書込みをお行います。
コマンドや受信データは取得したい情報によって異なるため、該当の仕様書を確認する必要があります。

5. SCardDisconnect

カードから切断します。
これで一連の処理は完了です。

winscard.dllによるサンプル実装 (C# コンソールアプリ)

プログラム本体

Program.cs
using PCSC;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PCSC_Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            IntPtr hContext = IntPtr.Zero;

            // ##################################################
            // 1. SCardEstablishContext
            // ##################################################
            Console.WriteLine("***** 1. SCardEstablishContext *****");
            uint ret = Api.SCardEstablishContext(Constant.SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                string message;
                switch (ret)
                {
                    case Constant.SCARD_E_NO_SERVICE:
                        message = "サービスが起動されていません。";
                        break;
                    default:
                        message = "サービスに接続できません。code = " + ret;
                        break;
                }
                throw new ApplicationException(message);
            }

            if (hContext == IntPtr.Zero)
            {
                throw new ApplicationException("コンテキストの取得に失敗しました。");
            }
            Console.WriteLine(" サービスに接続しました。");


            // ##################################################
            // 2. SCardListReaders
            // ##################################################
            Console.WriteLine("***** 2. SCardListReaders *****");
            uint pcchReaders = 0;

            // NFCリーダの文字列バッファのサイズを取得
            ret = Api.SCardListReaders(hContext, null, null, ref pcchReaders);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                // 検出失敗
                throw new ApplicationException("NFCリーダを確認できません。");
            }

            // NFCリーダの文字列を取得
            byte[] mszReaders = new byte[pcchReaders * 2]; // 1文字2byte
            ret = Api.SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                // 検出失敗
                throw new ApplicationException("NFCリーダの取得に失敗しました。");
            }


            UnicodeEncoding unicodeEncoding = new UnicodeEncoding();
            string readerNameMultiString = unicodeEncoding.GetString(mszReaders);

            // 認識したNDCリーダの最初の1台を使用
            int nullindex = readerNameMultiString.IndexOf((char)0);
            var readerName = readerNameMultiString.Substring(0, nullindex);
            Console.WriteLine(" NFCリーダを検出しました。 " + readerName);



            // ##################################################
            // 3. SCardConnect
            // ##################################################
            Console.WriteLine("***** 3. SCardConnect *****");
            IntPtr hCard = IntPtr.Zero;
            IntPtr activeProtocol = IntPtr.Zero;
            ret = Api.SCardConnect(hContext, readerName, Constant.SCARD_SHARE_SHARED, Constant.SCARD_PROTOCOL_T1, ref hCard, ref activeProtocol);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードに接続できません。code = " + ret);
            }
            Console.WriteLine(" カードに接続しました。");



            // ##################################################
            // 4. SCardTransmit
            // ##################################################
            Console.WriteLine("***** 4. SCardTransmit *****");
            uint maxRecvDataLen = 256;
            var recvBuffer = new byte[maxRecvDataLen + 2];
            var sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, 0x00 };  // ← IDmを取得するコマンド

            Api.SCARD_IO_REQUEST ioRecv = new Api.SCARD_IO_REQUEST();
            ioRecv.cbPciLength = 255;

            int pcbRecvLength = recvBuffer.Length;
            int cbSendLength = sendBuffer.Length;

            IntPtr handle = Api.LoadLibrary("Winscard.dll");
            IntPtr pci = Api.GetProcAddress(handle, "g_rgSCardT1Pci");
            Api.FreeLibrary(handle);

            ret = Api.SCardTransmit(hCard, pci, sendBuffer, cbSendLength, ioRecv, recvBuffer, ref pcbRecvLength);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                throw new ApplicationException("NFCカードへの送信に失敗しました。code = " + ret);
            }

            // 受信データからIDmを抽出する
            // recvBuffer = IDm + SW1 + SW2 (SW = StatusWord)
            // SW1 = 0x90 (144) SW1 = 0x00 (0) で正常だが、ここでは見ていない
            string cardId = BitConverter.ToString(recvBuffer, 0, pcbRecvLength - 2);
            Console.WriteLine(" カードからデータを取得しました。");
            Console.WriteLine(" 【IDm】:" + cardId);


            // ##################################################
            // 5. SCardDisconnect
            // ##################################################
            Console.WriteLine("***** 5. SCardDisconnect *****");
            ret = Api.SCardDisconnect(hCard, Constant.SCARD_LEAVE_CARD);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                throw new ApplicationException("NFCカードとの切断に失敗しました。code = " + ret);
            }
            Console.WriteLine(" カードを切断しました。");
        }
    }
}

API定義

nfcapi.cs
using System;
using System.Runtime.InteropServices;

namespace PCSC
{
    class Api
    {
        [DllImport("winscard.dll")]
        public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);

        [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
        public static extern uint SCardListReaders(
          IntPtr hContext, byte[] mszGroups, byte[] mszReaders, ref UInt32 pcchReaders);

        [DllImport("winscard.dll")]
        public static extern uint SCardReleaseContext(IntPtr phContext);

        [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
        public static extern uint SCardConnect(IntPtr hContext, string szReader,
             uint dwShareMode, uint dwPreferredProtocols, ref IntPtr phCard,
             ref IntPtr pdwActiveProtocol);

        [DllImport("winscard.dll")]
        public static extern uint SCardDisconnect(IntPtr hCard, int Disposition);

        [StructLayout(LayoutKind.Sequential)]
        internal class SCARD_IO_REQUEST
        {
            internal uint dwProtocol;
            internal int cbPciLength;
            public SCARD_IO_REQUEST()
            {
                dwProtocol = 0;
            }
        }

        [DllImport("winscard.dll")]
        public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
                byte[] RecvBuff, ref int RecvBuffLen);

        [DllImport("winscard.dll")]
        public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct SCARD_READERSTATE
        {
            internal string szReader;
            internal IntPtr pvUserData;
            internal UInt32 dwCurrentState;
            internal UInt32 dwEventState;
            internal UInt32 cbAtr;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
            internal byte[] rgbAtr;
        }

        [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
        public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);

        [DllImport("winscard.dll")]
        public static extern int SCardStatus(IntPtr hCard, string szReader, ref int cch, ref int state, ref int protocol, ref byte[] bAttr, ref int cByte);


        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll")]
        public static extern void FreeLibrary(IntPtr handle);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr handle, string procName);

    }
}

APIの定数定義

pcsc_const.cs
using System;

namespace PCSC
{
    class Constant
    {
        public const uint SCARD_S_SUCCESS = 0;
        public const uint SCARD_E_NO_SERVICE = 0x8010001D;
        public const uint SCARD_E_TIMEOUT = 0x8010000A;

        public const uint SCARD_SCOPE_USER = 0;
        public const uint SCARD_SCOPE_TERMINAL = 1;
        public const uint SCARD_SCOPE_SYSTEM = 2;

        public const int SCARD_STATE_UNAWARE = 0x0000;
        public const int SCARD_STATE_CHANGED = 0x00000002;
        public const int SCARD_STATE_PRESENT = 0x00000020;
        public const UInt32 SCARD_STATE_EMPTY = 0x00000010;
        public const int SCARD_SHARE_SHARED = 0x00000002;
        public const int SCARD_SHARE_EXCLUSIVE = 0x00000001;
        public const int SCARD_SHARE_DIRECT = 0x00000003;

        public const int SCARD_PROTOCOL_T0 = 1;
        public const int SCARD_PROTOCOL_T1 = 2;
        public const int SCARD_PROTOCOL_RAW = 4;

        public const int SCARD_LEAVE_CARD = 0;
        public const int SCARD_RESET_CARD = 1;
        public const int SCARD_UNPOWER_CARD = 2;
        public const int SCARD_EJECT_CARD = 3;

        // SCardStatus status values
        public const int SCARD_UNKNOWN = 0x00000000;
        public const int SCARD_ABSENT = 0x00000001;
        public const int SCARD_PRESENT = 0x00000002;
        public const int SCARD_SWALLOWED = 0x00000003;
        public const int SCARD_POWERED = 0x00000004;
        public const int SCARD_NEGOTIABLE = 0x00000005;
        public const int SCARD_SPECIFICMODE = 0x00000006;
    }
}

実行結果

本プログラムでは非常に基本的なことしか実装していません。
パソコンにNFCリーダを接続し、事前にNFCリーダ上にNFCタグ(カード、タグ、スマホなど)を置いた上で実行すると、コンソール上に以下のように表示されます。

***** 1. SCardEstablishContext *****
 サービスに接続しました。
***** 2. SCardListReaders *****
 NFCリーダを検出しました。 Sony FeliCa Port/PaSoRi 3.0 0
***** 3. SCardConnect *****
 カードに接続しました。
***** 4. SCardTransmit *****
 カードからデータを取得しました。
 【IDm】:04-D6-0E-42-**-**-**
***** 5. SCardDisconnect *****
 カードを切断しました。

4. SCardTransmit で表示している 【IDm】: 以降がNFCタグから取得した固有IDの値になります。
NFCリーダが認識できなかったり、NFCタグが存在しない/繋がらない場合など、異常時には例外を吐きます。

なお、NFCタグがリーダにかざされるまで待つには 3. SCardConnect でループさせるか、 SCardGetStatusChange メソッドを使ってNFCリーダの状態変化を検出するまで待つ必要があります。

また、ソースコード中の var sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, 0x00 }; の部分がNFCタグに送信するコマンドになります。
サンプルではIDmを取得するコマンドになっていますが、それ以外のやり取りを行いたい場合、各サービスの仕様書を確認する必要があります。

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

C#初心者の自分用メモ:最初に出てくるものの解読

Unity C#にでてくる最初の構文を解読しました

以下、サンプルコード。

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

public class *** : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
    }

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

こちらのコードを部分的に解読しました。

(注意)自分用のメモのため分かりやすさは追求していないです!!

using System.Collections; / using System.Collections.Generic; / using UnityEngine; について

「System.Collections」「System.Collections.Generic」「UnityEngine」は『名前空間』という。

名前空間とはクラスを種類ごとに整理する仕組みのこと。

ざっくり言うと、いろいろな機能があるお道具箱で、「System.Collections」「System.Collections.Generic」「UnityEngine」というお道具箱を使う(using)という意味の一文。

public class *** : MonoBehaviour について

public

他のクラスからどこからでも見えるようにする」という意味の予約語。
予約語とは「その言語内ですでに役割があるからオリジナルでは定義できない」という約束があるもの。
これが無いと、Unityエディタで使えなかったり、他のクラスから中身が見えない状態になってしまう。
逆に、隠すことを明示する場合はprivateを使う。

class ***

処理するデータとそれを処理する命令部分(メソッド)をひとつにまとめ(クラス)にするもの。
classに続く***が『クラス名』となり、このクラスを呼び出すことができる。
注意点としてUnityのルールでひとつの.csファイルで、ファイル名と同じクラスをひとつだけ宣言すると定めてあるので、それを守らないとエラーになる。

: MonoBehaviour

この後にあるクラスから派生していることを示す。
つまりクラス***は次の「MonoBehabiour」と言うクラスから派生しているよ、という意味。
MonoBehaviourは、Unityで作られるゲーム中で出てくる物体などの動作のきっかけ(イベント)で動かす処理のかたまり(メソッド)等をつなぐ役割をする。
Unityで扱うオブジェクトは、このMonoBehaviourから自動的に派生するようになっている。

void Start()とVoid Update() について

void Start()

スクリプト起動直後に1度だけ実行したい処理を書く。

void Update()

アニメーションなど「動かし続けたい」ものや、「ずっと処理し続けたい」ことを書く。
ゲームはフレーム単位ごとに更新され、その更新ごとにUpdate関数内の処理を繰り返してくれる。

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

C# よく使うusing 名前空間 / (Windows)Form テンプレ作った (Visual Studio使わない人向け)

自分用メモ

テキトーなサンプルコードを作るときに重宝するはず。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

class XXXX : Form
{
    XXXX()
    {
        //Text = ;
        //ClientSize = new Size(,);
        //Controls.Add(xx);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new XXXX());
    }
}

各名前空間の主な用途

面倒くさいのでコード上にコメントで記載。
正直、用途を忘れたものがある

using System;
using System.Collections;
using System.Collections.Generic;     // ジェネリック: List<xxx>, Dictionary<xxx>, ...
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;                 // Size, Point
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;                      // File
using System.Reflection;
using System.Runtime.InteropServices; // アンマネージコード(Win32API, COMなど)との連携: Marshal
using System.Text;                    // 文字コード処理: Encoding
using System.Text.RegularExpressions; // 正規表現:  Regex, Match
using System.Threading;               // スレッド: Thread
using System.Threading.Tasks;         // タスク:  Task
using System.Windows.Forms;           // フォーム: Form, Button, TextBox, ...

usingディレクティブを使うことのメリット・デメリット

メリット
・短く書ける
 (コードが見やすい。)

デメリット
・どの名前空間のクラスを使っているのかが読み取りづらくなる。
 (意図しないクラスを使ってしまったり、他人に分かりづらいかもしれない。)
コンパイルが多少遅くなるかもしれない(未確認だが、無視できるレベルのはず)

追記:Visual Studio Code (VSCode) スニペット追加

コメント頂いたので、スニペットを作ってみました。(初作成)
usingallとタイプすると出ます(下記のprefixのところで変更できます)。スニペット便利!

スニペットの追加(自作)の仕方:
https://webbibouroku.com/Blog/Article/vscode-snippets

さらに追記:usingだけ入れたい場合に邪魔しないように、ちょっと修正しました。

csharp.json

{
    // Place your snippets for csharp here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    "using directive": {
        "prefix": "using",
        "body": [
            "using "
        ],
        "description": "using directive"
    },
    "CSharp Template": {
        "prefix": "usingall",
        "body": [
            "using System;",
            "using System.Collections;",
            "using System.Collections.Generic;",
            "using System.ComponentModel;",
            "using System.Data;",
            "using System.Diagnostics;",
            "using System.Drawing;",
            "using System.Drawing.Drawing2D;",
            "using System.Drawing.Imaging;",
            "using System.IO;",
            "using System.Reflection;",
            "using System.Runtime.InteropServices;",
            "using System.Text;",
            "using System.Text.RegularExpressions;",
            "using System.Threading;",
            "using System.Threading.Tasks;",
            "using System.Windows.Forms;",
            "",
            "class XXXX : Form",
            "{",
            "    XXXX()",
            "    {",
            "        //Text = ;",
            "        //ClientSize = new Size(,);",
            "        //Controls.Add(xx);",
            "    }",
            "    ",
            "    [STAThread]",
            "    static void Main(string[] args)",
            "    {",
            "        Application.Run(new XXXX());",
            "    }",
            "}",
            ""
        ],
        "description": "Insert the template code"
    }
}

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

[C#]Genericの型を値型に限定したい

はじめに

C#のGenericは非常によく利用されます。
このGenericはwhereによって指定できる型をある程度制限できますが、こと値型のみに限定するということができません。

ここで言う値型とは

  • byte
  • sbyte
  • short
  • ushort
  • int
  • uint
  • long
  • ulong
  • float
  • double
  • decimal

を指します。

ただし、これらの型が共通で継承しているinterfaceをwhereで指定することでそこそこに限定できるようです。

確認環境

Unity 2019.2.0f1
IL2CPP
Android

実装

実装としてはこれだけです。
例はメソッドですがクラスでも一緒です。

void Hoge<T>() where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
}

これらのinterfaceを全て実装している独自のstructなどがあればGenericに指定できてしまいますが、ほぼほぼ無いと思うのでこれで大丈夫でしょう。
実際に最初に記述した値型以外の型を指定するとコンパイルエラーになります。

参考

https://stackoverflow.com/questions/805264/how-to-define-generic-type-limit-to-primitive-types

注意

全ての型のリファレンスを確認したわけではないのでもしかしたらこれらの型以外にも一致するものがあるかもしれません。

おまけ

この制約を利用して指定した型で合計と平均を集計するクラスを実装してみました。

IL2CPPでの実装

計算を行う必要がありますが、Tのままでは計算ができないため、計算時は全てdecimalに変換しています。

AggregateIL2CPP.cs
namespace MyEngine
{
    using System;

    /// <summary>
    /// データ集計管理クラス
    /// </summary>
    public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Properties

        /// <summary> 合計値 </summary>
        public T Sum { get; private set; }

        /// <summary> 平均値 </summary>
        public T Average { get; private set; }

        /// <summary> 集計データのサンプル数 </summary>
        public int SampleNum { get; private set; }

        #endregion


        #region Constructor

        public Aggregate()
        {
            Clear();
        }

        #endregion


        #region Methods

        private T ConvertValue(object data)
        {
            return (T)Convert.ChangeType(data, typeof(T));
        }

        #endregion


        #region API

        /// <summary>
        /// 集計データを初期化する
        /// </summary>
        public void Clear()
        {
            SampleNum = 0;
            Sum = ConvertValue(0);
            Average = ConvertValue(0);
        }

        /// <summary>
        /// 集計データを追加
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            SampleNum++;
            Sum = ConvertValue(Sum.ToDecimal(null) + data.ToDecimal(null));
            Average = ConvertValue(Sum.ToDecimal(null) / SampleNum);
        }

        #endregion
    }
}

Monoでの実装

Monoの場合はConvertをdynamicに置き換えることができます。

AggregateMono.cs
namespace MyEngine
{
    using System;

    /// <summary>
    /// データ集計管理クラス
    /// </summary>
    public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Properties

        /// <summary> 合計値 </summary>
        public T Sum { get; private set; }

        /// <summary> 平均値 </summary>
        public T Average { get; private set; }

        /// <summary> 集計データのサンプル数 </summary>
        public int SampleNum { get; private set; }

        #endregion


        #region Constructor

        public Aggregate()
        {
            Clear();
        }

        #endregion


        #region API

        /// <summary>
        /// 集計データを初期化する
        /// </summary>
        public void Clear()
        {
            SampleNum = 0;
            Sum = (dynamic)0;
            Average = (dynamic)0;
        }

        /// <summary>
        /// 集計データを追加
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            SampleNum++;
            Sum += (dynamic)data;
            Average = (dynamic)Sum / SampleNum;
        }

        #endregion

    }
}

使用方法

適当なオブジェクトにアタッチしたら実行できます。
Aggregateのintを他の型に変えるとその型で集計するようになります。
試しにfloatにすると少数まで集計します。

PCの場合
 左クリック : 一回のみ集計
 右クリック : 毎フレーム集計

Android実機の場合
 1本指でタッチ : 一回集計
 2本指でタッチ : 毎フレーム集計
 3本指でタッチ : 集計クリア

AggregateTest.cs
using UnityEngine;
using MyEngine;

using Random = System.Random;

public class AggregateTest : MonoBehaviour
{
    Aggregate<int> _aggregate = new Aggregate<int>();

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

    // Update is called once per frame
    void Update()
    {
        bool isUpdate = false;

#if UNITY_EDITOR
        if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(1))
        {
            isUpdate = true;
        }
#else

        if (Input.touchCount == 1 && Input.touches[0].phase == TouchPhase.Began)
        {
            isUpdate = true;
        }
        else if (Input.touchCount == 2)
        {
            isUpdate = true;
        }
        else if (Input.touchCount == 3)
        {
            _aggregate.Clear();
        }
#endif

        if (isUpdate)
        {
            Random rand = new Random();
            _aggregate.Add((int)(rand.NextDouble() * 100));
        }
    }

    private void OnGUI()
    {
        GUILayout.Label($"合計={_aggregate.Sum:N}\n平均={_aggregate.Average:N}\nサンプル数={_aggregate.SampleNum:N}");
    }
}

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

【C#】Genericの型を値型に限定したい

はじめに

C#のGenericは非常によく利用されます。
このGenericはwhereによって指定できる型をある程度制限できますが、こと値型のみに限定するということができません。

ここで言う値型とは

  • byte
  • sbyte
  • short
  • ushort
  • int
  • uint
  • long
  • ulong
  • float
  • double
  • decimal

を指します。

ただし、これらの型が共通で継承しているinterfaceをwhereで指定することでそこそこに限定できるようです。

確認環境

Unity 2019.2.0f1
IL2CPP
Android

実装

実装としてはこれだけです。
例はメソッドですがクラスでも一緒です。

void Hoge<T>() where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
}

これらのinterfaceを全て実装している独自のstructなどがあればGenericに指定できてしまいますが、ほぼほぼ無いと思うのでこれで大丈夫でしょう。
実際に最初に記述した値型以外の型を指定するとコンパイルエラーになります。

参考

https://stackoverflow.com/questions/805264/how-to-define-generic-type-limit-to-primitive-types

注意

全ての型のリファレンスを確認したわけではないのでもしかしたらこれらの型以外にも一致するものがあるかもしれません。

おまけ

この制約を利用して指定した型で合計と平均を集計するクラスを実装してみました。

IL2CPPでの実装

計算を行う必要がありますが、Tのままでは計算ができないため、計算時は全てdecimalに変換しています。

AggregateIL2CPP.cs
namespace MyEngine
{
    using System;

    /// <summary>
    /// データ集計管理クラス
    /// </summary>
    public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Properties

        /// <summary> 合計値 </summary>
        public T Sum { get; private set; }

        /// <summary> 平均値 </summary>
        public T Average { get; private set; }

        /// <summary> 集計データのサンプル数 </summary>
        public int SampleNum { get; private set; }

        #endregion


        #region Constructor

        public Aggregate()
        {
            Clear();
        }

        #endregion


        #region Methods

        private T ConvertValue(object data)
        {
            return (T)Convert.ChangeType(data, typeof(T));
        }

        #endregion


        #region API

        /// <summary>
        /// 集計データを初期化する
        /// </summary>
        public void Clear()
        {
            SampleNum = 0;
            Sum = ConvertValue(0);
            Average = ConvertValue(0);
        }

        /// <summary>
        /// 集計データを追加
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            SampleNum++;
            Sum = ConvertValue(Sum.ToDecimal(null) + data.ToDecimal(null));
            Average = ConvertValue(Sum.ToDecimal(null) / SampleNum);
        }

        #endregion
    }
}

Monoでの実装

Monoの場合はConvertをdynamicに置き換えることができます。

AggregateMono.cs
namespace MyEngine
{
    using System;

    /// <summary>
    /// データ集計管理クラス
    /// </summary>
    public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Properties

        /// <summary> 合計値 </summary>
        public T Sum { get; private set; }

        /// <summary> 平均値 </summary>
        public T Average { get; private set; }

        /// <summary> 集計データのサンプル数 </summary>
        public int SampleNum { get; private set; }

        #endregion


        #region Constructor

        public Aggregate()
        {
            Clear();
        }

        #endregion


        #region API

        /// <summary>
        /// 集計データを初期化する
        /// </summary>
        public void Clear()
        {
            SampleNum = 0;
            Sum = (dynamic)0;
            Average = (dynamic)0;
        }

        /// <summary>
        /// 集計データを追加
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            SampleNum++;
            Sum += (dynamic)data;
            Average = (dynamic)Sum / SampleNum;
        }

        #endregion

    }
}

使用方法

適当なオブジェクトにアタッチしたら実行できます。
Aggregateのintを他の型に変えるとその型で集計するようになります。
試しにfloatにすると少数まで集計します。

PCの場合
 左クリック : 一回のみ集計
 右クリック : 毎フレーム集計

Android実機の場合
 1本指でタッチ : 一回集計
 2本指でタッチ : 毎フレーム集計
 3本指でタッチ : 集計クリア

AggregateTest.cs
using UnityEngine;
using MyEngine;

using Random = System.Random;

public class AggregateTest : MonoBehaviour
{
    Aggregate<int> _aggregate = new Aggregate<int>();

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

    // Update is called once per frame
    void Update()
    {
        bool isUpdate = false;

#if UNITY_EDITOR
        if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(1))
        {
            isUpdate = true;
        }
#else

        if (Input.touchCount == 1 && Input.touches[0].phase == TouchPhase.Began)
        {
            isUpdate = true;
        }
        else if (Input.touchCount == 2)
        {
            isUpdate = true;
        }
        else if (Input.touchCount == 3)
        {
            _aggregate.Clear();
        }
#endif

        if (isUpdate)
        {
            Random rand = new Random();
            _aggregate.Add((int)(rand.NextDouble() * 100));
        }
    }

    private void OnGUI()
    {
        GUILayout.Label($"合計={_aggregate.Sum:N}\n平均={_aggregate.Average:N}\nサンプル数={_aggregate.SampleNum:N}");
    }
}

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

【Unity】uGUIのドロップダウンメニューをスクリプトから作る

myGIf.gif

Main.cs
static Dropdown GenDropdown(RectTransform parent)
    {
        Dropdown dropdown = new GameObject("dropdown").AddComponent<Dropdown>();
        RectTransform dropdownRC = dropdown.GetComponent<RectTransform>();
        dropdownRC.SetParent(parent);
        Util.SetAnchorPointZero(dropdownRC);

        dropdown.captionText = Util.AddTextRight(dropdownRC); // 選択中のテキストが選択される     
        {
            // dropdown.targetGraphicを設定してない場合、dropdown.captionTextの領域を開けばドロップダウンメニューが開く
            dropdown.targetGraphic = dropdown.gameObject.AddComponent<Image>(); // ドロップダウンメニューを開くための画像領域
            dropdown.targetGraphic.color = Color.green;
        }        

        RectTransform template = new GameObject("template").AddComponent<RectTransform>(); // 一覧表示される際のひな型(テンプレート)
        template.gameObject.SetActive(false);
        template.SetParent(dropdownRC);
        Util.SetAnchorPointZero(template);
        Toggle tog = Util.GenToggle(template, dropdownRC.sizeDelta.x);
        RectTransform togRC = tog.GetComponent<RectTransform>();
        togRC.localPosition = new Vector2(0, -dropdownRC.sizeDelta.y);
        Text text = Util.AddTextRight(togRC); // 一覧の表示されるテキスト

        dropdown.template = template;
        dropdown.itemText = text;

        return dropdown;
    }

    void Test()
    {
        Dropdown dropdown = GenDropdown(GameObject.Find("Canvas").GetComponent<RectTransform>());
        dropdown.options.Add(new Dropdown.OptionData { text = "AAA" });
        dropdown.options.Add(new Dropdown.OptionData { text = "BBB" });
        dropdown.options.Add(new Dropdown.OptionData { text = "CCC" });
        dropdown.onValueChanged.AddListener((data) => MyOnValueChanged(data));
    }

    static public void MyOnValueChanged(int listNum)
    {
        Debug.Log(listNum); // BBBを選択した場合、1が返される。dropdown.optionsのリストのインデックスが返される。
    }

    void Start()
    {
        Test();
    }
Util.cs
static public RectTransform SetAnchorPointZero(RectTransform rect)
    {
        // アンカーポイントを左上に合わせ、ローカル座標をリセットする。
        rect.anchorMin = Vector2.up;
        rect.anchorMax = Vector2.up;
        rect.pivot = Vector2.up;
        rect.localPosition = Vector3.zero;
        rect.rotation = Quaternion.identity;
        rect.localScale = Vector3.one;
        return rect;
    }

    static public Toggle GenToggle(RectTransform parent, float checkBoxSize = 50)
    {
        Toggle tog = new GameObject("togle").AddComponent<Toggle>();
        tog.isOn = true;
        Image background = new GameObject("background").AddComponent<Image>();
        Image checkmark = new GameObject("checkmark").AddComponent<Image>();

        RectTransform togRC = tog.GetComponent<RectTransform>();
        checkmark.rectTransform.SetParent(background.rectTransform);
        background.rectTransform.SetParent(togRC);

        togRC.sizeDelta = Vector2.one * checkBoxSize;
        background.rectTransform.sizeDelta = Vector2.one * checkBoxSize;
        checkmark.rectTransform.sizeDelta = Vector2.one * checkBoxSize * 0.8f;
        checkmark.color = Color.red;

        tog.graphic = checkmark;
        tog.targetGraphic = background;

        togRC.SetParent(parent);
        SetAnchorPointZero(togRC);

        return tog;
    }

    // あるRectTransformの右側に文字を付け加える
    static public Text AddTextRight(RectTransform rc, float offsetX_scale = 1.2f)
    {        
        Text text = new GameObject("text").AddComponent<Text>();
        text.font = Resources.Load<Font>("Font/mplus-1c-black");
        if (text.font == null)
            throw new Exception("フォントが見つかりません!");        
        text.fontSize = (int)(rc.sizeDelta.y * 0.7f); // 0.7倍までなら文字が描画される。
        text.horizontalOverflow = HorizontalWrapMode.Overflow;
        text.rectTransform.sizeDelta = new Vector2(100, rc.sizeDelta.y);
        text.rectTransform.SetParent(rc);
        SetAnchorPointZero(text.rectTransform).localPosition = new Vector2(rc.sizeDelta.x * offsetX_scale, 0);
        return text;
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SerializeField]属性をプリプロセッサ(#if〜#endif)で括ったらどうなるのか

特に理由のない意地悪がUnity2019.2.2を襲う――!!

using UnityEngine;

public class IfDefIjiwaru : MonoBehaviour
{
#if PLATFORM_STANDALONE
    [SerializeField]
    private string Ijiwaru;
#endif
}

実験

まずは対象のプラットフォームで適当に値を入れてみます。
スクリーンショット 2019-11-01 0.16.21.png
そしておもむろに対象プラットフォーム切り替え。
スクリーンショット 2019-11-01 0.17.11.png
フィールドが消えました。
スクリーンショット 2019-11-01 0.16.48.png
元のプラットフォームに戻すと、入力したはずの値が消えています。
スクリーンショット 2019-11-01 0.21.44.png
ここで諦めてはいけません。
UnityのSceneファイルはYAMLなので、Sublime Textなどのテキストエディタで開けます。
見れば値そのものは残っている様子。
スクリーンショット 2019-11-01 0.23.00.png
ということで、シーンを一度閉じて読み込み直すとこのように値が復帰しました。
スクリーンショット 2019-11-01 0.16.21.png

まとめ

そうなんだ そういうことも あるんだね

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