20200526のC#に関する記事は13件です。

【C#】 ファイルの作成・書き込みのやり方。

自己紹介

こんにちは。tetraです。
最近C#の学習を始めました。C#歴2ヶ月目の新卒エンジニアです。
仕事中にファイルの扱い方が分からなくて時間をかなり使ったのでチートシートとして作成します。

ファイル作成

例えば、a.txtというファイルを作りたい場合以下のように書くことができます。

MakeFile_a.cs
using System;
using System.IO;
class Test
{
    public static void Main()
    {
        using (FileStream fs = File.Create("./a.txt")) ;
    }
}

また、ファイルを他から参照することも可能です。

MakeFile_a2.cs
public static void Main()
{
    string path = "./a.txt"
    using (FileStream fs = File.Create(path)) ;
}

ファイル書き込み

ファイルの書き込みは、上書き保存タイプと追記タイプがあります。

上書き保存

overwrite.cs
using System;
using System.IO;
using System.Text;

class Test
{
    static void Main()
    {
        string arg = "hello";
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        using (StreamWriter writer = new StreamWriter("./a.txt", false, enc))
        {
            writer.WriteLine(arg);
        }
    }
}

追記タイプ

変わるところは
using (StreamWriter writer = new StreamWriter("./a.txt", true, enc))
です。

add.cs
using System;
using System.IO;
using System.Text;

class Sample
{
    static void Main()
    {
        string arg = "world";
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        using (StreamWriter writer = new StreamWriter("./a.txt", true, enc))
        {
            writer.WriteLine(arg);
        }
    }
}

いかがでしょうか?

いかがでしょうか。経験が浅いため至らぬところがまだまだあります。
もし間違い等に気がつきましたら、コメント又はtetraまでお知らせください。

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

C# ファイルの作成・読み込み・書き込みのやり方。

自己紹介

こんにちは。tetraです。
最近C#の学習を始めました。C#歴2ヶ月目の新卒エンジニアです。
仕事中にファイルの扱い方が分からなくて時間をかなり使ったのでチートシートとして作成します。

ファイル作成

例えば、a.txtというファイルを作りたい場合以下のように書くことができます。

MakeFile_a.cs
using System;
using System.IO;
class Test
{
    public static void Main()
    {
        using (FileStream fs = File.Create("./a.txt")) ;
    }
}

また、ファイルを他から参照することも可能です。

MakeFile_a2.cs
public static void Main()
{
    string path = "./a.txt"
    using (FileStream fs = File.Create(path)) ;
}

ファイル読み込み

後日更新

ファイル書き込み

後日更新

いかがでしょうか?

いかがでしょうか。経験が浅いため至らぬところがまだまだあります。
もし間違い等に気がつきましたら、コメント又はtetraまでお知らせください。

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

C# ファイルの作成・書き込みのやり方。

自己紹介

こんにちは。tetraです。
最近C#の学習を始めました。C#歴2ヶ月目の新卒エンジニアです。
仕事中にファイルの扱い方が分からなくて時間をかなり使ったのでチートシートとして作成します。

ファイル作成

例えば、a.txtというファイルを作りたい場合以下のように書くことができます。

MakeFile_a.cs
using System;
using System.IO;
class Test
{
    public static void Main()
    {
        using (FileStream fs = File.Create("./a.txt")) ;
    }
}

また、ファイルを他から参照することも可能です。

MakeFile_a2.cs
public static void Main()
{
    string path = "./a.txt"
    using (FileStream fs = File.Create(path)) ;
}

ファイル書き込み

ファイルの書き込みは、上書き保存タイプと追記タイプがあります。

上書き保存

overwrite.cs
using System;
using System.IO;
using System.Text;

class Test
{
    static void Main()
    {
        string arg = "hello";
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        using (StreamWriter writer = new StreamWriter("./a.txt", false, enc))
        {
            writer.WriteLine(arg);
        }
    }
}

追記タイプ

変わるところは
using (StreamWriter writer = new StreamWriter("./a.txt", true, enc))
です。

add.cs
using System;
using System.IO;
using System.Text;

class Sample
{
    static void Main()
    {
        string arg = "world";
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        using (StreamWriter writer = new StreamWriter("./a.txt", true, enc))
        {
            writer.WriteLine(arg);
        }
    }
}

いかがでしょうか?

いかがでしょうか。経験が浅いため至らぬところがまだまだあります。
もし間違い等に気がつきましたら、コメント又はtetraまでお知らせください。

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

C# ストップウォッチクラス

クラス

using System;

using System.Diagnostics;

namespace myStpWtch
{
public class CStopWatch
{

public Stopwatch myStopWatch = new Stopwatch(); //時間経過をはかるためのクラス
public Boolean sw = false; //スイッチOFF状態

    public void Start()
    {
        myStopWatch.Start(); //計測開始
        sw = true;           //スイッチON状態
    }

    public void Stop()
    {
        myStopWatch.Stop();   //計測停止
        sw = false;           //スイッチOFF状態
    }

    public void Reset()
    {
        myStopWatch.Reset();
    }
}

}

BODY

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Diagnostics;
using System.Windows.Forms.DataVisualization.Charting;

using myStpWtch;

namespace myCamera
{
public partial class Form1 : Form
{
CStopWatch stpwatch = new CStopWatch();

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        //===========================================
        // StopWatch
        //===========================================


        //===========================================
        // CameraTab
        //===========================================

        //===========================================
        // MouseTab
        //===========================================


    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        if (stpwatch.sw == false)  // OFF状態なら
        {
            stpwatch.Start();                    //時間計測開始
            timer1.Start();                      //時間表示 
            btnReset.Enabled = false;           //リセットボタン不許可
            btnStart.Text = "Stop";             //ストップボタンに変更
        }
        else
        {
            stpwatch.Stop();                    //時間計測開始
            timer1.Stop();                      //時間固定 
            btnReset.Enabled = true;           //リセットボタン不許可
            btnStart.Text = "Start";             //ストップボタンに変更
        }

    }

    private void btnReset_Click(object sender, EventArgs e)
    {
        stpwatch.Reset();                       //初期化
        txtStpWtch.Text = stpwatch.myStopWatch.Elapsed.ToString();
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        //label1にスタートから現在までの時間を表示させる
        txtStpWtch.Text = stpwatch.myStopWatch.Elapsed.ToString();
    }
}

}

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

Pidginを使ったPEGパーザ構築の手習い

前書き

軽量・軽快・拡張性バッチという謳い文句の benjamin-hodgson/Pidginというパーザコンビネータを通してPEGパーザの作成を学習した記録(継続中)
SQL:2003のBNFを題材として選んだ。

準備

毎度の如く、NUnitのプロジェクトを作成して振る舞いを確認。
Pidginの導入は、Nugetの手順に従って参照に追加した。

& dotnet add <<>MyProject>  package Pidgin --version 2.3.0

リテラルのパーザ

最初のマイルストーンとして、

select 2 * 3 as y from hoge

を解析できるようにする。
そのためにselect句のリテラルの解析を最初の目標とした。

Booleanリテラル

SQLは三値論理なので、TRUE / FALSE / UNKNOWNの3つの値が存在する。

using NUnit.Framework;

using Pidgin;

public class ParseSelectTest {
    [Test]
    public void _Bool値のパーズ() {
        var bool_p = Parser.OneOf(Parser.Try(Parser.CIString("TRUE")), Parser.Try(Parser.CIString("FALSE")), Parser.Try(Parser.CIString("UNKNOWN")));

        var result1_1 = bool_p.Parse("TRUE");
        Assert.That(result1_1.Success, Is.True, "[1.1]パーズは成功しなければならない");
        Assert.That(result1_1.Value, Is.EqualTo("TRUE"), "[1.1]取り出されたリテラル");

        var result1_2 = bool_p.Parse("true");
        Assert.That(result1_2.Success, Is.True, "[1.2]パーズは成功しなければならない");
        Assert.That(result1_2.Value, Is.EqualTo("true"), "[1.2]取り出されたリテラル");

        var result2 = bool_p.Parse("false");
        Assert.That(result2.Success, Is.True, "[2]パーズは成功しなければならない");
        Assert.That(result2.Value, Is.EqualTo("false"), "[2]取り出されたリテラル");

        var result3 = bool_p.Parse("unknown");
        Assert.That(result3.Success, Is.True, "[3]パーズは成功しなければならない");
        Assert.That(result3.Value, Is.EqualTo("unknown"), "[3]取り出されたリテラル");
    }
}

整数リテラル

手戻りを書いていないためか、消費できたところまででOKとしてしまうらしい。

public class ParseSelectTest {
    // (snip)
    [Test]
    public void _数字リテラルのパーズ_符号なし整数の場合() {
        var unum_p = Parser.Digit.AtLeastOnceString();

        var result1 = unum_p.Parse("8 ");
        Assert.That(result1.Success, Is.True, "[1]パーズは成功しなければならない");
        Assert.That(result1.Value, Is.EqualTo("8"), "[1]取り出された数字リテラル");

        var result2 = unum_p.Parse("9876543210987");
        Assert.That(result2.Success, Is.True, "[2]パーズは成功しなければならない");
        Assert.That(result2.Value, Is.EqualTo("9876543210987"), "[2]取り出された数字リテラル");

        var result3 = unum_p.Parse("9876543XYZ");
        Assert.That(result3.Success, Is.True);
        Assert.That(result3.Value, Is.EqualTo("9876543"), "[3]取り出された数字リテラル");
    }
}

小数リテラル

符号なし整数との共存に苦労した。

public class ParseSelectTest {
    // (snip)
    [Test]
    public void _数字リテラルのパーズ_小数の場合() {
        var unum_p = Parser.Digit.AtLeastOnceString();
        var frac_num_p = Parser.Char('.').Then(unum_p, (left, right) => left + right);

        var result4 = frac_num_p.Parse(".654");
        Assert.That(result4.Success, Is.True, "[4]パーズは成功しなければならない");
        Assert.That(result4.Value, Is.EqualTo(".654"), "[4]取り出された数字リテラル");

        var decimal_p = unum_p
            .Then(frac_num_p.Optional(), (left, right) => right.HasValue ? left + right.Value : left)
        ;

        var result5 = decimal_p.Parse("1234.567");
        Assert.That(result5.Success, Is.True, "[5]パーズは成功しなければならない");
        Assert.That(result5.Value, Is.EqualTo("1234.567"), "[5]取り出された数字リテラル");

        var result6 = decimal_p.Parse("567");
        Assert.That(result6.Success, Is.True, "[6]パーズは成功しなければならない");
        Assert.That(result6.Value, Is.EqualTo("567"), "[6]取り出された数字リテラル");

        var decimal_p2 = decimal_p.Then(Parser<char>.End, (l, r) => l);

        var result7 = decimal_p2.Parse("5678UV");
        Assert.That(result7.Success, Is.Not.True, "[7]パーズは失敗しなければならない");
        Assert.That(result7.Error, Is.Not.Null, "[7]エラーあり");
        Assert.That(result7.Error.ErrorPos.Col, Is.EqualTo(5), "数字ではないところで失敗");
    }
}

