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

【C#】ListViewを用いて特定のフォルダー内に散らばった画像を表示する

環境

  • OS:Windows10Pro
  • IDE:VisualStadio2019

どんなものを作りたいのか

フォルダ
├フォルダ
 └画像
├フォルダ
 └画像
├フォルダ
 └画像

のように散らばった画像を一覧表示するもの

手順

プロジェクトの作成

下記の画像のものを選択する。
image.png

プロジェクト名等を適当に決めて作成する。
image.png

レイアウトの作成

自分が必要だと思う機能を盛り込んでレイアウトを考える。

image.png

コーディング

各オブジェクトに任意の機能をつけ足していく
細かい機能はgithubに載せているコードを見てほしい。

表示ボタンのソースコード

private void button2_Click(object sender, EventArgs e)
        {
            listView1.Clear();
            string extension = "*." + textBox1.Text;
            string imageDir = this.path; // 画像ディレクトリ
            string[] jpgFiles = Directory.GetFiles(imageDir, extension, SearchOption.AllDirectories);

            int width = 100;
            int height = 80;

            imageList1.ImageSize = new Size(width, height);
            listView1.LargeImageList = imageList1;

            for (int i = 0; i < jpgFiles.Length; i++)
            {
                Image original = Bitmap.FromFile(jpgFiles[i]);
                Image thumbnail = createThumbnail(original, width, height);

                imageList1.Images.Add(thumbnail);
                listView1.Items.Add(jpgFiles[i], i);

                original.Dispose();
                thumbnail.Dispose();
            }
        }

上記のコードで呼び出されているfunction

Image createThumbnail(Image image, int w, int h)
        {
            Bitmap canvas = new Bitmap(w, h);

            Graphics g = Graphics.FromImage(canvas);
            g.FillRectangle(new SolidBrush(Color.White), 0, 0, w, h);

            float fw = (float)w / (float)image.Width;
            float fh = (float)h / (float)image.Height;

            float scale = Math.Min(fw, fh);
            fw = image.Width * scale;
            fh = image.Height * scale;

            g.DrawImage(image, (w - fw) / 2, (h - fh) / 2, fw, fh);
            g.Dispose();

            return canvas;
        }