科学表記の小数リテラル

小数の焼き直しでなんとかなった。

public class ParseSelectTest {
    // (snip)
    [Test]
    public void _数字リテラルのパーズ_小数の場合() {
        var unum_p = Parser.Digit.AtLeastOnceString();
        var exp_part_p = Parser.CIChar('E').Then(unum_p, (l, r) => l + r);

        var result8 = exp_part_p.Parse("E31");
        Assert.That(result8.Success, Is.True, "[8]パーズは成功しなければならない");
        Assert.That(result8.Value, Is.EqualTo("E31"), "[8]取り出された数字リテラル");

        var result8_2 = exp_part_p.Parse("e13");
        Assert.That(result8_2.Success, Is.True, "[8_2]パーズは成功しなければならない");
        Assert.That(result8_2.Value, Is.EqualTo("e13"), "[8_2]取り出された数字リテラル");

        var exp_num_p = unum_p.Then(exp_part_p.Optional(), (l, r) => r.HasValue ? l + r.Value : l);

        var result9 = exp_num_p.Parse("1234E31");
        Assert.That(result9.Success, Is.True, "[9]パーズは成功しなければならない");
        Assert.That(result9.Value, Is.EqualTo("1234E31"), "[9]取り出された数字リテラル");

        var result10 = exp_num_p.Parse("234");
        Assert.That(result10.Success, Is.True, "[10]パーズは成功しなければならない");
        Assert.That(result10.Value, Is.EqualTo("234"), "[10]取り出された数字リテラル");
    }
}

任意の数字リテラル

ここまでの数字パーザを組み合わせただけ。

public class ParseSelectTest {
    // (snip)
    [Test]
    public void _数字のパーズ_Parserのチョイス() {
        var uint_p = Parser.Digit.AtLeastOnceString();

        var frac_num_p = Parser.Char('.').Then(uint_p, (l, r) => l + r);
        var exact_num_p = uint_p.Then(frac_num_p, (l, r) => l + r);
        var exp_part_p = Parser.CIChar('E').Then(uint_p, (l, r) => l + r);
        var exp_num_p = uint_p.Then(exp_part_p.Optional(), (l, r) => r.HasValue ? l + r.Value : l);

        var unum_p = Parser.OneOf(Parser.Try(exact_num_p), Parser.Try(exp_num_p), uint_p);

        var result1_1 = uint_p.Parse("1234567890123");
        Assert.That(result1_1.Success, Is.True, "[1.1]パーズは成功しなければならない");
        Assert.That(result1_1.Value, Is.EqualTo("1234567890123"), "[1.1]取り出された数字リテラル");

        var result1_2 = unum_p.Parse("1234567890123");
        Assert.That(result1_2.Success, Is.True, "[1.2]パーズは成功しなければならない");
        Assert.That(result1_2.Value, Is.EqualTo("1234567890123"), "[1.2]取り出された数字リテラル");

        var result2 = unum_p.Parse("1234.567");
        Assert.That(result2.Success, Is.True, "[2]パーズは成功しなければならない");
        Assert.That(result2.Value, Is.EqualTo("1234.567"), "[2]取り出された数字リテラル");

        var result3 = unum_p.Parse("1234E31");
        Assert.That(result3.Success, Is.True, "[3]パーズは成功しなければならない");
        Assert.That(result3.Value, Is.EqualTo("1234E31"), "[3]取り出された数字リテラル");

        var sign_p = Parser.CIOneOf('+', '-');

        var snum_p = sign_p.Optional().Then(unum_p, (l, r) => l.HasValue ? l.Value  + r: r);

        var result4 = snum_p.Parse("1234567890123");
        Assert.That(result4.Success, Is.True, "[4]パーズは成功しなければならない");
        Assert.That(result4.Value, Is.EqualTo("1234567890123"), "[4]取り出された数字リテラル");

        var result5 = snum_p.Parse("-98765432");
        Assert.That(result5.Success, Is.True, "[5]パーズは成功しなければならない");
        Assert.That(result5.Value, Is.EqualTo("-98765432"), "[5]取り出された数字リテラル");

        var result6 = snum_p.Parse("+2224444");
        Assert.That(result6.Success, Is.True, "[6]パーズは成功しなければならない");
        Assert.That(result6.Value, Is.EqualTo("+2224444"), "[6]取り出された数字リテラル");
    }
}

今日はここまで

余談

Pidginでググると、真っ先にインスタントメッセンジャーの方が引っかかるのでとてもググラビリティが低い。
あとQiitaのPidginタグがインスタントメッセンジャーの方のために作られた感があったので、混乱を避けるため泣く泣くタグから除外した。

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

[Roslyn] オブジェクトインスタンス化コード生成に必要な要素の調査(おまけ)

前書き

オブジェクトインスタンス化コード生成に必要な要素の調査で、オブジェクトインスタンス化に必要な最低限の要素を取得したが、せっかくなので取得結果を用いて、実際にインスタンス化のコード生成をユニットテストを書いて確認したので投下しておく。

確認内容

生成するコードとして、

  • Nullable + コンストラクタをもつ構造体
  • プロパティでの初期化が必要な構造体
  • IEnumerable + 公開フィールドでの初期化が必要な構造体

の3パターンについて、これらの型を戻りとするメソッドを持つインターフェースでもって確認した。

意味解析の結果を取得する際、ASTを構築する必要があるため、そのあたりの諸々ののコードを静的クラスSyntaxGeneratorHelperとして用意した(Roslynによるインターフェースの実装クラスの構築で作ったコードを叩き台にして切り出しただけ)。

    public static class SyntaxGeneratorHelper {
        public static UsingDirectiveSyntax ToUsingDirective(string inUsing) {
            return 
                SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(inUsing).WithLeadingTrivia(SyntaxFactory.Space))
                .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed)
            ;
        }
        public static CSharpCompilation CreateCompilation(SyntaxTree inDaoAST, SyntaxTree inEntityAST) {
            var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();

            var opts = new CSharpCompilationOptions(
                outputKind: OutputKind.DynamicallyLinkedLibrary
            );

            return 
                CSharpCompilation.Create("autoGen", 
                    syntaxTrees: new[] { 
                        inDaoAST, inEntityAST                  
                    },
                    references: new[] {
                        AssemblyMetadata.CreateFromFile(typeof(object).Assembly.Location).GetReference(),
                        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")),
                        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")),
                    },
                    options: opts
                );
        }    
    }
}

コード生成についてのユニットテスト

以下ソースコード生成を行うソースコード

using NUnit.Framework;

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

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using GenSyntaxTestHelpers;
using SemSymbols;

namespace GenTypeInitializationTests
{
    public class GenTypeInitializationTest {
        [SetUp]
        public void Setup()
        {
        }

        private static readonly string IntfSource1 = @"
        namespace SemModels {
            public interface IColorDao1 {
                ColorData? FindById(int id);
            }
        }            
        ";
        private static readonly string EntitySource1 = @"
        namespace SemModels {
            public readonly struct ColorData {
                public int Id { get; }
                public string Name { get; }
                public int Red { get; }
                public int Green { get; }
                public int Blue { get; }

                public ColorData(int id, string name, int red = default, int green = default, int blue = default) => 
                    (Id, Name, Red, Green, Blue) = (id, name, red, green, blue);
            }    
        }    
        ";

        private static readonly string IntfSource2 = @"
        namespace SemModels {
            public interface IColorDao2 {
                ColorDataMut FindById(int id);
            }
        }            
        ";
        private static readonly string EntitySource2 = @"
        namespace SemModels {
            public struct ColorDataMut {
                public int Id { get; set; }
                public int Code { get => this.Id; }
                public string Name { get; set; }
                public int Red { get; set; }
                public int Green { get; set; }
                public int Blue { get; set; }
            }    
        }    
        ";

        private static readonly string IntfSource3 = @"
        using System.Collections.Generic;

        namespace SemModels {
            public interface IColorDao3 {
                IEnumerable<ColorDataMut2> FindAll();
            }
        }            
        ";

        private static readonly string EntitySource3 = @"
        namespace SemModels {
            public struct ColorDataMut2 {
                public int id;
                public string name;
                public int red;
                public int green;
                public int blue;
            }    
        }    
        ";

        [Test]
        public void _メソッドの実装_Nullableな戻り値_コンストラクタが適用される場合() {
            var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource1);
            var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource1);

            var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree);

            var implTree = SyntaxFactory.ParseSyntaxTree(@"
            namespace SemModelsImpl {
                public class ColorDaoImpl: IColorDao1 {

                }
            }   
            ");

            var classTree = 
                implTree.GetCompilationUnitRoot()
                .DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .First()
            ;

            var method = intfTree.GetCompilationUnitRoot()
                .DescendantNodes()
                .OfType<MethodDeclarationSyntax>()
                .First()
            ;

            var model = compiler.GetSemanticModel(intfTree);
            var entityInfo = model.GetTypeInfo(method.ReturnType);  
            SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx);

            var parameters = ctx.Constructors[0].Symbols;
            var arg = method.ParameterList.Parameters[0].Identifier.Text;

            var implMethod = 
                method
                .WithSemicolonToken(default)
                .WithLeadingTrivia(SyntaxFactory.Space)
                .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword).AsTokens())
                .WithBody(
                    SyntaxFactory.Block(
                        SyntaxFactory.ReturnStatement(
                                SyntaxFactory.ObjectCreationExpression(
                                    SyntaxFactory.IdentifierName(ctx.NamedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).WithLeadingTrivia(SyntaxFactory.Space),
                                    SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList<ArgumentSyntax>(new[] {
                                        SyntaxFactory.Argument(
                                            SyntaxFactory.NameColon(parameters[0].Name), SyntaxFactory.Token(default), 
                                            SyntaxFactory.IdentifierName(arg)
                                        ),
                                        SyntaxFactory.Argument(
                                            SyntaxFactory.NameColon(parameters[1].Name), SyntaxFactory.Token(default), 
                                            SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("cyan"))
                                        ),
                                        SyntaxFactory.Argument(
                                            SyntaxFactory.NameColon(parameters[2].Name), SyntaxFactory.Token(default), 
                                            SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0))
                                        ),
                                        SyntaxFactory.Argument(
                                            SyntaxFactory.NameColon(parameters[3].Name), SyntaxFactory.Token(default), 
                                            SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255))
                                        ),
                                        SyntaxFactory.Argument(
                                            SyntaxFactory.NameColon(parameters[4].Name), SyntaxFactory.Token(default), 
                                            SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255))
                                        ),
                                    })),
                                    null

                                )
                                .WithLeadingTrivia(SyntaxFactory.Space)
                        )
                    )
                )
            ;

            classTree = classTree.AddMembers(new[] { implMethod });

            var ns = (NamespaceDeclarationSyntax)implTree.GetCompilationUnitRoot().Members[0];
            var usings = new[] {
                "SemModels"
            };

            var newUnit = 
                SyntaxFactory.CompilationUnit().AddMembers(ns.WithLeadingTrivia(null).WithMembers(classTree.AsMemberDecls()))
                .WithUsings(usings.Select(SyntaxGeneratorHelper.ToUsingDirective).ToSyntaxList())
            ;

            var emitResult = this.EmitGenUnit("ConstructorEntityDao", newUnit, asm => {
                Assert.That(asm.GetTypes().Length, Is.EqualTo(1), "生成されたクラス数");

                var typeNames = asm.GetTypes().Select(t => t.FullName).ToArray();

                Assert.That(typeNames, Does.Contain("SemModelsImpl.ColorDaoImpl"), "生成された型名"); 

                var instance = (SemModels.IColorDao1)asm.CreateInstance("SemModelsImpl.ColorDaoImpl");

                var data = instance.FindById(4);

                Assert.That(data, Is.InstanceOf<System.Nullable<SemModels.ColorData>>(), "目的の値が生成されていること");
                Assert.That(data.HasValue, Is.True, "内包する型のインスタンスが生成されていること"); 
                Assert.That(data.Value.Id, Is.EqualTo(4), "渡したIdと一致していること");
                Assert.That(data.Value.Name, Is.EqualTo("cyan"), "色名");
                Assert.That(data.Value.Red, Is.EqualTo(0), "赤成分");
                Assert.That(data.Value.Green, Is.EqualTo(255), "緑成分");
                Assert.That(data.Value.Blue, Is.EqualTo(255), "青成分");
            });

            foreach (var d in emitResult.Diagnostics) {
                TestContext.Progress.WriteLine(d);
            }
            Assert.That(emitResult.Success, Is.True, "コンパイル結果");
        }

        [Test]
        public void _メソッドの実装_Nullableな戻り値_コプロパティによる初期化が適用される場合() {
            var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource2);
            var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource2);

            var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree);

            var implTree = SyntaxFactory.ParseSyntaxTree(@"
            namespace SemModelsImpl {
                public class ColorDaoImpl2: IColorDao2 {

                }
            }   
            ");

            var classTree = 
                implTree.GetCompilationUnitRoot()
                .DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .First()
            ;

            var method = intfTree.GetCompilationUnitRoot()
                .DescendantNodes()
                .OfType<MethodDeclarationSyntax>()
                .First()
            ;

            var model = compiler.GetSemanticModel(intfTree);
            var entityInfo = model.GetTypeInfo(method.ReturnType);  
            SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx);

            var props = ctx.PropertyTypeVars.Symbols;
            var arg = method.ParameterList.Parameters[0].Identifier.Text;

            var implMethod =
                method
                .WithSemicolonToken(default)
                .WithLeadingTrivia(SyntaxFactory.Space)
                .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword).AsTokens())
                .WithBody(
                    SyntaxFactory.Block(
                            SyntaxFactory.ReturnStatement(
                                SyntaxFactory.ObjectCreationExpression(
                                    SyntaxFactory.IdentifierName(ctx.NamedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).WithLeadingTrivia(SyntaxFactory.Space),
                                    SyntaxFactory.ArgumentList(),
                                    SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression,
                                        SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(props[0].Name),
                                                SyntaxFactory.IdentifierName(arg)
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(props[1].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("magenta"))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(props[2].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(props[3].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(props[4].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255))
                                            )
                                        })
                                    )
                                )
                                .WithLeadingTrivia(SyntaxFactory.Space)
                            )
                    )
                )
            ;


            classTree = classTree.AddMembers(new[] { implMethod });

            var ns = (NamespaceDeclarationSyntax)implTree.GetCompilationUnitRoot().Members[0];
            var usings = new[] {
                "SemModels"
            };

            var newUnit = 
                SyntaxFactory.CompilationUnit().AddMembers(ns.WithLeadingTrivia(null).WithMembers(classTree.AsMemberDecls()))
                .WithUsings(usings.Select(SyntaxGeneratorHelper.ToUsingDirective).ToSyntaxList())
            ;

            var emitResult = this.EmitGenUnit("PropertyEntityDao", newUnit, asm => {
                Assert.That(asm.GetTypes().Length, Is.EqualTo(1), "生成されたクラス数");

                var typeNames = asm.GetTypes().Select(t => t.FullName).ToArray();

                Assert.That(typeNames, Does.Contain("SemModelsImpl.ColorDaoImpl2"), "生成された型名"); 

                var instance = (SemModels.IColorDao2)asm.CreateInstance("SemModelsImpl.ColorDaoImpl2");

                var data = instance.FindById(5);

                Assert.That(data, Is.InstanceOf<SemModels.ColorDataMut>(), "目的の値が生成されていること");
                Assert.That(data.Id, Is.EqualTo(5), "渡したIdと一致していること");
                Assert.That(data.Name, Is.EqualTo("magenta"), "色名");
                Assert.That(data.Red, Is.EqualTo(255), "赤成分");
                Assert.That(data.Green, Is.EqualTo(0), "緑成分");
                Assert.That(data.Blue, Is.EqualTo(255), "青成分");            
            });

            foreach (var d in emitResult.Diagnostics) {
                TestContext.Progress.WriteLine(d);
            }
            Assert.That(emitResult.Success, Is.True, "コンパイル結果");
        }


        [Test]
        public void _メソッドの実装_Nullableな戻り値_公開フィールドによる初期化が適用される場合() {
            var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource3);
            var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource3);

            var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree);

            var implTree = SyntaxFactory.ParseSyntaxTree(@"
            namespace SemModelsImpl {
                public class ColorDaoImpl3: IColorDao3 {

                }
            }   
            ");

            var classTree = 
                implTree.GetCompilationUnitRoot()
                .DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .First()
            ;

            var method = intfTree.GetCompilationUnitRoot()
                .DescendantNodes()
                .OfType<MethodDeclarationSyntax>()
                .First()
            ;

            var model = compiler.GetSemanticModel(intfTree);
            var entityInfo = model.GetTypeInfo(method.ReturnType);  
            SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx);

            var fields = ctx.FieldTypeVars.Symbols;

            var implMethod =
                method
                .WithSemicolonToken(default)
                .WithLeadingTrivia(SyntaxFactory.Space)
                .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword).AsTokens())
                .WithBody(
                    SyntaxFactory.Block(
                            SyntaxFactory.YieldStatement(
                                SyntaxKind.YieldReturnStatement,
                                SyntaxFactory.Token(SyntaxKind.YieldKeyword),
                                SyntaxFactory.Token(SyntaxKind.ReturnKeyword).WithLeadingTrivia(SyntaxFactory.Space),                                
                                SyntaxFactory.ObjectCreationExpression(
                                    SyntaxFactory.IdentifierName(ctx.NamedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).WithLeadingTrivia(SyntaxFactory.Space),
                                    SyntaxFactory.ArgumentList(),
                                    SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression,
                                        SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(fields[0].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(5))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(fields[1].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("yellow"))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(fields[2].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(fields[3].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255))
                                            ),
                                            SyntaxFactory.AssignmentExpression(
                                                SyntaxKind.SimpleAssignmentExpression, 
                                                SyntaxFactory.IdentifierName(fields[4].Name),
                                                SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0))
                                            )
                                        })
                                    )
                                )
                                .WithLeadingTrivia(SyntaxFactory.Space),
                                SyntaxFactory.Token(SyntaxKind.SemicolonToken)
                            )
                    )
                )
            ;

            classTree = classTree.AddMembers(new[] { implMethod });

            var ns = (NamespaceDeclarationSyntax)implTree.GetCompilationUnitRoot().Members[0];
            var usings = new[] {
                "System.Collections.Generic",
                "SemModels"
            };

            var newUnit = 
                SyntaxFactory.CompilationUnit().AddMembers(ns.WithLeadingTrivia(null).WithMembers(classTree.AsMemberDecls()))
                .WithUsings(usings.Select(SyntaxGeneratorHelper.ToUsingDirective).ToSyntaxList())
            ;

            var emitResult = this.EmitGenUnit("FieldEntityDao", newUnit, asm => {
                Assert.That(asm.GetTypes().Length, Is.EqualTo(2), "生成されたクラス数(自動生成されたIteratorを含むため)");

                var typeNames = asm.GetTypes().Select(t => t.FullName).ToArray();

                Assert.That(typeNames, Does.Contain("SemModelsImpl.ColorDaoImpl3"), "生成された型名"); 

                var instance = (SemModels.IColorDao3)asm.CreateInstance("SemModelsImpl.ColorDaoImpl3");

                var seq = instance.FindAll();

                Assert.That(seq, Is.InstanceOf<IEnumerable<SemModels.ColorDataMut2>>(), "目的の値が生成されていること");
                Assert.That(seq.Any(), Is.True, "内包する型のインスタンスが生成されていること");
                Assert.That(seq.Count(), Is.EqualTo(1), "要素数");

                var data = seq.First();

                Assert.That(data.id, Is.EqualTo(5), "渡したIdと一致していること");
                Assert.That(data.name, Is.EqualTo("yellow"), "色名");
                Assert.That(data.red, Is.EqualTo(255), "赤成分");
                Assert.That(data.green, Is.EqualTo(255), "緑成分");
                Assert.That(data.blue, Is.EqualTo(0), "青成分");            
            });

            foreach (var d in emitResult.Diagnostics) {
                TestContext.Progress.WriteLine(d);
            }
            Assert.That(emitResult.Success, Is.True, "コンパイル結果");
        }

        private EmitResult EmitGenUnit(string inAsmName, CompilationUnitSyntax inGenTree, Action<Assembly> inCallback) {
            using(var stream = new MemoryStream()) {
                var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();

                var opts = new CSharpCompilationOptions(
                    outputKind: OutputKind.DynamicallyLinkedLibrary
                );

                var newCompilation = CSharpCompilation.Create(inAsmName, 
                    syntaxTrees: new[] { SyntaxFactory.SyntaxTree(inGenTree) },
                    references: new[] {
                        AssemblyMetadata.CreateFromFile(typeof(object).Assembly.Location).GetReference(),
                        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")),
                        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")),
                        AssemblyMetadata.CreateFromFile(this.GetType().Assembly.Location).GetReference(),
                    },
                    options: opts
                );

                var emitResult = newCompilation.Emit(stream);

                if (emitResult.Success) {
                    Assert.That(inCallback, Is.Not.Null);

                    stream.Position = 0;
                    var buf = new byte[stream.Length];
                    stream.Read(buf, 0, buf.Length);

                    var asm = Assembly.Load(buf);

                    inCallback(asm);
                }

                return emitResult;
            }
        }
    }
}

namespace SemModels {
    public readonly struct ColorData {
        public int Id { get; }
        public string Name { get; }
        public int Red { get; }
        public int Green { get; }
        public int Blue { get; }

        public ColorData(int id, string name, int red = default, int green = default, int blue = default) => 
            (Id, Name, Red, Green, Blue) = (id, name, red, green, blue);
    }    