'''

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

C# - NumericUpDownコントロールはHexadecimal=trueで使用すると0x80000000が負になる(せっかくValueがDecimal型なのに入力値がInt32でConvertされる)

概要

NumericUpDownコントロールは下記のように、数値の入力をUp/Down操作するのに使いやすいコントロールです。

image.png

経緯

マイコンのエミュレータをフルスクラッチでつくろうというクレイジーなことをやり始めたときにはまった話。。

NumericUpDownコントロールはHexadecimaltrueにすると16進法で数値を表示できるのですが、32bit以上を扱おうとすると厄介な問題が発生しました。

Maximum0xFFFFFFFFとかにし、Minimumを0にして、スピン(?)(コントロールの右端の上下の▲のボタン)をいじってるときはよかったんですが、、

コントロールに直接FFFFFFFFFとかを入力すると、0になるんですよ。。。 えっ???
ってなりましたが、さすがにもうこの手の現象には慣れてきて、どうせ内部でInt32に変換しているんだろうなと思ったら案の定でした。。。

内部コード

ILSpyで確認してみた。

/// <summary>スピン ボックス (アップダウン コントロール) に表示するテキストを数値に変換して評価します。</summary>
protected void ParseEditText()
{
    try
    {
        if (!string.IsNullOrEmpty(Text) && (Text.Length != 1 || !(Text == "-")))
        {
            if (Hexadecimal)
            {
                Value = Constrain(Convert.ToDecimal(Convert.ToInt32(Text, 16)));
            }
            else
            {
                Value = Constrain(decimal.Parse(Text, CultureInfo.CurrentCulture));
            }
        }
    }
    catch
    {
    }
    finally
    {
        base.UserEdit = false;
    }
}

ヤッパリネ。。なんでInt32やねん・・・。けちくさい・・・。

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

C# - NumericUpDownコントロールはHexadecimal=trueで使用するときはMaximumプロパティとMinimumプロパティをそれぞれInt32.MaxValueと.Int32.MinValueにする制約事項があるが、負数の表示がなぜかlong型になっている

まえおき

追加調査した結果タイトルを見直しました。

旧タイトル:
C# - NumericUpDownコントロールはHexadecimal=trueで使用すると0x80000000が負になる(せっかくValueがDecimal型なのに入力値がInt32でConvertされる)

概要

NumericUpDownコントロールは下記のように、数値の入力をUp/Down操作するのに使いやすいコントロールです。

image.png

経緯

マイコンのエミュレータをフルスクラッチでつくろうというクレイジーなことをやり始めたときにはまった話。。

NumericUpDownコントロールはHexadecimaltrueにすると16進法で数値を表示できるのですが、32bit以上を扱おうとすると厄介な問題が発生しました。

Maximum0xFFFFFFFFとかにし、Minimumを0にして、スピン(?)(コントロールの右端の上下の▲のボタン)をいじってるときはよかったんですが、、

コントロールに直接FFFFFFFFとかを入力すると、0になるんですよ。。。 えっ???
ってなりましたが、さすがにもうこの手の現象には慣れてきて、どうせ内部でInt32に変換しているんだろうなと思ったら案の定でした。。。

内部コード

ILSpyで確認してみた。

/// <summary>スピン ボックス (アップダウン コントロール) に表示するテキストを数値に変換して評価します。</summary>
protected void ParseEditText()
{
    try
    {
        if (!string.IsNullOrEmpty(Text) && (Text.Length != 1 || !(Text == "-")))
        {
            if (Hexadecimal)
            {
                Value = Constrain(Convert.ToDecimal(Convert.ToInt32(Text, 16)));
            }
            else
            {
                Value = Constrain(decimal.Parse(Text, CultureInfo.CurrentCulture));
            }
        }
    }
    catch
    {
    }
    finally
    {
        base.UserEdit = false;
    }
}

ヤッパリネ。。なんでInt32やねん・・・。けちくさい・・・。

追調査

コメントを頂いて、Hexadecimaltrueに設定して使用した際の振る舞いとか、
今回やりたいこと(00xFFFFFFFF1(あえて型は問わず、16進数8桁を入力・設定できるユーザインタフェースをFormに置きたい。)ができないか追加で調べてみた。

そもそもドキュメントに制約が記載されている

しっかり制約事項が書かれていました。。。※日本語サイトのほうは訳が壊れていたので英語のほうを引用しています。
https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.numericupdown.hexadecimal?view=netcore-3.1#remarks

When the Hexadecimal property is set, the UpdateEditText method is called to update the spin box's display to the new format.
When the Hexadecimal property is set to true, the Maximum property should be set to Int32.MaxValue and the Minimum property should be set to Int32.MinValue.

ドキュメントの通り使ってみたら・・・

using System;
using System.Drawing;
using System.Windows.Forms;

class NudHexTest : Form
{
    NumericUpDown nud;

    NudHexTest()
    {
        Controls.Add(nud = new NumericUpDown(){
            Width = 180,
            Hexadecimal = true,
            Maximum =  Int32.MaxValue, //  0x7FFFFFFF
            Minimum =  Int32.MinValue, // -0x80000000
            Value   =  0
        });
        ClientSize = new Size(180, 100);
    }

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

image.png

▼を押したらFFFFFFFFが表示される(今回の使い方が一応実現できる)と思ったら、16桁になった。。

image.png

内部コード

public decimal Value
{
    get
    {
        if (base.UserEdit)
        {
            ValidateEditText();
        }
        return currentValue;
    }
    set
    {
        if (value != currentValue)
        {
            if (!initializing && (value < minimum || value > maximum))
            {
                throw new ArgumentOutOfRangeException("Value", SR.GetString("InvalidBoundArgument", "Value", value.ToString(CultureInfo.CurrentCulture), "'Minimum'", "'Maximum'"));
            }
            currentValue = value;
            OnValueChanged(EventArgs.Empty);
            currentValueChanged = true;
            UpdateEditText();
        }
    }
}

/// <summary>スピン ボックス (アップダウン コントロール) の現在の値を適切な形式で表示します。</summary>
protected override void UpdateEditText()
{
    if (!initializing)
    {
        if (base.UserEdit)
        {
            ParseEditText();
        }
        if (currentValueChanged || (!string.IsNullOrEmpty(Text) && (Text.Length != 1 || !(Text == "-"))))
        {
            currentValueChanged = false;
            base.ChangingText = true;
            Text = GetNumberText(currentValue);
        }
    }
}

private string GetNumberText(decimal num)
{
    if (Hexadecimal)
    {
        return ((long)num).ToString("X", CultureInfo.InvariantCulture);
    }
    return num.ToString((ThousandsSeparator ? "N" : "F") + DecimalPlaces.ToString(CultureInfo.CurrentCulture), CultureInfo.CurrentCulture);
}

なぜかこっちはlong (Int64)。。

参考

Decimal構造体 - Microsoft Docs


  1. もしくは0x80000000( $= -2^{31} = $Int32.MinValue)~0x7FFFFFFF($= 2^{31}-1=$ Int32.MaxValue

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

Unity初心者が思ったアレコレ。

Uniyを勉強していて思ったこと一覧。
思っただけで、実用的かは不明。

[Transform関係]
・オブジェクトの回転角はtransform.eulerAngleで取得する。
・オブジェクトの回転はtransform.rotationで行う。(Quaternionを使うべし)
・transform.Rotateはなるべく使用しない。

[Rigidbody関係]
・オブジェクトへの力をかける方法はAddForce()よりvelocity。

[Animation関係]
・アニメーションの遷移はanimator.Play()を極力使わない。
・paramaterを設定して、animator.SetFloat()などを用いて遷移を行う。
・アニメーションの終了確認はanimator.GetCurrentAnimatorStateInfo().normalizedTimeで比較する。
・目的のアニメーション名かはanimator.GetCurrentAnimatorStateInfo().IsName()で確認する。

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

Microsoft Fakesを用いてHttpContextを利用するクラスのテストを行う

概要

Webアプリケーションの開発で、HttpContextから情報を取得するメソッドがあったとする。このメソッドのテストはIISが必要となるため、通常であれば結合テストフェーズにならないとテストができない。しかしどうしても単体テストを行いたい場合には、Visual Studio Enterpriseで利用可能なMS Fakesを利用することで、何とか単体でのテストが可能であるということを、この記事で述べていきたいと思う。

Webアプリケーションのテストに関して

概要では、HttpContextから値を取得するメソッドのテストについて記述すると書いたが、本来であればテスト対象となるようなメソッドがHttpContextからじかに値を取得するような設計があまりよくないと思われる。
つまり、ブラウザから入力した値をコードビハインドやハンドラーで取得せずに後続処理に投げていたり、もしくはセッション状態に常にアクセス可能なオブジェクトを保持し続けるといった実装はクラス間の結合が密になってしまうため、改修やテストの際に扱いにくくなってしまうということである。またレガシーナコードなどでは、ビジネスロジックとしてオブジェクト(モデル)に移譲すべき処理をコードビハインドに直接記述しいるケースも考えられる。

正しく対応するのであれば、サーバー処理に必要な引数はコードビハインドで変数として抜き出して業務ロジックに引き渡すようにする。また、セッションにオブジェクトを保持しなくていいような設計にする。コードビハインドに業務ロジックがあった場合はリファクタリングして別のクラスに処理を委譲するなどプロジェクトのクラス構成から設計しなおした場合が良いことが多いだろう。

だが、レガシーなアプリケーションや、複雑な状態を保持するオブジェクトを何らかの理由(工期やプロジェクトのチーム構成の問題など)で保持する理由ケースが存在することは確かなので、そのようなメソッドをテストしたい場合もあるのが実情である。
では、どうすれば単体テストを実施することが可能なのだろうか。

※ここでは本当にHttpContextを必要としている場合を想定している。HttpContextから抜き出した値を保持している別のクラスからその値を取得する場合などは、その別クラスをインターフェース化しテスト用のスタブを作成するだけで十分テストを実施できる場合が多い。

Microsoft Fakes

Microsoft Fakesは2020年10月現在Visual StudioのEnterprise版でしか利用できないが、アプリケーションの一部の機能(プロパティ、メソッド)をテスト実施時に自分の好きなふるまいをするように置き換えることができるツールである。
(参考:https://docs.microsoft.com/ja-jp/visualstudio/test/isolating-code-under-test-with-microsoft-fakes?view=vs-2019 )
このツールを用いて、HttpContextの返す様々なオブジェクトや値をテスト実施の際に偽装してしまえば、今問題になっているメソッドのテストが行える。

サンプルプロジェクト

今、適当なWebアプリを作成してみる。

image.png

Visual Studio 2019だと、空のソリューションに対してC#>Windows>Webでプロジェクトの種類を絞り込んで、ASP.NET Webアプリケーション(.NET Framework)を選択し、空のプロジェクトを選択作成したうえで、default.aspxとエラーのリダイレクト先のerropage.htmlを追加して作っている。

それぞれのファイルの中身は以下のような感じ

defautl.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="WebAppTest._default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <p>表示用の文字を入力してください</p>
            <p><input type="text" name="presentation" /></p>
            <p>
                <button type="submit" id="button1">"送信"</button>
            </p>
        </div>
    </form>
</body>
</html>
default.aspx.cs
using System;
using System.Web;

namespace WebAppTest
{
    public partial class _default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string sentence = HttpContext.Current.Request["presentation"];
            if (!string.IsNullOrEmpty(sentence))
            {
                if (sentence == "例外表示")
                {
                    // Exceptionを投げたい
                    throw new ApplicationException("エラー表示をします");
                }
                else
                {
                    this.Context.Response.Write($"あなたは「{sentence}」と入力しました。");
                }
            }
        }
    }
}
web.config
  <system.web>
    <compilation debug="true" targetFramework="4.8"/>
    <httpRuntime targetFramework="4.8"/>
    <!-- リダイレクト先を指定 -->
    <customErrors defaultRedirect="errorpage.html" mode="On" />
  </system.web>
errorpage.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    例外が発生しました。
</body>
</html>

フォームに入力した文字を加工して表示するだけの単純なアプリで、「例外発生」という文字列が送られてきた時だけエラーページに飛ばすような作りである。
では、このdefault.aspx.csにあるPage_Loadで、本当に「例外発生」という文字列が送られてきた時にApplicationExceptionをスローしているかをテストするにはどうすればいいだろうか。

テストプロジェクトの作成

ソリューションに空のテストプロジェクトを追加して、以下の手順でテストクラスを作成します。

  1. 追加するのはC#>Windows>テスト の中の 単体テストプロジェクト(.NET Framework)
  2. WebAppTestに対してプロジェクト参照を追加
  3. テストする対象がPageオブジェクトの継承クラスなので、System.Web.dllに対するアセンブリ参照を追加
  4. テスト対象メソッドがprotectedメソッドなので、外部から呼び出せるように_default.aspx.csの継承クラスdefaultPageInheritor.csを追加
defaultPageInheritor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebAppTest;

namespace WebAppTest_Test
{
    /// <summary>
    /// _default.aspx.csをテストするための継承クラス
    /// </summary>
    public class defaultPageInheritor : _default
    {
        /// <summary>
        /// protectedメソッドは直接実行できないので、このメソッドから呼び出す
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Page_Load(object sender, EventArgs e)
        {
            base.Page_Load(sender, e);
        }
    }
}
  1. テストクラスdefaultTests.csを追加
  2. 必要に応じて以下のテストフレームワークをNuGetからインストールする image.png

テストの実装

Page_Loadのタイミングで例外を投げることをテストしたいので、以下のようなテストコードを記述してみるが、このままだとフォームからの入力がないため文字列を渡すことができないうえに、HttpContext.Currentが存在していないためプロパティにアクセス時に例外発生するという問題が発生する。
ここで本題であるMicrosoft Fakesを利用することで、本来存在しないはずのHttpContext.Current及びその先のプロパティを偽装することができるのである。

Fakesを用いたテストの実装

Fakesで偽装をしたい対象がHttpCotextクラスのCurrentプロパティなので、このクラスを含むSystem.Web.dllをFakesアセンブリに追加する
image.png

すると、Fakesというフォルダがテストプロジェクトに追加され、このdllに含まれるクラスのクラスを偽装できることがわかる。
image.png

次に、HttpContextを偽装していくわけだが、偽装の定義はShimContextのスコープ内でしか利用できないので、テストの最中だけShimContextを用いるようusing節の内部にテストコードを書くことになる。もちろんusing節を必ずしも用いる必要はないのだが、その場合自分でShimContextを破棄しなければならないので注意が必要である。

偽装の実装法は、ラムダ式で定義するのが一般的なため以下のようなコードになる。

defaultTests.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace WebAppTest_Test
{
    [TestClass]
    public class defaultTests
    {
        [TestMethod]
        public void Page_Load_Test()
        {
            using (ShimsContext.Create())
            {
                #region 偽装の実装
                // Request.Formが返すダミーを準備
                NameValueCollection dummyForm = new NameValueCollection();
                // Request.QueryStringが返すダミーを宣言
                NameValueCollection dummyQS = new NameValueCollection();
                // Request.Itemsが返すダミーを準備
                NameValueCollection dummyItems = new NameValueCollection();

                // HttpContext.Requestが返すダミーを準備
                var fakeRequest = new System.Web.Fakes.ShimHttpRequest()
                {
                    // Formプロパティの偽装
                    FormGet = () => dummyForm,
                    // QueryStringの偽装
                    QueryStringGet = () => dummyQS,
                    ItemGetString = (string key) => dummyItems[key]
                };

                // HttpContext.Sessionが返すダミーを準備
                DummySessionState dummySessionItem = new DummySessionState();
                var fakeSessionState = new System.Web.SessionState.Fakes.ShimHttpSessionState()
                {
                    ItemGetString = (x) => dummySessionItem[x],
                    ItemSetStringObject = (x, value) => dummySessionItem[x] = value
                };

                // HttpContextのItemsが返すダミーを準備
                var fakeItems = new Hashtable();

                // HttpContext.Curretnの偽装
                System.Web.Fakes.ShimHttpContext.CurrentGet = ()
                    => new System.Web.Fakes.ShimHttpContext()
                    {
                        // HttpContext.Current.Requestの偽装
                        RequestGet = () => fakeRequest,
                        // HttpContext.Current.Sessionの偽装
                        SessionGet = () => fakeSessionState,
                        // // HttpContext.Current.Itemsの偽装
                        ItemsGet = () => fakeItems,
                    };
                #endregion

                // 例外を発生させる文言をセット
                dummyItems["presentation"] = "例外表示";

                var page = new defaultPageInheritor();
                Assert.ThrowsException<ApplicationException>(
                    () => page.Page_Load(null, null)
                    );
            }
        }
    }
}

ここで、DummySessionStateクラスはSessionの偽装用クラスでどのように実装してもいいのだが、一例としては以下のような実装が考えられる。

DummySessionState.cs
using System.Collections;
using System.Collections.Generic;

namespace WebAppTest_Test
{
    class DummySessionState : IDictionary<string, object>
    {
        Dictionary<string, object> innerDic = new Dictionary<string, object>();

        public object this[string key] 
        { 
            get
            {
                if(innerDic.ContainsKey(key))
                {
                    return innerDic[key];
                }
                else
                {
                    return null;
                }
            }
            set => ((IDictionary<string, object>)innerDic)[key] = value; 
        }

        public ICollection<string> Keys => ((IDictionary<string, object>)innerDic).Keys;

        public ICollection<object> Values => ((IDictionary<string, object>)innerDic).Values;

        public int Count => ((ICollection<KeyValuePair<string, object>>)innerDic).Count;

        public bool IsReadOnly => ((ICollection<KeyValuePair<string, object>>)innerDic).IsReadOnly;

        public void Add(string key, object value)
        {
            ((IDictionary<string, object>)innerDic).Add(key, value);
        }

        public void Add(KeyValuePair<string, object> item)
        {
            ((ICollection<KeyValuePair<string, object>>)innerDic).Add(item);
        }

        public void Clear()
        {
            ((ICollection<KeyValuePair<string, object>>)innerDic).Clear();
        }

        public bool Contains(KeyValuePair<string, object> item)
        {
            return ((ICollection<KeyValuePair<string, object>>)innerDic).Contains(item);
        }

        public bool ContainsKey(string key)
        {
            return ((IDictionary<string, object>)innerDic).ContainsKey(key);
        }

        public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
        {
            ((ICollection<KeyValuePair<string, object>>)innerDic).CopyTo(array, arrayIndex);
        }

        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            return ((IEnumerable<KeyValuePair<string, object>>)innerDic).GetEnumerator();
        }

        public bool Remove(string key)
        {
            return ((IDictionary<string, object>)innerDic).Remove(key);
        }

        public bool Remove(KeyValuePair<string, object> item)
        {
            return ((ICollection<KeyValuePair<string, object>>)innerDic).Remove(item);
        }

        public bool TryGetValue(string key, out object value)
        {
            return ((IDictionary<string, object>)innerDic).TryGetValue(key, out value);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)innerDic).GetEnumerator();
        }
    }
}

Visual Studioでは、インターフェース(今の場合IDictionary)の継承クラス内に、そのインターフェースを実装したクラスをフィールド(今の場合innerDic)として保持していると、そのフィールドを通じてクラス実装のコードを自動生成してくれる機能があるので、このような実装も簡単に行うことができる。

多少コードが長くなったが、内容はシンプルなので少し読んでみれば簡単にわかる内容となっているはずである。
この程度のコーディングでテストを可能にするFakesはテストの適用範囲を広げてくれる可能性を感じさせてくれるのではないだろうか。

まとめ

今回の例では、本来RequestのItemsのみを偽装すればテストには十分だったが、よくHttpContextからアクセスする可能性のあるFormやQueryStringなどのプロパティに関しても偽装を実装してみた。

Fakesを用いれば、今回のような静的プロパティを偽装してテストを実施できることを示したが、冒頭にも記したようにグローバルオブジェクトのように振舞えるHttpContextへのアクセスは限定的にし、テスト可能なクラス設計を行うことが正攻法なので、何でもかんでも偽装をすればよいとクラス構成をおろそかにしてはならないことに注意しよう。

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

【Unity】特定のLayerだけPostProcessを適用する

こんにちはっ?八ツ橋まろんです

今回はUnityで『特定のレイヤーだけPostProcessを適用する』方法の解説です。
対象は
・PostProcessingStack
・Layer
がわかる人です。

概説

そもそも特定のLayerだけPostProcessを適用したい時ってどんな時よ?って人向けの画像は以下です↓↓↓↓↓↓↓↓
2020-10-12_21-40-40.png

ここでは眼とペンダント以外の全てのオブジェクトにPostProcessをかけて見た目をグレースケールにしています。かっこいい✨!!

UnityのPostProcessingStackはとても便利で使いやすいですが、基本的には「描画した全てのオブジェクトに対して適用されるもの」なので、特定のオブジェクトだけに適用することはできないのですが、CameraとLayerを使うことで疑似的に実現していきます。

やり方

?Cameraを3つ用意します。以下のように、どれか一つを親にして残りを子にしておくと後で楽です。
grg.png

・1つめ:通常描画用のCamera
図1.png
Clear Flags: Don't Clear
Culling Mask: PostProcessをかけたいオブジェクト以外を描画
Depth: 1

・2つめ:PostProcess適用する用のCamera
図2.png
Clear Flags: Don't Clear
Culling Mask: PostProcessをかけたいオブジェクトのみを描画
Depth: 0
PostProcessgLayer Componentを付ける

・3つめ:Skybox描画用のCamera
図3.png
Clear Flags: Skybox
Culling Mask: Nothing
Depth: 指定なし
Target Texture: 1920x1080など、Game画面に合わせた適当なRenderTextureを選択

?Canvas (Raw Image)を用意します。Skybox描画用です。
・Raw Image:上記のRenderTextureを指定し、サイズはGame画面と同じに。
・Canvas:Render Mode: Screen Space-Cameraで1つ目のカメラを指定

?PostProcessingVolumeを用意します。設定は自由です。

これで準備は完了です。2つ目のカメラに映るオブジェクトだけがPostProcessの影響を受けます。

解説

・Game画面に映るのはHierarchyにあるCameraに映るものをDepthの小さい順に並べた映像になります。

・Cameraが2個以上ある場合、デフォルトではClear FlagsがSkyboxなので、Depthの値が最も大きいCameraの映像のSkyboxによってそれ以外のCamera映像は上書きされ、Game画面に映りません。

・Clear FlagsがDon't ClearのCameraの映像は背景が透過した映像になるので、Game画面にはDepthの小さいCameraの映像も一緒に映ります。

・今回は2つ目のカメラにPostProcessが付いているため、それ以下のDepth値のCameraに映るオブジェクトは全てPostProcessが適用されます。
(Depthが-3,-2,-1,0のCameraがあったとして、-1のCameraにPostProcessが適用される場合、Depthの小さい順に描画するため-1のCamera描画時には-3と-2の映像は既に描画されており、その映像と-1のCamera映像をまるっと含めてPostProcessが適用される)

・その性質上、SkyboxはDepthが最も小さいCameraで描画しないといけないですが、そうするとどうしてもPostProcessに影響されてしまうので、RenderTextureに描画して、Canvasを使ってDepthの高いCameraのScreenSpaceに描画することで、Depthが高く且つPostProcessよりも後に描画するという回避を行っています。

解説は以上となります。

その他応用

・親子構造にしたため、CinemachineなどでCameraを動かしたい場合、親のCameraを動かすことで子も一緒に追従するので便利です。
・CameraのField of viewなどのパラメータを変更したいとき、3つのCameraを全て変更しないといけませんが、以下のようなスクリプトを書けば親の変更が子に適用されるので便利です。([ExecuteAlways]のためEditor上でも動きます)

CameraParamSync.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteAlways]
public class CameraParamSync : MonoBehaviour
{
    [SerializeField]
    Camera ParentCamera;
    [SerializeField]
    Camera[] ChildCamera;

    void Update()
    {
        if (ParentCamera != null && ChildCamera != null)
        {
            for (int i = 0; i < ChildCamera.Length; i++)
            {
                ChildCamera[i].fieldOfView = ParentCamera.fieldOfView;
                ChildCamera[i].nearClipPlane = ParentCamera.nearClipPlane;
                ChildCamera[i].farClipPlane = ParentCamera.farClipPlane;
            }
        }

    }
}

・HDRPの場合、Clear Flags: Don't Clearがないためこのままのやり方は適用できませんが、RenderTextureを複数枚使うことで上手く動かせると思います。

以上『特定のLayerだけPostProcessを適用する』でしたっ?

八ツ橋まろん
Twitter
YouTube

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

C#で簡単にイージング

C#で簡単にイージング

C#で簡単にイージングができるライブラリを作ってみました!
NuGetのリンク
Nuget

インストール方法

VisualStudioの上にある、ツールをクリックします
image.png
そして、Nuget パッケージマネージャー
→ソリューションのNuGetパッケージの管理(N)
をクリックします。
image.png
そして、出てきた検索ボックスに、
EasingSharp
と入力します。
image.png
一番上のものをクリック、
そして右の画面でインストールするプロジェクトを選択。
インストールをクリックしたらインストールが開始されます。

image.png

使い方

using EasingSharp; //コードの一番上に書いてください
Easing.easeOutExpo((x) => //easeOutExpoで375から3まで1000ミリ秒でアニメーションさせる
{
               //処理  (アニメーションが終了するときには、xは3になります、 intにキャストして使用できます)
}, 初期値,最終値,アニメーションさせる時間ms);

初期値 , 最終値 , アニメーションさせる時間(ms)を任意の値へ置き換えてください
イージングのCallbackは、別スレッドから行われるので、
コントロールなどを操作しようとすると、System.InvalidOperationExceptionが発生します。
そういう場合は、Invokeを使用してください
例 : Button1.Invoke((Action)(() => { 処理 }));

その外にも、

Easing.Run((x) => //easeInExpoで375から3まで1000ミリ秒でアニメーションさせる
{
  //処理
},"easeInExpo", 375, 3, 1000);//easeInExpoが見つからなかった場合、EasingNotFoundExceptionをスローします

や、

Easing.Run((x) => //easeInExpoで375から3まで1000ミリ秒でアニメーションさせる
{
    //処理
},Easing.Easings.easeOutCubic, 375, 3, 1000);

といった方法でも実行できます

Q&A

  • FPSは?
    60FPSです。

  • ライセンスは?
    MITライセンスです。
    MITについての詳細は、こちらを参照してください

  • System.InvalidOperationException が発生した!
    イージングのCallbackは、別スレッドから行われるので、
    Control.Invoke などで実行してください。

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

EventPipe(EventSource)から流れてくるデータを処理する

始めに

前回の記事で、DiagnosticSourceからEventSourceへ出力する記事 を書いたが、今回は、そのEventSourceのデータを処理するためのライブラリの使い方を書く。

これによって、コマンドラインで来たデータを加工したり、あるいはWPFで流す等の処理も書けるようになる。

今回調査に使ったソースをgistに貼っておく。

前提条件

  • dotnet sdk-3.0以降が必要
  • 情報収集対象のアプリが、netcoreapp3.0以降

準備(イベント出力側の用意)

前回の記事でも作成したソースを使用する。

using System.Diagnostics;
using System;
using System.Threading.Tasks;
class C1
{
    public DiagnosticSource _D = new DiagnosticListener("Diag1");
    public void A()
    {
        if(_D.IsEnabled("ev1"))
        {
            _D.Write("ev1", new { X = "str" });
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        while(true)
        {
            // 1秒に一回"ev1"イベントが発生する
            // 停止機能はないので、Ctrl+C等を使用して止めること
            new C1().A();
            Task.Delay(1000).Wait();
        }
    }
}

必要なライブラリ

Microsoft.Diagnostics.NETCore.ClientMicrosoft.Diagnostics.Tracing.TraceEventが必要。

Microsoft.Diagnostics.NETCore.Clientについて

EventPipeプロトコルでdotnetプロセスにアタッチするためのライブラリ。dotnetプロセスが用意するEventPipe読み込みの口へ、PIDをキーにしてアクセスして、データを受け取る。

主に使用するクラスは以下(名前空間はMicrosoft.Diagnostics.NETCore.Clientからの相対位置)

  • EventPipeProvider
    • EventSourceの出力元の情報を保持するクラス
    • 捕捉するイベントの種類、レベル、各種フラグ等を設定する
  • DiagnosticClient
    • EventProviderとPIDを元に、EventPipe接続を行う
    • StartEventPipeSession
    • public static IEnumerable<int> DiagnosticClient.GetPublishedProcesses()で、アタッチ可能なPIDの一覧が取得可能
  • EventPipeSession
    • DiagnosticClient.StartEventPipeSessionから生成される、セッションオブジェクト
    • EventPipeSession.EventStreamで生のバイトデータが取得できる
    • 使用後は、必ずStop()メソッドで動作を停止すること

さて、これだけではEventPipeの生データが取得できるだけで、中身はバイナリデータなので人に読めるものではない(いや、もしかしたら読める人もいるかもしれないが)。
これを解析してくれるライブラリが、Microsoft.Diagnostics.Tracing.TraceEventとなる

Microsoft.Diagnostics.Tracing.TraceEventについて

前述のEventPipeSessionから得たデータを解釈して、プログラム的に解析するためのライブラリ。起点はStreamから読み込むので、先のEventPipeSession.EventStreamをの内容をそのまま出力したファイルを使っても良い

EventStreamの解析に使用する場合、主に使用するクラスはMicrosoft.Diagnostics.Tracing.EventPipeEventSourceである。

EventPipeEventSourceについて

System.IO.Streamをコンストラクタにとる。Streamには、EventPipeSession.EventStreamから得たデータが入っている想定。
データ解析手順には非同期イベントパターン(EAP)を採用しているので、EventPipeEventSourceに付随するイベントを購読する。
また、IObservable<T>を返すインターフェイスも提供している。

イベントは、共通のヘッダ部分以外はイベント固有のデータが流れてくるため、そのイベント用の解析クラスを用意する必要がある。
このイベント解析用ベースクラスがTraceEventParserで、解析クラスはこれを継承して各イベントの解析処理を実装している。

例えば、CLRのイベント(GCの回収イベントとか)は、専用のパーサーが用意されており、EventPipeEventSource.Clrで専用のイベントが用意されている。

なお、汎用的な用途に使えるDynamicTraceEventParserも存在しており、ライブラリ側で用意されていない場合はこちらを使用することになるだろう。

そして、一通りのイベント設定が終わったら、EventPipeEventSource.Processで解析を開始する。
注意点として、Processを実行した時点でスレッドがロックされるため、途中キャンセル等をしたい場合は、
別スレッドを展開して、別のスレッドから、EventPipeEventSource.StopProcessingと、元のストリームのクローズ(EventPipeSessionからStreamを取得している場合は、EventPipeSession.Stopを実行する
具体的には以下

// EventPipeSession session;
// 汎用的なイベントを購読
evsrc.Dynamic.All += (TraceEvent evt) =>
{
    // 解析処理
};
await Task.WhenAll(
  // 解析スレッド
  Task.Run(() => evsrc.Process()),
  // 停止スレッド
  // エンターキーを押したら終了する
  Task.Run(() =>
  {
    Console.ReadLine();
    evsrc.StopProcessing();
    session.Stop();
  }
);

なお、これを通さないAction<TraceEvent> EventPipeEventSource.AllEventsなるイベントもあるが、これを通すとPayloadNames(イベントデータプロパティの名前)など、主にPayload関連について、セットされないプロパティが出てくる。その場合は、TraceEvent.EventData()でバイトデータを取得して、自力でパースする必要がある。

EventPipeEventSource.Dynamic.Allの中の解析処理について

EventPipeEventSource.Dynamic.Allの中では、TraceEvent.PayloadNamesTraceEvent.PayloadByName(string name)の組み合わせでデータを取得する。具体的には以下のようになる。

// TracEvent traceevt;
foreach(var pname in traceevt.PayloadNames)
{
    object pobj = traceevt.PayloadByName(pname);
    Console.WriteLine($"{pname} = {pobj}");
}

pobjが何になるか、というところは、プリミティブ型(数値、文字列)ならばそのままキャストすればOK。しかし、それ以外のオブジェクト等がイベント発生元で渡された場合は少々工夫が必要。

ペイロードデータにオブジェクトで来た場合

この場合、実際の型はinternalで隠蔽されているが、取り出す時にはIDictionary<string, object>[]で取り出すことができる([]なことに注意)。
IDictionary<string, object>には二つ要素が格納されており、それぞれのキーはKeyValueで、値の方にそれぞれプロパティ名と、実際の値が格納されている
具体的には以下のように値を取り出す。

// object pobj = traceevt.PayloadByName(pname);
if(pobj is IDictionary<string, object>[] pdicar)
{
    foreach(var pdic in pdicar)
    {
        string key = (string)pdic["Key"];
        object value = pdic["Value"];
        // 解析処理
    }
}

より複雑な型の場合はネストして処理が必要になるだろうが、そもそもEventSourceでそこまで複雑なデータは作らない方が良いと思う。
パーサー自体についての詳細なドキュメントは、 githubのmicrosoft/perfviewのドキュメントにある

解析処理の注意点

コールバック内の例外処理

解析コールバックの中で例外をキャッチし損ねると、以後のイベントが流れなくなり、更に、StopProcessingも受け付けなくなった。
なので、現時点ではコールバックの中ではtry-catchで囲った方が良い。

一つもイベントが来ない条件で指定したらフリーズする

少なくともMicrosoft.Diagnostics.Tracing.TraceEvent-2.0.61時点では、一つも該当するイベントが無いようなEventPipeProviderを設定すると、EventPipeEventSourceのコンストラクタで処理が止まってしまうという不具合があるらしい。
詳しい内容は下記issue

終わりに

前回の記事の派生で、EventPipeについて調べてみたけど、取っ掛かりは思ったより簡単だなという感想だった。
ここから、自分なりに使いやすいモニタリングツールを作ってみるのもいいかもしれない。

また、EventPipe自体はローカル限定なので、ここから外部ネットワークに流す口を作るというのも面白い発想かもしれない。

今回紹介したのはイベントの取得のみだが、その他にもメモリダンプを取ることもできる等、トラブルシューティングに役立つ機能があるので、うまく活用していきたい。

参考資料

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

【ポエム】System.IO.IOException: デバイスの準備ができていません。

System.IO.IOException: デバイスの準備ができていません。とか言ったって
そもそも存在しないドライブを参照しようとしているだけだったり
するんだなあ

ドキュメントに ばっちり書いて あるんだなあ

https://docs.microsoft.com/ja-jp/dotnet/visual-basic/misc/device-unavailable

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

C# / WPFのデスクトップアプリで通知を出す(2案)

概要

MaterialDesign ToolKit for xamlを使っていて、標準ダイアログでは我慢できなかった。
Snackbarはちょっと物足りないし、Windows10の通知みたいなものも使いたい。

調べたところ、MaterialDesignになじみそうなNotifications.Wpfと、名前がよく似ている(というかフォーク)のNotification.Wpfが見つかった。

2017年から更新されていない前者に対し、ProgressBarとかを追加して2020年現在メンテされてる模様。
いずれもNuGetからインストールできる。

ただMDに馴染むがWindowsの通知機能ではないので、そちらも使える方法を探すことにした。

前提環境

  • WPF
  • Windows(8.1↑)
  • .NET Framework (4.7↑)

Notification.Wpfによる通知

GitHubにサンプルが動画であがっているので、そちらを参考にした。
完成系がこちら↓

Window内への通知

image.png

ここで通知を押すとさらに↓画面右下に以下のような通知が出る。
image.png

WinRTによる通知

日曜研究室さんの情報、およびリンク先で紹介されているWinRTの参照方法を参考にした。

みかづきメモによると8種類ほどテンプレートをWindowsが用意してくれていて、独自にToastをカスタマイズするにはXMLで行う模様。

完成系がこちら↓

image.png

Windowsでの通知なのでアクションセンターで履歴も表示される。

image.png

ソースコード

(Livetのプロジェクトで開発しはじめたのに、普通にコードビハインドつかってたりするのは無視してください…)

設定

Windows通知Toastを利用するにはWindowsSDKが必要な様子。はいってなければインストールする。

WinRTを読みこむため元情報にあるようにVisualStudio外でcsprojを編集した。
続いてVisualStudioで、「プロジェクト→プロジェクト参照の追加」で参照マネージャよりSystem.Runtime.WindowsRuntimeWindows.winmdの参照の追加を行った。 1
下のソースでなんとなく読み取れると思うが、それぞれProgramFiles(x86)以下のフォルダをたどって、直接指定している。

またNuGetでNotication.Wpfをインストールした。 2

image.png

hoge.csproj
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>

    <TargetPlatformVersion>8.0</TargetPlatformVersion><!-- ←これ -->

    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Caliburn.Micro.Core" Version="3.2.0" />
    <PackageReference Include="LivetCask" Version="3.2.1" />
    <PackageReference Include="Notification.WPF" Version="1.0.2.1" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="System.Runtime.WindowsRuntime">
      <HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</HintPath>
    </Reference>
    <Reference Include="Windows">
      <HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Annotated\Windows.winmd</HintPath>
      <IsWinMDFile>true</IsWinMDFile>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <None Update="Resources\giant.png">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

App.xaml

App.xaml
<Application x:Class="LivetNotifyWpfTest01.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:controls="clr-namespace:Notification.Wpf.Controls;assembly=Notifications.Wpf"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             xmlns:local="clr-namespace:LivetNotifyWpfTest01"
             ShutdownMode="OnMainWindowClose"
             StartupUri="Views\MainWindow.xaml"
             Startup="Application_Startup">
    <Application.Resources>
        <local:CustomTemplateSelector x:Key="CustomTemplateSelector" />

        <Style TargetType="controls:Notification">
            <Setter Property="ContentTemplateSelector" Value="{StaticResource CustomTemplateSelector}" />
            <Style.Resources>
                <DataTemplate DataType="{x:Type system:String}" x:Key="PinkStringTemplate">
                    <Label MinHeight="80"
                           Content="{Binding}"
                           Foreground="DeepPink" HorizontalContentAlignment="Center"
                           VerticalContentAlignment="Center" />
                </DataTemplate>
            </Style.Resources>
        </Style>
    </Application.Resources>
</Application>

MainWindow.xaml

ボタン2こおいて、通知エリア用にすこしウィンドウを広くしている。
一番したの controles:NotificationArea が完成系の青い通知に相当。

MainWindow.xaml
<Window
    x:Class="LivetNotifyWpfTest01.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:v="clr-namespace:LivetNotifyWpfTest01.Views"
    xmlns:controls="clr-namespace:Notification.Wpf.Controls;assembly=Notifications.Wpf"
    xmlns:local="clr-namespace:LivetNotifyWpfTest01"
    xmlns:vm="clr-namespace:LivetNotifyWpfTest01.ViewModels"
    Title="MainWindow"
    Width="525"
    Height="350">

    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <behaviors:Interaction.Triggers>
        <behaviors:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodName="Initialize" MethodTarget="{Binding}" />
        </behaviors:EventTrigger>

        <behaviors:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction />
        </behaviors:EventTrigger>
    </behaviors:Interaction.Triggers>

    <Grid >
        <Button x:Name="button" Content="Notification.Wpf" HorizontalAlignment="Left" Height="35" Margin="57,83,0,0" VerticalAlignment="Top" Width="138" Click="button_Click"/>
        <Button x:Name="button2" Content="WinRT" HorizontalAlignment="Left" Height="35" Margin="57,150,0,0" VerticalAlignment="Top" Width="138" Click="button_Click2" />

        <controls:NotificationArea x:Name="WindowArea" Position="BottomRight" MaxItems="3" Margin="0 0 10 10"/>
    </Grid>
</Window>

MainWindow.xaml.cs

MainWindow.xaml.cs
using System.Diagnostics;
using System.IO;
using System.Windows;
using Windows.UI.Notifications;
using Notification.Wpf;

namespace LivetNotifyWpfTest01.Views
{
    public partial class MainWindow
    {
        private readonly NotificationManager _notificationManager = new NotificationManager();

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Notification.Wpfでの通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button_Click(object sender, RoutedEventArgs e)
        {
            var content = new NotificationContent {Title = "ウィンドウ内の通知やでー", Message = "推して"};
            var clickContent = new NotificationContent
            {
                Title = "推された!?",
                Message = "う、うおーーー。にっぽごーー",
                Type = NotificationType.Error
            };
            _notificationManager.Show(content, "WindowArea", onClick: () => _notificationManager.Show(clickContent));

        }

        /// <summary>
        /// WinRTを使った通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button_Click2(object sender, RoutedEventArgs e)
        {
            var template = ToastTemplateType.ToastImageAndText02;
            var xml = ToastNotificationManager.GetTemplateContent(template);

            var images = xml.GetElementsByTagName("image");
            var src = images[0].Attributes.GetNamedItem("src");
            if (src != null)
            {
                src.InnerText = "file:///" + Path.GetFullPath("Resources\\giant.png");
                Debug.WriteLine(src.InnerText);
            }

            var texts = xml.GetElementsByTagName("text");
            texts[0].AppendChild(xml.CreateTextNode("のび太~。"));
            texts[1].AppendChild(xml.CreateTextNode("ジャイ子が家に来てほしいってさ~"));

            var toast = new ToastNotification(xml);

            ToastNotificationManager.CreateToastNotifier("? 通 知 ?").Show(toast);
        }

    }
}

その他

Notification.WpfはCaliburn.Microに依存しているらしく、サンプルコピペでビルドしたらdependエラーとなった。
csprojにあるように、NuGetでCaliburn.Micro.Coreを追加でインストールしている。

かっこよさをとるか、標準仕様をとるかという選択肢があっていいですね。
はい/いいえ等ユーザからのフォードバックもとれそうなので、ダイアログ替わりにも使えそう。


  1. .NET5からは楽になるみたい 

  2. Notification.Wpfが求めてくるのでCaliburn.Micro.Coreもインストールした。 

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

C++構造体メンバに対しC#クラスのプロパティでマーシャリングする

メンバではなくプロパティでやる理由

C++の構造体に対しマーシャリングする時、通常ならメンバで充分対応出来るのですが
XMLやjsonへのシリアライズや、PropertyGridクラスへの対応が簡単に出来るようになるので
クラスのプロパティを使用します
(MSDNにプロパティとマーシャリングの関連について記述が見当たらなかったのでメモした具合です)

一例

睡眠限界状態なので色々省いてるかもです

C++側実装

C++
// 構造体定義
struct TestStruct
{
    int numA;
    int numB;
    wchar_t txt[20];
};

// 関数定義
extern "C" __declspec(dllexport) void CppDll(TestStruct test);

C#側実装

C#
// マーシャリング用クラス定義
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class TestStruct
{
    // メンバでやる方法
    int numA;

    // 公開プロパティにする
    // このプロパティはマーシャリングに影響しない
    public int numAProperty
    {
        get { return numA; }
        set { numA = value; }
    }

    /* ============================================ */

    // プロパティでやる方法
    // 自動プロパティで適用可
    public int numB { get; set; }

    /* ============================================ */

    // 文字列の場合
    // 自動プロパティは不可(後述)
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    string txt;

    public string txtProperty
    {
        get { return txt; }
        set { txt = value; }
    }
}

// C++関数定義
[DllImport("hoge.dll")]
public static extern void CppDll(TestStruct test);

static void Main(string[] args)
{
    // 使用例
    var hoge = new TestStruct()
    {
        numAProperty = 1,
        numB = 2,
        txtProperty = "piyo",
    };
    CppDll(hoge);
}

stringクラスは自動プロパティ化で対応出来ない

C#
// 以下エラーとなる
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string txt { get; set; }

MarshalAsAttributeクラスはプロパティに対しては指定不可なので、
自動プロパティ以外の方法で実装する必要があります

最後に

寝る時間惜しんで書くもんでもなかった
C#でWM_COPYDATAを大量に使う事になるのが悪い

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