    public interface IColorDao1 {
        ColorData? FindById(int id);
    }

    public struct ColorDataMut {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Red { get; set; }
        public int Green { get; set; }
        public int Blue { get; set; }
    }    

    public interface IColorDao2 {
        ColorDataMut FindById(int id);
    }

    public struct ColorDataMut2 {
        public int id;
        public string name;
        public int red;
        public int green;
        public int blue;
    }    

    public interface IColorDao3 {
        IEnumerable<ColorDataMut2> FindAll();
    }
}

追記

テストコードにコメントアウトしたゴミを拾い集めコード生成の手引きとしてまとめる

Roslyn@SyntaxFactory`はアホほどメソッドが生えててどれを使ったいいか結構迷う。

その際の手引きとして、

var body = SyntaxFactory.ParseCompilationUnit("public SemModels.ColorData x(int id) { return new SemModels.ColorData() { Red = 128 }; }");
foreach (var n in body.DescendantNodesAndTokensAndSelf()) {
   TestContext.Progress.WriteLine($"{n.Kind().ToString().PadRight(30)}{n.ToFullString()}");
}

のように文字列から構文木を実体化させ、コンソールに出力してみるのが近道かも。
(NUnitの場合、TestContext.Progress.WriteLineメソッドで、出力してくれる)

また、組み立てた構文木についても、ToFullString()メソッドを呼ぶことで文字列に戻すことができる(インデントはめちゃくちゃな可能性あり)

TestContext.Progress.WriteLine(newUnit.ToFullString());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】LINQでJOINは、メソッド構文よりクエリ構文

LINQで複数テーブルをJoinする場合は、メソッド構文ではなくクエリ構文で記述した方が可読性は高い

複数エンティティをメソッド構文で内部結合すると可読性が低い。
複数エンティティ結合する必要がある場合は、クエリ構文で記述した方が可読性は上がる。

メソッド構文とクエリ構文で比較

4テーブル結合した場合のメソッド構文クエリ構文の例

メソッド構文の例

            var query =
                db.Parent
                .Join(db.ChildA, x => x.id, y => y.id, (p, a) => new { p, a })
                .Join(db.ChildB, x => x.p.id, y => y.id, (grp, b) => new { grp, b })
                .Join(db.ChildC, x => x.grp.p.id, y => y.id, (grp, c) => new { grp, c })
                .Join(db.ChildD, x => x.grp.grp.p.id, y => y.id, (grp, d) => new { grp, d })
                .Select(x => new { x.grp.grp.grp.p, x.grp.grp.grp.a, x.grp.grp.b, x.grp.c, x.d });

クエリ構文の例

            var query =
                from p in db.Parent
                join a in db.ChildA on p.id equals a.id
                join b in db.ChildB on p.id equals b.id
                join c in db.ChildC on p.id equals c.id
                join d in db.ChildD on p.id equals d.id
                select new {p,a,b,c,d};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IE/Edgeに保存されているログインデータを出力してみる

はじめに

Chromeに保存されているパスワードを解読してみる
に引き続き、今回はInternet ExplorerとMicrosoft EdgeがどのようにWebサイトのログインデータを保存しているのか調べてみました。

IE/Edgeのログインデータの保存方法

調べてみたところ、ログインデータは「Windows Vaults」と呼ばれる特殊なフォルダに保存されていることがわかりました。
具体的には、C:\Windows\System32\vaultcli.dllに含まれているWindows VaultsのAPIを使用してアクセスする仕組みになっているようです。

ログインデータを読み込んでみる

ログインデータを読み込んで出力するプログラムは以下のようになります。

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace FetchVaultCredential
{
    public static class Program
    {
        public enum VAULT_ELEMENT_TYPE : int
        {
            Undefined = -1,
            Boolean = 0,
            Short = 1,
            UnsignedShort = 2,
            Int = 3,
            UnsignedInt = 4,
            Double = 5,
            Guid = 6,
            String = 7,
            ByteArray = 8,
            TimeStamp = 9,
            ProtectedArray = 10,
            Attribute = 11,
            Sid = 12,
            Last = 13
        }

        public enum VAULT_SCHEMA_ELEMENT_ID : int
        {
            Illegal = 0,
            Resource = 1,
            Identity = 2,
            Authenticator = 3,
            Tag = 4,
            PackageSid = 5,
            AppStart = 100,
            AppEnd = 10000
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct VAULT_ITEM_WIN8
        {
            public Guid SchemaId;
            public IntPtr pszCredentialFriendlyName;
            public IntPtr pResourceElement;
            public IntPtr pIdentityElement;
            public IntPtr pAuthenticatorElement;
            public IntPtr pPackageSid;
            public ulong LastModified;
            public int dwFlags;
            public int dwPropertiesCount;
            public IntPtr pPropertyElements;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct VAULT_ITEM_WIN7
        {
            public Guid SchemaId;
            public IntPtr pszCredentialFriendlyName;
            public IntPtr pResourceElement;
            public IntPtr pIdentityElement;
            public IntPtr pAuthenticatorElement;
            public ulong LastModified;
            public int dwFlags;
            public int dwPropertiesCount;
            public IntPtr pPropertyElements;
        }

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
        public struct VAULT_ITEM_ELEMENT
        {
            [FieldOffset(0)] public VAULT_SCHEMA_ELEMENT_ID SchemaElementId;
            [FieldOffset(8)] public VAULT_ELEMENT_TYPE Type;
        }

        [DllImport("vaultcli.dll")]
        public extern static int VaultOpenVault(ref Guid vaultGuid, int offset, ref IntPtr vaultHandle);

        [DllImport("vaultcli.dll")]
        public extern static int VaultCloseVault(ref IntPtr vaultHandle);

        [DllImport("vaultcli.dll")]
        public extern static int VaultFree(ref IntPtr vaultHandle);

        [DllImport("vaultcli.dll")]
        public extern static int VaultEnumerateVaults(int offset, ref int vaultCount, ref IntPtr vaultGuid);

        [DllImport("vaultcli.dll")]
        public extern static int VaultEnumerateItems(IntPtr vaultHandle, int chunkSize, ref int vaultItemCount, ref IntPtr vaultItem);

        [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")]
        public extern static int VaultGetItem_WIN8(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr pPackageSid, IntPtr zero, int arg6, ref IntPtr passwordVaultPtr);

        [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")]
        public extern static int VaultGetItem_WIN7(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr zero, int arg5, ref IntPtr passwordVaultPtr);

        public static void GetLogins()
        {
            // Windows Vaultsをチェック
            var OSVersion = Environment.OSVersion.Version;
            var OSMajor = OSVersion.Major;
            var OSMinor = OSVersion.Minor;

            Type VAULT_ITEM;

            if (OSMajor >= 6 && OSMinor >= 2)
            {
                // Windows 8以降
                VAULT_ITEM = typeof(VAULT_ITEM_WIN8);
            }
            else
            {
                // Windows 7以前
                VAULT_ITEM = typeof(VAULT_ITEM_WIN7);
            }

            // VAULT_ITEM_ELEMENTからItemValueを取り出す
            object GetVaultElementValue(IntPtr vaultElementPtr)
            {
                object results;
                object partialElement = Marshal.PtrToStructure(vaultElementPtr, typeof(VAULT_ITEM_ELEMENT));
                FieldInfo partialElementInfo = partialElement.GetType().GetField("Type");
                var partialElementType = partialElementInfo.GetValue(partialElement);

                IntPtr elementPtr = (IntPtr)(vaultElementPtr.ToInt64() + 16);
                switch ((int)partialElementType)
                {
                    case 7: // String(パスワード)
                        IntPtr StringPtr = Marshal.ReadIntPtr(elementPtr);
                        results = Marshal.PtrToStringUni(StringPtr);
                        break;
                    case 0: // bool
                        results = Marshal.ReadByte(elementPtr);
                        results = (bool)results;
                        break;
                    case 1: // Short
                        results = Marshal.ReadInt16(elementPtr);
                        break;
                    case 2: // Unsigned Short
                        results = Marshal.ReadInt16(elementPtr);
                        break;
                    case 3: // Int
                        results = Marshal.ReadInt32(elementPtr);
                        break;
                    case 4: // Unsigned Int
                        results = Marshal.ReadInt32(elementPtr);
                        break;
                    case 5: // Double
                        results = Marshal.PtrToStructure(elementPtr, typeof(Double));
                        break;
                    case 6: // GUID
                        results = Marshal.PtrToStructure(elementPtr, typeof(Guid));
                        break;
                    case 12: // セキュリティ識別子
                        IntPtr sidPtr = Marshal.ReadIntPtr(elementPtr);
                        var sidObject = new System.Security.Principal.SecurityIdentifier(sidPtr);
                        results = sidObject.Value;
                        break;
                    default:
                        // VAULT_ELEMENT_TYPEが実装されていないので無視
                        results = null;
                        break;
                }
                return results;
            }

            int vaultCount = 0;
            // VaultのGUIDポインタ
            IntPtr vaultGuidPtr = IntPtr.Zero;
            // 全てのVaultを取得
            var result = VaultEnumerateVaults(0, ref vaultCount, ref vaultGuidPtr);

            if (result != 0)
            {
                throw new Exception("Vaultを取得できません。(0x" + result.ToString() + ")");
            }

            // GUID対応表
            IntPtr guidAddress = vaultGuidPtr;
            Dictionary<Guid, string> vaultSchema = new Dictionary<Guid, string>();
            vaultSchema.Add(new Guid("2F1A6504-0641-44CF-8BB5-3612D865F2E5"), "Windows Secure Note");
            vaultSchema.Add(new Guid("3CCD5499-87A8-4B10-A215-608888DD3B55"), "Windows Web Password Credential");
            vaultSchema.Add(new Guid("154E23D0-C644-4E6F-8CE6-5069272F999F"), "Windows Credential Picker Protector");
            vaultSchema.Add(new Guid("4BF4C442-9B8A-41A0-B380-DD4A704DDB28"), "Web Credentials");
            vaultSchema.Add(new Guid("77BC582B-F0A6-4E15-4E80-61736B6F3B29"), "Windows Credentials");
            vaultSchema.Add(new Guid("E69D7838-91B5-4FC9-89D5-230D4D4CC2BC"), "Windows Domain Certificate Credential");
            vaultSchema.Add(new Guid("3E0E35BE-1B77-43E7-B873-AED901B6275B"), "Windows Domain Password Credential");
            vaultSchema.Add(new Guid("3C886FF3-2669-4AA2-A8FB-3F6759A77548"), "Windows Extended Credential");
            vaultSchema.Add(new Guid("00000000-0000-0000-0000-000000000000"), null);

            for (int i = 0; i < vaultCount; i++)
            {
                // Vaultを開く
                object vaultGuidString = Marshal.PtrToStructure(guidAddress, typeof(Guid));
                Guid vaultGuid = new Guid(vaultGuidString.ToString());
                guidAddress = (IntPtr)(guidAddress.ToInt64() + Marshal.SizeOf(typeof(Guid)));
                IntPtr vaultHandle = IntPtr.Zero;
                string vaultType;
                if (vaultSchema.ContainsKey(vaultGuid))
                {
                    vaultType = vaultSchema[vaultGuid];
                }
                else
                {
                    vaultType = vaultGuid.ToString();
                }
                result = VaultOpenVault(ref vaultGuid, 0, ref vaultHandle);
                if (result != 0)
                {
                    throw new Exception(vaultType + "からVaultを開けませんでした。" + "(0x" + result.ToString() + ")");
                }

                // Vault内のアイテムを列挙
                int vaultItemCount = 0;
                IntPtr vaultItemPtr = IntPtr.Zero;
                result = VaultEnumerateItems(vaultHandle, 512, ref vaultItemCount, ref vaultItemPtr);
                if (result != 0)
                {
                    throw new Exception(vaultType + "からのVaultの列挙に失敗しました。" + "(0x" + result.ToString() + ")");
                }
                var structAddress = vaultItemPtr;
                if (vaultItemCount > 0)
                {
                    for (int j = 1; j <= vaultItemCount; j++)
                    {
                        // Vaultの列挙を開始
                        var currentItem = Marshal.PtrToStructure(structAddress, VAULT_ITEM);
                        structAddress = (IntPtr)(structAddress.ToInt64() + Marshal.SizeOf(VAULT_ITEM));

                        IntPtr passwordVaultItem = IntPtr.Zero;
                        // フィールド情報の検索
                        FieldInfo schemaIdInfo = currentItem.GetType().GetField("SchemaId");
                        Guid schemaId = new Guid(schemaIdInfo.GetValue(currentItem).ToString());
                        FieldInfo pResourceElementInfo = currentItem.GetType().GetField("pResourceElement");
                        IntPtr pResourceElement = (IntPtr)pResourceElementInfo.GetValue(currentItem);
                        FieldInfo pIdentityElementInfo = currentItem.GetType().GetField("pIdentityElement");
                        IntPtr pIdentityElement = (IntPtr)pIdentityElementInfo.GetValue(currentItem);
                        FieldInfo dateTimeInfo = currentItem.GetType().GetField("LastModified");
                        ulong lastModified = (ulong)dateTimeInfo.GetValue(currentItem);

                        IntPtr pPackageSid = IntPtr.Zero;
                        if (OSMajor >= 6 && OSMinor >= 2)
                        {
                            // 新しいバージョンにはパッケージSIDがある
                            FieldInfo pPackageSidInfo = currentItem.GetType().GetField("pPackageSid");
                            pPackageSid = (IntPtr)pPackageSidInfo.GetValue(currentItem);
                            result = VaultGetItem_WIN8(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, pPackageSid, IntPtr.Zero, 0, ref passwordVaultItem);
                        }
                        else
                        {
                            result = VaultGetItem_WIN7(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, IntPtr.Zero, 0, ref passwordVaultItem);
                        }

                        if (result != 0)
                        {
                            throw new Exception("Error occured while retrieving vault item. Error: 0x" + result.ToString());
                        }
                        object passwordItem = Marshal.PtrToStructure(passwordVaultItem, VAULT_ITEM);
                        FieldInfo pAuthenticatorElementInfo = passwordItem.GetType().GetField("pAuthenticatorElement");
                        IntPtr pAuthenticatorElement = (IntPtr)pAuthenticatorElementInfo.GetValue(passwordItem);
                        // 認証情報を取得
                        object cred = GetVaultElementValue(pAuthenticatorElement);
                        object packageSid = null;
                        if (pPackageSid != IntPtr.Zero && pPackageSid != null)
                        {
                            packageSid = GetVaultElementValue(pPackageSid);
                        }
                        if (cred != null) // 取得に成功したデータを表示
                        {
                            Console.WriteLine("Vault Type   : {0}", vaultType);
                            object resource = GetVaultElementValue(pResourceElement);
                            if (resource != null)
                            {
                                Console.WriteLine("Resource     : {0}", resource);
                            }
                            object identity = GetVaultElementValue(pIdentityElement);
                            if (identity != null)
                            {
                                Console.WriteLine("Identity     : {0}", identity);
                            }
                            if (packageSid != null)
                            {
                                Console.WriteLine("PacakgeSid  : {0}", packageSid);
                            }
                            Console.WriteLine("Credential   : {0}", cred);
                            Console.WriteLine("LastModified : {0}", System.DateTime.FromFileTimeUtc((long)lastModified));
                            Console.WriteLine();
                        }
                    }
                }
            }
        }

        public static void Main(string[] args)
        {
            GetLogins();
            Console.ReadKey(true);
        }
    }
}

実行する際は、ビルド設定の「32 ビットを選ぶ」のチェックを外してください。

仕組みとしては、Windows VaultのAPIをDllImportで読み込み、それを使ってVaultの構造体を取得してデータを取り出しています。
取得したデータ自体に暗号化などはされていないようです。

これを実行すると、以下のようにログインデータが出力されます。

Vault Type   : Web Credentials
Resource     : https://login.microsoftonline.com/
Identity     : foobar314159@yahoo.co.jp
Credential   : TekitounaPassword
LastModified : 2020/05/23 15:50:49

Vault Type   : Web Credentials
Resource     : https://accounts.google.com/
Identity     : unknown141421356@gmail.com
Credential   : SecurePass123456
LastModified : 2020/05/26 11:52:57

おわりに

という訳で、IE/Edgeに保存されているログインデータも取得できてしまいました。
実は、Windows Vaultsに関するAPIの仕様はMicrosoft公式からは公開されていません。
だから、Microsoftは特にログインデータに暗号化処理をしなかったのだと思います。
しかし、海外のハッカー達にとってWindows Vaultsの仕様を一から自力で解析することなど朝飯前だったようです。恐ろしいですね。

参考文献

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

【VRChat】 Udon開発する上での注意点【Unity】

はじめに

この記事はVRChatにおけるUdonおよびUdonSharp(U#)を使った際の備忘録です。
これからUdonを使い始める人のために書き連ねておきます。

この記事は2020/5/26現在のVRChatを前提に書いています。

Udonとパフォーマンスチューニング

Udonは実質、UnityのAPIをラップして呼び出しているだけにすぎません。
そのためUnityでプログラミングをするときと同じ様にパフォーマンスにもこだわる必要があります。

プロファイラを見よう

Unityには標準でProfilerという機能が備わっています。
1フレーム単位でどのような処理が実行され、それにどれくらいの時間がかかっているかをチェックすることができます。

OpenProfiler.png

Profiler.png

詳しい使い方はこちらを参考にしてください。

なお、プロファイラで動作を監視すること自体がかなりの負荷となります。
プロファイラを使っている間はfpsがガタ落ちしますが、しょうがないと割り切ってください。
(普通のUnity開発なら回避策があるのですが、VRChatだと仕様上どうしようもできないです)

Raw Hierarchyで調査

プロファイラの表示をRaw Hierarchyに切り替えて時間がかかっている順にソートすると何がボトルネックか調査することができます。
とくにこのモードだとUdon VMの中身の実行順も見ることが出来ます。

Profiler2.png

Udonの仕様上、どのスクリプトであるか名前はわからないのですが、メソッド呼び出しの様子からどのスクリプトかあたりをつけることはできます。

GCアロケートを避けよう

とくに負荷の原因となりやすいものはGC Allocと表示されているものです。
これはUnity上で、プログラムを実行するために必要なメモリを確保する動作を表しています。
GCアロケートと呼ぶ)

そしてこの確保したメモリですが、解放される瞬間にVRChatが一瞬フリーズしてしまいます。
GC(ガベージコレクタ)が実行される、と呼びます)

GCが実行される頻度は少ないほどfpsに与える影響は小さくなります。
逆に高頻度でGCが実行されると、体感できるレベル(ひどいと数十fps)で影響がでてきます。
そのためGCの実行をさける、つまりGC Allocの頻度を下げる工夫が必要となります。

stringは避けよう

C#の仕様上、string(文字列)は定義するだけでかなりのGC Allocを引き起こします。
そのためUpdate()で毎フレーム文字列を生成するなどしていると、パフォーマンスにかなりの悪影響を及ぼします。

極力stringは使わない、使うにしても必要なタイミングで必要なだけ生成する工夫が必要です。

U#は別コンポーネントのメソッド呼び出しがコスト

非常に便利なUdonSharpですが、見えないところでコストがかかります。
それはUdonSharpBehaviourから別のUdonSharpBehaviourなオブジェクトのメソッドを呼びだす時です。

たとえば、次のようなU#スクリプトがあったとして。

Runner
using UdonSharp;
using UnityEngine;

namespace DebugTest
{
    public class Runner : UdonSharpBehaviour
    {
        private Rigidbody _rigidbody;

        void Start()
        {
            _rigidbody = GetComponent<Rigidbody>();
        }

        public Vector3 GetCurrentVelocity()
        {
            return _rigidbody.velocity;
        }
    }
}
Observer
using UdonSharp;
using UnityEngine;

namespace DebugTest
{
    public class Observer : UdonSharpBehaviour
    {
        [SerializeField] private Runner _runner;

        private void Update()
        {
            // 取得するだけで何も使わない
            var velocity = _runner.GetCurrentVelocity();
        }
    }
}

これのObserver.cs側をUdon Assemblyにトランスパイルした結果をみるとこうなっています。

_update:

    PUSH, __0_const_intnl_SystemUInt32

    # {

    # var velocity = _runner.GetCurrentVelocity();
    PUSH, _runner
    PUSH, __0_const_intnl_SystemString
    EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SendCustomEvent__SystemString__SystemVoid"
    PUSH, _runner
    PUSH, __1_const_intnl_SystemString
    PUSH, __0_intnl_SystemObject
    EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__GetProgramVariable__SystemString__SystemObject"
    PUSH, __0_intnl_SystemObject
    PUSH, __0_intnl_UnityEngineVector3
    COPY
    PUSH, __0_intnl_UnityEngineVector3
    PUSH, __0_velocity_Vector3
    COPY
    PUSH, __0_intnl_returnTarget_UInt32 #Function epilogue
    COPY
    JUMP_INDIRECT, __0_intnl_returnTarget_UInt32

注目して欲しいところは、他のUdonSharpBehaviourへのメソッド呼び出しがSendCustomEventGetProgramVariableに変換されているところです。
(メソッドに引数を渡すとSetProgramVariableも追加される)

そしてこのSendCustomEventGetProgramVariableですが、なぜかGC Allocします

GCAlloc.png
(Udon内部実装の問題なのでおそらく回避不可)

ということで、U#を用いた場合、気軽にメソッド呼び出しを実行するとそれだけでGC Allocが発生します。
普通のUnity開発ではノーコストな操作が、U#ではコストがかかる点はかなり罠な気がします。

OnTriggerStay大暴走

UdonBehaviourにはOnTriggerStayが定義されています。
そのためVRC_Pickup + UdonBehaviourなオブジェクト」を一箇所に大量にまとめて配置するとOnTriggerStayが暴走します。

OnTriggerStay.png
(50個ほど重ねて配置した例)

数個程度なら問題ないですが、数十個レベルで一箇所にまとめるとfpsがガタ落ちするレベルで影響がでてきます。
アイテムを一箇所にまとめておいて擬似的な「無限湧き」を作るようなことはやめておきましょう。

Udon Synced Variables役に立たない問題

結論からいうとUdon Synced Variablesはパフォーマンスのために 「使わない」 が正解です。

UdonにはUdon Synced Variablesという機能があります。
こちらは指定したプリミティブな変数をネットワークをまたいで同期する機能です。
U#でいうところの[UdonSynced]

ですがこのUdon Synced Variables、挙動が結構ヤバイです。

  • Owner常時パラメータを相手に送信し続ける
  • 転送量が増えるとパケットロスして不着となる
  • スループットがかなり低い

同期するオブジェクト、変数の数が増えるとDeath Run Detected: dropped N eventsというエラーが大量に出てきます。
これが発生してしまうと、変数同期の成功率が極端に下がってしまいます。

image.png
(大量にパケロスしている様子)

そのため、Udon Synced Variablesで大量のデータを同期することはまったくオススメできません。

たとえば、オブジェクトの位置と姿勢(Vector3 + Quaternion)をUdon Synced Variablesで同期するのは止めたほうがいいでしょう。
私が試した場合ではオブジェクト数が20個を超えたあたりからパケロスが発生しました。
さらにVRChatの通信にかなりの負荷をかけるためか、Playerの挙動までもが不安定になりました。

ちなみに、この仕様ではほぼ使い物にならないのでフィードバック報告済みではあります。

補足: Udon Synced Variablesについての公式フォーラムでの報告

VRChatのフォーラムのこちらの投稿では次のように報告されています。

  • 2つのUdon Synced Variablesな文字列をもつUdon Behaviourをシーンにいくつか配置
  • 8個置いた程度ではパケロスはほぼゼロ
  • 16個置くとパケロスが発生する

とのことなので、Udon Synced Variablesを使う場合はオブジェクト数が少ない場合のみにした方が無難でしょう。

文字列にエンコードして同期する、は高コスト

また、とある場所で「オブジェクトの状態をstringにエンコードしてUdon Synced Variablesで同期する」という手法が提案されていました。

Udon Synced Variablesで配列が同期できないのを回避するために編み出された手法ですが、こちらかなりコストが高いです。

  • 大量のstringを生成することによるGC Alloc
  • 長い文字列を常時伝送するネットワークへの負荷

そのため本当にどうしようもないときの最終手段としとっておいて、常用はしないほうが無難でしょう。
(とはいえどこれしか方法が無いならば使わざるを得ないのがUdonのツライところなのですが…。)

位置同期

オブジェクトの位置を同期する方法ですが、次の2とおり(実質1とおり)があります。

  • A: Udon Synced Variablesで位置姿勢を送る
  • B: Udon BehaviourSynchronize Positionを使う

Aのパターンは前述の問題があるのでオススメできません。
ということで実質的にBの「Synchronize Position」一択になります。

このSynchronize Positionはちゃんと差分同期してくれるため、大量にオブジェクトがあってもネットワークへの負荷は小さいです。

Synced.png

Synchronize Positionの同期ズレ問題

Synchronize Positionは差分同期してくれるためネットワーク負荷は小さいのですが、大量にオブジェクトがある場合、後からワールドに参加した人には正しく位置と姿勢が同期されない場合があります。
こちらはワールドにいるプレイヤー数とオブジェクト数によりますが、「5人以上かつ20個くらいオブジェクトを動かした」あたりから発生してきます。

原因はハッキリとはしていないのですが、どうも次の複数の問題が絡んでいるっぽいです。

前者についてはバグ報告済みですが、後者についてはいまいち挙動がつかめていないため報告していません。

Synchronize Positionの同期ズレ対策

対処療法として、次の対策をいれましょう。
実際にモノレールワールドで実施している対策がこれです。

  • 触れていないPickupオブジェクトはすべてMasterが所有権をもつ

    • オブジェクトを持っている間は持っている人にOwnerを渡す
    • 手を離したらMasterに所有権を返すようにする
    • 若干安定するが、それでもまだ同期ズレは起きる
  • 強制的に位置を同期する仕組みをいれる

    1. Master側で同期対象のオブジェクトをすべて少しだけ位置と姿勢をズラす
    2. 数秒後に元の位置姿勢に戻す

image.png
(同期ズレの発生をゼロにはできないので、同期ズレが起きる前提で対策した方が早い)

かなりツラミがある仕組みですが、現状これくらいしか大量のオブジェクトを安定して同期する方法がありません。

まとめ

Udonつらいし、UdonSharpも結構ツライです。
それなりのUnity開発経験と、Unityでパフォーマンスチューニングをできるスキルが求められますね。

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

Blazor WebAssembly を触ってみる - その①環境を整える、サンプルを動かす

Blazor WebAssembly とは

Web ブラウザー内で .NET コードを実行する WebAssembly ベースの SPA フレームワークです。
Blazor で開発したアプリやその依存関係、.NET ランタイムがブラウザーにダウンロード、実行されるという仕組みです。
サーバーサイドだけではなく、クライアントサイドまで C# で開発できるようになるので、C# が使える方にとっては便利そうですね。
また、C# から JavaScript の呼び出すこともその逆もできますので、これまでの開発したリソースも活かせそうです。

image.png
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/?view=aspnetcore-3.1#blazor-webassembly

つい先日正式にリリースされたのでサンプルを動かすところまで試して見たいと思います。

https://www.publickey1.jp/blog/20/blazor_webassemblycnetwebmicrosoft_build_2020.html
https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/

試してみる

以下のドキュメントを元に試してみます。

最初の Blazor アプリをビルドする
https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/build-your-first-blazor-app?view=aspnetcore-3.1

ここから先は VS Code でデバッグ実行するところまでの手順ですが、手っ取り早く動かしたい方は以下のコマンドだけで十分です。

dotnet new blazorwasm -o BlazorApp1
cd BlazorApp1
dotnet run

用意するもの

手順

1. テンプレートのインストール (.NET Core 3.1.300 よりも低いバージョンのときのみ)

コマンドプロンプトより dotnet --version を実行して 3.1.300 よりも低いバージョンが表示されたときは、以下のコマンドを実行して最新のテンプレートをインストールします。
Blazor WebAssembly の正式サポートは .NET Core SDK 3.1.300 からだからでしょう。

dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-rc1.20223.4

2. VS Code の設定

Settings (Ctrl + ,) より debug.javascript.usePreview を True にします。
image.png

次に C# と JavaScript Debugger (Nightly) の Extension をインストールします。
image.png

Extension はこちらから検索できます。
image.png

3. テンプレートからアプリを作成

dotnet new blazorwasm -o WebApplication1

中身はこんな感じです。ASP.NET Core っぽいですね。
image.png

4. 実行

Run -> Start Debugging よりデバッグ実行します。
初回実行時には VS Code のデバッグ設定のファイルである launch.json を作成するため、以下のような画面が表示されますので、.NET Core を選択します。(その後、もう一度 Start Debugging をクリックします)
.NET Core
image.png

DEBUG CONSOLE に以下のようなメッセージが表示されたら、ブラウザで http://localhost:5000 を開きます。

image.png

以下のように Hello, world! の画面が表示されたら成功です。

image.png

カウントができるようです。
image.png

ディベロッパーツールを開いてみると、たしかに .NET Core のアセンブリがロードされているようです。
image.png

いろいろ気になりますが今回はここまで。

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

Blazor WebAssembly を触ってみる

Blazor WebAssembly とは

Web ブラウザー内で .NET コードを実行する WebAssembly ベースの SPA フレームワークです。
Blazor で開発したアプリやその依存関係、.NET ランタイムがブラウザーにダウンロード、実行されるという仕組みです。
サーバーサイドだけではなく、クライアントサイドまで C# で開発できるようになるので、C# が使える方にとっては便利そうですね。
また、C# から JavaScript の呼び出すこともその逆もできますので、これまでの開発したリソースも活かせそうです。

image.png
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/?view=aspnetcore-3.1#blazor-webassembly

つい先日正式にリリースされたのでサンプルを動かすところまで試して見たいと思います。

https://www.publickey1.jp/blog/20/blazor_webassemblycnetwebmicrosoft_build_2020.html
https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/

試してみる

以下のドキュメントを元に試してみます。

最初の Blazor アプリをビルドする
https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/build-your-first-blazor-app?view=aspnetcore-3.1

ここから先は VS Code でデバッグ実行するところまでの手順ですが、手っ取り早く動かしたい方は以下のコマンドだけで十分です。

dotnet new blazorwasm -o BlazorApp1
cd BlazorApp1
dotnet run

用意するもの

手順

1. テンプレートのインストール (.NET Core 3.1.300 よりも低いバージョンのときのみ)

コマンドプロンプトより dotnet --version を実行して 3.1.300 よりも低いバージョンが表示されたときは、以下のコマンドを実行して最新のテンプレートをインストールします。
Blazor WebAssembly の正式サポートは .NET Core SDK 3.1.300 からだからでしょう。

dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-rc1.20223.4

2. VS Code の設定

Settings (Ctrl + ,) より debug.javascript.usePreview を True にします。
image.png

次に C# と JavaScript Debugger (Nightly) の Extension をインストールします。
image.png

Extension はこちらから検索できます。
image.png

3. テンプレートからアプリを作成

dotnet new blazorwasm -o WebApplication1

中身はこんな感じです。ASP.NET Core っぽいですね。
image.png

4. 実行

Run -> Start Debugging よりデバッグ実行します。
初回実行時には VS Code のデバッグ設定のファイルである launch.json を作成するため、以下のような画面が表示されますので、.NET Core を選択します。(その後、もう一度 Start Debugging をクリックします)
.NET Core
image.png

DEBUG CONSOLE に以下のようなメッセージが表示されたら、ブラウザで http://localhost:5000 を開きます。

image.png

以下のように Hello, world! の画面が表示されたら成功です。

image.png

カウントができるようです。
image.png

ディベロッパーツールを開いてみると、たしかに .NET Core のアセンブリがロードされているようです。
image.png

いろいろ気になりますが今回はここまで。

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

C#でFormにBingマップを表示する

はじめに

Windows Formsに触れたことがある人なら、きっと誰もがマップをFormに埋め込みたいと思うでしょう。
しかしWindows Formsには、残念ながらマップを表示するコントロールはありません。
しかし、CefSharpとBing Maps APIを組み合わせればそのような機能を実現できます。
今回はそのやり方を紹介します。

HTMLファイルを作る

仕組みとしては、Bing Mapsを埋め込んだHTMLファイルをCefSharpで表示する形となります。
本当はGoogle Maps APIを使いたかったのですが、クレジットカード情報を登録する必要があるみたいで面倒だったので、今回はあきらめました。

Bing Maps APIを使うにはMicrosoftのアカウントが必要です。持っていない方は登録しましょう。
持っている方はBing Maps APIのページにアクセスし、「Sign in」ボタンを押してサインインします。
サインインできたら、上のメニューの「My account」から「My keys」をクリックします。
「Create key」という画面が表示されたら、必要事項を記入し「Create」をクリックします。
するとキーの一覧が表示されるので、「Show key」を押せばキーが表示されます。
このキーは後で使うのでコピーしておいてください。

それではHTMLを記述していきます。

<!DOCTYPE html>
<html>
  <head>
    <title>Bing Maps API</title>
    <meta charset="utf-8" />
    <script type='text/javascript'
      src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=[キーを指定]&setLang=ja' 
      async defer></script>

   <script type='text/javascript'>
    var map;
    function GetMap() {
      map = mapStart(35, 140, 10);
    }

    // Mapを初期化
    // 緯度, 経度, 拡大率(1~20)
    function mapStart(lat, lon, num) {
      return new Microsoft.Maps.Map('#myMap', {
        center: new Microsoft.Maps.Location(lat, lon),
        // 表示モード(road:普通, aerial:航空写真)
        mapTypeId: Microsoft.Maps.MapTypeId.aerial,
        zoom: num
      });
    }

    // ピンを立てる(緯度, 経度)
    function mapPushpin(lat, lon) {
      const location = new Microsoft.Maps.Location(lat, lon);
      const pin = new Microsoft.Maps.Pushpin(location, {
        color: "red",     // ピンの色
        draggable: true,  // ドラッグできるか
      });
      map.entities.push(pin);
    }
    </script>
</head>
<body style="margin:0;">
    <div id="myMap" style="position:relative;width:100%;height:100%;"></div>
</body>
</html>

[キーを指定]のところに先ほど取得したキーを指定してください。
これをC:\Maps.htmlとして保存します。

CefSharpの導入

次に、Bingマップを表示するアプリのプロジェクトを作成しておきましょう。
作成したら、このプロジェクトに「CefSharp」というパッケージを導入します。
CefSharpはChromiumのC#向け実装です。

「HTMLファイルを表示したいならWebBrowserコントロールを使えばいいのでは?」
と思うかもしれませんが、WebBrowserコントロールは内部に古いIEのエンジンを使っているので、エラーが頻発してまともに使えません。

今回は、NuGetでCefSharp.WinFormsをインストールします。
スクリーンショット (52).png

インストールが完了したら、プロジェクトのフォルダにある設定ファイル(csprojファイル)をテキストエディタで開きます。
以下のように、<CefSharpAnyCpuSupport>true</CefSharpAnyCpuSupport>をPropertyGroupタグの中に追加して保存します。
スクリーンショット (55).png

次に、App.configファイルを開き、configurationタグの中に

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <probing privatePath="x86"/>
    </assemblyBinding>
</runtime>

を追加して保存します。
スクリーンショット (53).png

これでCefSharpの導入は完了です。

Bingマップを表示するプログラム

それでは、プログラムを作っていきます。
以下のようにPanelやステータスバーなどを置きます。
CefSharpはツールボックスに表示できないので、Panelの内部に合わせて表示するようにします。
スクリーンショット (54).png

以下が今回のプログラムです。

using System;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;

namespace BingMapsSample
{
    public partial class Form1 : Form
    {
        string maphtml = @"C:\Maps.html";  // さっきのHTMLファイル
        ChromiumWebBrowser cefBrowser;
        CefSettings settings;
        public Form1()
        {
            InitializeComponent();
            button1.Enabled = false;
            toolStripStatusLabel1.Text = "読み込み中...";

            settings = new CefSettings();
            // レンダリングを最適化(これをやらないとバグる)
            settings.SetOffScreenRenderingBestPerformanceArgs();
            Cef.Initialize(settings);
            cefBrowser = new ChromiumWebBrowser(maphtml);
            cefBrowser.LoadingStateChanged += CefBrowser_LoadingStateChanged;
            // Panelに合わせて表示
            panel1.Controls.Add(cefBrowser);
            cefBrowser.Dock = DockStyle.Fill;
        }

        private void CefBrowser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
        {
            if(!e.IsLoading)
            {
                // 読み込み完了時
                Invoke((MethodInvoker)delegate {
                    toolStripStatusLabel1.Text = "読み込み完了";
                    button1.Enabled = true;
                });
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // Javascriptの実行
            cefBrowser.ExecuteScriptAsync("map = mapStart(" + textBox1.Text +
                "," + textBox2.Text + "," + "10);");
            cefBrowser.ExecuteScriptAsync("mapPushpin(" + textBox1.Text +
                "," + textBox2.Text + ");");
        }
    }
}

button1_Click内ではExecuteScriptAsyncメソッドを使って、HTMLファイルで定義した関数を呼び出しています。
実行するとこのようにマップが表示されます。
スクリーンショット (56).png
緯度と経度を入力して「移動」を押せば
スクリーンショット (57).png
このようにマークが付きます。

おわりに

以上、C#でFormにBingマップを表示する方法でした。
回りくどい方法でしたが、意外とちゃんと動くので是非試してみてください。

参考文献

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

[Roslyn] オブジェクトインスタンス化コード生成に必要な要素の調査

前書き

Roslynによるインターフェースの実装クラスの構築で、ソースコードをパーズし、ASTを作るところまでは見たので、続きとして戻り値の型のインスタンスを生成する。

インスタンスを生成するためには、少なくとも

  • 目的の型の名称
  • 引数
    • 引数の型名
    • 仮引数名
      • 名前付き引数形式で値を渡す場合
    • デフォルト引数の有無
    • インスタンス科においてパラメータ修飾子(outrefなど)は不要そう
  • 初期化子
    • コンストラクタが明示されず、プロパティやフィールドへの直接代入する場合

が必要。

また、IEnumerable<T>Nullable<T>でエンベロープする場合は、それらの型も必要。

実行時であれば、リフレクションで取得できるが、まだビルド前のためそれも叶わない。
このような状況で、Roslynでは意味解析の結果を取得するAPI(セマンティックAPI)が提供されている。

そこでオブジェクトインスタンス化に必要な要素の収集するため、このセマンティックAPIを使用してを調べた記録を残す。

調査結果

ここでは結果を保持しておく型として

using System.Linq;

using System.Collections.Immutable;
using System.Collections.Generic;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace SemSymbols {
    public enum TypeVerCategory {
        Unknown,
        Constructor,
        Properties,
        Fields,
    }

    public struct TypeVar<TSymbol> 
        where TSymbol: ISymbol
    {
        public TypeVerCategory Category { get; private set; }
        public ImmutableArray<TSymbol> Symbols { get; private set; } 

        public TypeVar(TypeVerCategory inCategory, ImmutableArray<TSymbol> inSymbols) {
            this.Category = inCategory;
            this.Symbols = inSymbols;
        }
    }

    public static class TypeVar {
        public static TypeVar<IParameterSymbol> OfConstructor(ImmutableArray<IParameterSymbol> inSymbols) {
            return new TypeVar<IParameterSymbol>(TypeVerCategory.Constructor, inSymbols);
        }
        public static TypeVar<IPropertySymbol> OfProperties(ImmutableArray<IPropertySymbol> inSymbols) {
            return new TypeVar<IPropertySymbol>(TypeVerCategory.Properties, inSymbols);
        }
        public static TypeVar<IFieldSymbol> OfFields(ImmutableArray<IFieldSymbol> inSymbols) {
            return new TypeVar<IFieldSymbol>(TypeVerCategory.Fields, inSymbols);
        }
    }

    public struct MethodParamTypeVar {
        IParameterSymbol[] Parameters { get; set; }
    }

    public struct EntitiyContext {
        public SpecialType ContainerType { get; set; }
        public INamedTypeSymbol NamedType { get; set; }
        public TypeVar<IParameterSymbol>[] Constructors { get; set; }
        public TypeVar<IPropertySymbol> PropertyTypeVars { get; set; }
        public TypeVar<IFieldSymbol> FieldTypeVars { get; set; }
    }
}

を用意し、セマンティックAPIを駆使して以下のメソッドを組み上げた。

namespace SemSymbols {
    // (snip)

    public static class SemSymbolHelper {
        public static bool TryCResolveReturnTypeContext(Microsoft.CodeAnalysis.TypeInfo inReturnTypeInfo, out EntitiyContext outCtx) {
            outCtx = default;

            var typeSymbol = inReturnTypeInfo.Type as INamedTypeSymbol;
            if (typeSymbol == null) return false;

            var fmtFqcn = new SymbolDisplayFormat(
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
                miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable
            );

            (outCtx.ContainerType, outCtx.NamedType) = ResolveAsContainerType(typeSymbol);

            if (outCtx.NamedType == null) return false;

            var memberSymbols = outCtx.NamedType.GetMembers();

            outCtx.Constructors = 
                memberSymbols
                .OfType<IMethodSymbol>()
                .Where(s => s.MethodKind == MethodKind.Constructor)
                .Where(s => s.Parameters.Length > 0)
                .Select(s => TypeVar.OfConstructor(s.Parameters))
                .ToArray()
            ;

            outCtx.PropertyTypeVars = TypeVar.OfProperties(
                memberSymbols.OfType<IPropertySymbol>().Where(s => ! s.IsReadOnly).ToImmutableArray()
            );

            outCtx.FieldTypeVars = TypeVar.OfFields(
                memberSymbols.OfType<IFieldSymbol>().Where(s => ! s.IsImplicitlyDeclared).ToImmutableArray()
            );

            return true;
        }

        private static System.ValueTuple<SpecialType, INamedTypeSymbol> ResolveAsContainerType(INamedTypeSymbol inSymbol) {
            var fmtFqcn = new SymbolDisplayFormat(
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
                miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable
            );

            return 
                inSymbol.ToDisplayString(fmtFqcn) switch {
                    "System.Nullable" => 
                        (SpecialType.System_Nullable_T, inSymbol.TypeArguments[0] as INamedTypeSymbol), 
                    "System.Collections.Generic.IEnumerable" =>
                        (SpecialType.System_Collections_Generic_IEnumerable_T, inSymbol.TypeArguments[0] as INamedTypeSymbol),
                    _ => 
                        (SpecialType.None, inSymbol),
                }
            ;
        }
    }
}

ユニットテスト

以下のユニットテスト、(最低限の要素が)収集できたことを確認した。

using NUnit.Framework;

using System;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using GenSyntaxTestHelpers;
using SemSymbols;

namespace SemModelTests {
    public class SemModelTest {
        private static readonly string IntfSource1 = @"
        namespace SemModels {
            public interface IColorDao1 {
                ColorData? FindById(int id);
            }
        }            
        ";
        private static readonly string EntitySource1 = @"
        namespace SemModels {
            public readonly struct ColorData {
                public int Id { get; }
                public string Name { get; }
                public int Red { get; }
                public int Green { get; }
                public int Blue { get; }

                public ColorData(int id, string name, int red = default, int green = default, int blue = default) => 
                    (Id, Name, Red, Green, Blue) = (id, name, red, green, blue);
            }    
        }    
        ";

        private static readonly string IntfSource2 = @"
        namespace SemModels {
            public interface IColorDao2 {
                ColorDataMut FindById(int id);
            }
        }            
        ";
        private static readonly string EntitySource2 = @"
        namespace SemModels {
            public struct ColorDataMut {
                public int Id { get; set; }
                public int Code { get => this.Id; }
                public string Name { get; set; }
                public int Red { get; set; }
                public int Green { get; set; }
                public int Blue { get; set; }
            }    
        }    
        ";

        private static readonly string IntfSource3 = @"
        using System.Collections.Generic;

        namespace SemModels {
            public interface IColorDao3 {
                IEnumerable<ColorDataMut2> FindAll();
            }
        }            
        ";

        private static readonly string EntitySource3 = @"
        namespace SemModels {
            public struct ColorDataMut2 {
                public int id;
                public string name;
                public int red;
                public int green;
                public int blue;
            }    
        }    
        ";

        [Test]
        public void _戻り値型のコンテキストを解決_コンストラクタを持つ場合() {
            var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource1);
            var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource1);

            var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree);

            var ns = (NamespaceDeclarationSyntax)intfTree.GetCompilationUnitRoot().Members[0];
            var intf = (InterfaceDeclarationSyntax)ns.Members[0];
            var meth = (MethodDeclarationSyntax)intf.Members[0];

            var model = compiler.GetSemanticModel(intfTree);
            var entityInfo = model.GetTypeInfo(meth.ReturnType);    

            Assert.IsTrue(SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx), "解決できていること");
            Assert.That(ctx.ContainerType, Is.EqualTo(SpecialType.System_Nullable_T), "Nullableによるコンテナ型");
            Assert.That(ctx.NamedType, Is.Not.Null, "型名のシンボルが取得できていること");
            Assert.That(ctx.NamedType.ToDisplayString(), Is.EqualTo("SemModels.ColorData"), "型名が一致すること");

            Assert.That(ctx.Constructors.Length, Is.EqualTo(1), "1つコンストラクタが定義されていること(デフォルトコンストラクタは除外)");
            Assert.That(ctx.Constructors[0].Category, Is.EqualTo(TypeVerCategory.Constructor), "カテゴリがコンストラクタであること");
            Assert.That(ctx.Constructors[0].Symbols.Length, Is.EqualTo(5), "コンストラクタ引数の数が一致していること");

            var arg1 = ctx.Constructors[0].Symbols[0];
            {
                Assert.That(arg1.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[1]がint型であること");
                Assert.That(arg1.Name, Is.EqualTo("id"), "引数名[1]");
                Assert.That(arg1.IsOptional, Is.Not.True, "引数[1はオプショナルではないこと");
            }
            var arg2 = ctx.Constructors[0].Symbols[1];
            {
                Assert.That(arg2.Type.SpecialType, Is.EqualTo(SpecialType.System_String), "引数[2]がstring型であること");
                Assert.That(arg2.Name, Is.EqualTo("name"), "引数名[2]");
                Assert.That(arg2.IsOptional, Is.Not.True, "引数[2]はオプショナルではないこと");
            }

            var arg3 = ctx.Constructors[0].Symbols[2];
            {
                Assert.That(arg3.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[3]がint型であること");
                Assert.That(arg3.Name, Is.EqualTo("red"), "引数名[3]");
                Assert.That(arg3.IsOptional, Is.True, "引数[3]はオプショナル");
            }
            var arg4 = ctx.Constructors[0].Symbols[3];
            {
                Assert.That(arg4.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[4]がint型であること");
                Assert.That(arg4.Name, Is.EqualTo("green"), "引数名[4]");
                Assert.That(arg4.IsOptional, Is.True, "引数[4]はオプショナル");
            }
            var arg5 = ctx.Constructors[0].Symbols[4];
            {
                Assert.That(arg5.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[5]がint型であること");
                Assert.That(arg5.Name, Is.EqualTo("blue"), "引数名[5]");
                Assert.That(arg5.IsOptional, Is.True, "引数[5]はオプショナル");
            }

            Assert.That(ctx.PropertyTypeVars.Category, Is.EqualTo(TypeVerCategory.Properties), "カテゴリがプロパティであること");
            Assert.That(ctx.PropertyTypeVars.Symbols.Length, Is.EqualTo(0), "Readonlyプロパティは含めないこと");

            Assert.That(ctx.FieldTypeVars.Category, Is.EqualTo(TypeVerCategory.Fields), "カテゴリがフィールドであること");
            Assert.That(ctx.FieldTypeVars.Symbols.Length, Is.EqualTo(0), "暗黙宣言されたフィールドは含めないこと");
        }

        [Test]
        public void _戻り値型のコンテキストを解決_セッタープロパティによる初期化が要求される場合() {
            var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource2);
            var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource2);

            var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree);

            var ns = (NamespaceDeclarationSyntax)intfTree.GetCompilationUnitRoot().Members[0];
            var intf = (InterfaceDeclarationSyntax)ns.Members[0];
            var meth = (MethodDeclarationSyntax)intf.Members[0];

            var model = compiler.GetSemanticModel(intfTree);
            var entityInfo = model.GetTypeInfo(meth.ReturnType);    

            Assert.IsTrue(SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx), "解決できていること");
            Assert.That(ctx.ContainerType, Is.EqualTo(SpecialType.None), "エンティティ型");
            Assert.That(ctx.NamedType, Is.Not.Null, "型名のシンボルが取得できていること");
            Assert.That(ctx.NamedType.ToDisplayString(), Is.EqualTo("SemModels.ColorDataMut"), "型名が一致すること");

            Assert.That(ctx.Constructors.Length, Is.EqualTo(0), "1つもコンストラクタが定義されていないこと(デフォルトコンストラクタは除外)");

            Assert.That(ctx.PropertyTypeVars.Category, Is.EqualTo(TypeVerCategory.Properties), "カテゴリがプロパティであること");
            Assert.That(ctx.PropertyTypeVars.Symbols.Length, Is.EqualTo(5), "Writableプロパティを持つこと");

            var prop1 = ctx.PropertyTypeVars.Symbols[0];
            {
                Assert.That(prop1.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[1]がint型であること");
                Assert.That(prop1.Name, Is.EqualTo("Id"), "プロパティ名[1]");
            }
            var prop2 = ctx.PropertyTypeVars.Symbols[1];
            {
                Assert.That(prop2.Type.SpecialType, Is.EqualTo(SpecialType.System_String), "プロパティ[2]がstring型であること");
                Assert.That(prop2.Name, Is.EqualTo("Name"), "プロパティ名[2]");
            }
            var prop3 = ctx.PropertyTypeVars.Symbols[2];
            {
                Assert.That(prop3.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[3]がint型であること");
                Assert.That(prop3.Name, Is.EqualTo("Red"), "プロパティ名[3]");
            }
            var prop4 = ctx.PropertyTypeVars.Symbols[3];
            {
                Assert.That(prop4.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[4]がint型であること");
                Assert.That(prop4.Name, Is.EqualTo("Green"), "プロパティ名[4]");
            }
            var prop5 = ctx.PropertyTypeVars.Symbols[4];
            {
                Assert.That(prop5.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[5]がint型であること");
                Assert.That(prop5.Name, Is.EqualTo("Blue"), "プロパティ名[5]");
            }

            Assert.That(ctx.FieldTypeVars.Category, Is.EqualTo(TypeVerCategory.Fields), "カテゴリがフィールドであること");
            Assert.That(ctx.FieldTypeVars.Symbols.Length, Is.EqualTo(0), "暗黙フィールドは除外されていること");
        }

        [Test]
        public void _戻り値型のコンテキストを解決_公開フィールドを持つ場合() {
            var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource3);
            var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource3);

            var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree);

            var ns = (NamespaceDeclarationSyntax)intfTree.GetCompilationUnitRoot().Members[0];
            var intf = (InterfaceDeclarationSyntax)ns.Members[0];
            var meth = (MethodDeclarationSyntax)intf.Members[0];

            var model = compiler.GetSemanticModel(intfTree);
            var entityInfo = model.GetTypeInfo(meth.ReturnType);  

            Assert.IsTrue(SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx), "解決できていること");

            Assert.That(ctx.NamedType, Is.Not.Null, "型名のシンボルが取得できていること");
            Assert.That(ctx.NamedType.ToDisplayString(), Is.EqualTo("SemModels.ColorDataMut2"), "型名が一致すること");
            Assert.That(ctx.ContainerType, Is.EqualTo(SpecialType.System_Collections_Generic_IEnumerable_T), "コレクションコンテナ型");

            Assert.That(ctx.Constructors.Length, Is.EqualTo(0), "1つもコンストラクタが定義されていないこと(デフォルトコンストラクタは除外)");

            Assert.That(ctx.PropertyTypeVars.Category, Is.EqualTo(TypeVerCategory.Properties), "カテゴリがプロパティであること");
            Assert.That(ctx.PropertyTypeVars.Symbols.Length, Is.EqualTo(0), "Readonlyプロパティは含めないこと");

            Assert.That(ctx.FieldTypeVars.Category, Is.EqualTo(TypeVerCategory.Fields), "カテゴリがフィールドであること");
            Assert.That(ctx.FieldTypeVars.Symbols.Length, Is.EqualTo(5), "暗黙宣言されたフィールドは含めないこと");

            var f1 = ctx.FieldTypeVars.Symbols[0];
            {
                Assert.That(f1.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[1]がint型であること");
                Assert.That(f1.Name, Is.EqualTo("id"), "フィールド名[1]");
            }
            var f2 = ctx.FieldTypeVars.Symbols[1];
            {
                Assert.That(f2.Type.SpecialType, Is.EqualTo(SpecialType.System_String), "フィールド[2]がstring型であること");
                Assert.That(f2.Name, Is.EqualTo("name"), "フィールド名[2]");
            }
            var f3 = ctx.FieldTypeVars.Symbols[2];
            {
                Assert.That(f3.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[3]がint型であること");
                Assert.That(f3.Name, Is.EqualTo("red"), "フィールド名[3]");
            }
            var f4 = ctx.FieldTypeVars.Symbols[3];
            {
                Assert.That(f4.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[4]がint型であること");
                Assert.That(f4.Name, Is.EqualTo("green"), "フィールド名[4]");
            }
            var f5 = ctx.FieldTypeVars.Symbols[4];
            {
                Assert.That(f5.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[5]がint型であること");
                Assert.That(f5.Name, Is.EqualTo("blue"), "フィールド名[5]");
            }
        }  
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む