20200107のC#に関する記事は5件です。

OpenCV をソースコードからビルドしてXamarin で利用する

はじめに

本記事は下記の続き(リベンジ)です。

Xamarin.Forms(Android) から OpenCV(C/C++) を利用する

前回はOpenCV公式の Android 用ライブラリを Xamarin から使用しました。
今回はソースコードをセルフビルドして Android 用の共有ライブラリ(*.so) を作成します。

類型的ですが、ライブラリのセルフビルドには以下のメリットがあります。

  • モジュール(機能)を絞ることでライブラリのファイルサイズを減らせる

  • 拡張モジュール群(opencv_contrib)をライブラリに含められる

確認環境

  • OpenCV 4.2.0
  • Windows 10
  • CMake 3.16.2
  • MinGW 8.2.0-5
  • Android SDK 28.0.3
  • Android NDK 20.0.5594570

環境構築

まずはビルドに必要なツールを導入していきます。OpenCV の入手は割愛します。

  • CMake

  • MinGW

  • Android SDK / NDK

CMake

ココ の Latest Release (3.16.2) をインストールしました。

MinGW

ココ のインストーラ (mingw-get-setup.exe) を使い、以下の設定でインストールしました。

mingw.PNG

インストールが完了したら 環境変数 PATH に binフォルダのパスを通しておきます。 ポイント

Android SDK / NDK

最後に Android SDK と NDK をインストールするため、ココ からAndroid SDK Command line tools を取得します。

(Android Studio をインストールされている方は、そちらからインストールすると良いと思います)

次に SDK / NDK をインストールします。今回は C:\dev\android-sdk\tools に解凍しました。

$ cd C:\dev\android-sdk\tools\bin
$ .\sdkmanager.bat --update
$ .\sdkmanager.bat --list          # パッケージの一覧表示

表示されたリストを見て SDK / NDK のバージョンを指定します。

今回は(VisualStudio に合わせて)以下のバージョンを選択しました。

$ .\sdkmanager.bat "build-tools;28.0.3"    # SDK
$ .\sdkmanager.bat "ndk;20.0.5594570"     # NDK

それぞれ C:\dev\android-sdk 以下のディレクトリにインストールされます。

後工程の CMake Generate にて、android.toolchain.cmake の設定と NDK 内のフォルダ名が一致しなかったので、フォルダ名を手作業で変更しました。ポイント

 変更前:C:\dev\android-sdk\ndk\20.0.5594570\toolchains\llvm\prebuilt\windows

 変更後:C:\dev\android-sdk\ndk\20.0.5594570\toolchains\llvm\prebuilt\windows-x86_64

CMake Generate

環境構築が済んだので、いよいよ ビルドに向けた準備 に入ります。長い…

設定1

CMake(GUI) を立ち上げて、ソース / ビルドディレクトリ を指定します。

早速 Configure を押しそうになりますが、落ち着いて以下を "+ Add Entry" します。ポイント

(Androidのアーキテクチャが異なる方は armeabi-v7a や x86_64 を指定してください)

Name Type Value
ANDROID_SDK_ROOT FILEPATH C:\dev\android-sdk
ANDROID_ABI STRING arm64-v8a

追加したら Configure ボタンを押します。

cmake1.PNG

Configure

以下を選択して、Next を押します。

cmak2.PNG

次画面の toolchain では、インストールした NDK 内のファイルを選択します。

(OpenCV内の android.toolchain.cmake は古いっぽい(?)ので使いません)

  • C:/dev/android-sdk/ndk/20.0.5594570/build/cmake/android.toolchain.cmake

設定2

Configure が完了したら、以下を追加で "+ Add Entry" して、再度 Configure を行います。

この辺りの設定はご自身の環境に合わせて ちょちょい と変えると良いと思います。

静的ライブラリを作成される方は、BUILD_SHARED_LIBS = OFF, BUILD_FAT_JAVA_LIB = ON (多分デフォルト設定)にして下さい。

Name Type Value(参考)
ANDROID_NATIVE_API_LEVEL STRING 26
ANDROID_STL STRING c++_shared
BUILD_PERF_TESTS BOOL OFF
BUILD_TESTS BOOL OFF
BUILD_ANDROID_EXAMPLES BOOL OFF
BUILD_SHARED_LIBS BOOL ON
BUILD_FAT_JAVA_LIB BOOL OFF

Configure 完了後に、各設定の赤文字がなくなっていると思いますので Generate しましょう。

OpenCV ビルド

ついにビルドします!

コンソールで buildフォルダを開いて make を実行します。

私はこの make でエラーが出て 右往左往 しました…

$ cd C:\dev\opencv\opencv-4.2.0_build
$ mingw32-make.exe
   ~~~ build ~~~
$ mingw32-make.exe install

OpenCVのビルド作業は以上です。

C:\dev\opencv\opencv-4.2.0_build\include に 共有ライブラリ (*.so) が格納されてるはずです。

Xamarinからのライブラリ利用

こちらの記事と同じ手順で確認しました。
実機:Google Pixel 3 (Android 10.0 - API 29)

Xamarin.Forms(Android) から OpenCV(C/C++) を利用する

参考

How to Build OpenCV 4.X for Native Android Development

Error compiling opencv for android #15457

Generate custom opencv .so for Android [Windows 10]

終わりに

冬休み中に完了できず足が出てしましましたのですが、何とか投稿まで進ることができ、年始に悔いを残さずに済みました。

Xamarin は 今 C# でホットな Blazor / Unity / .NET Core / UNO 等と比べて、傍から見ていて 少し熱が冷めた?落ち着いてきた? 印象を受けますが、まだまだ魅力のある環境だと思っています!

少し出遅れましたが、今年はぼちぼち学習していきたいです!

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

[xaml/WPF] フリックやピンチインアウトでControlを移動・拡大縮小する(ManipulationDeltaイベントとMatrixTransform)

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

回転/拡大縮小/移動の関連記事

やりたいこと

タッチ対応のディスプレイを使用しているときに、画面上の画像をフリック・ピンチインアウトで移動・拡大縮小させたい。

やり方

ManipulationDeltaイベントを拾って、指の動いた位置と量をとり、それをMatrixクラスにセットして色々やることで実現する。

手順

  • xamlで、移動や拡大をしたいコントロールのManipulationDeltaイベントハンドラをセットする
  • 移動や拡大をしたいコントロールのIsManipulationEnabledプロパティをTrueにする
  • コードビハインドのManipulationDeltaイベントハンドラで、引数から指の移動量・中心点情報をとって、Matrixクラスの移動・拡大のためのメソッドに渡す
  • そのMatrixをMatrixTransformに渡して、さらにMatrixTransformを移動や拡大をしたいコントロールのRenderTransformにセットする。

具体例は、下のサンプルを参照。

ManipulationDeltaについて

とれるもの

イベントハンドラの引数として渡されてくるManipulationDeltaEventArgs eに、下記のような情報が載ってくる。

取れる情報 取る引数
指の移動量(左右) e.DeltaManipulation.X
指の移動量(上下) e.DeltaManipulation.Y
2本指の中心点(左右) e.ManipulationOrigin.X
2本指の中心点(上下) e.ManipulationOrigin.Y
拡大率(左右) e.ManipulationOrigin.Scale.X
拡大率(上下) e.ManipulationOrigin.Scale.Y

上記のような値が取れる。で、

  • 移動を行うためのMatrixクラスのメソッドTranslate()は引数に移動する量をとる
    • ⇒移動する量に、指の移動量をセットする。
  • 移動を行うためのMatrixクラスのメソッドScaleAt()は引数に上下方向の拡大率左右方向の拡大率と、上下方向の拡大中心点左右方向の拡大中心点をとる
    • ⇒上下と左右の拡大中心点に、2本指の中心点をセットする
    • ⇒上下と左右の拡大率に、拡大率をセットする。

ということをすることで、指が移動した分だけ移動と拡大縮小ができる。

サンプル

MainWIndow.xaml
<Window x:Class="WpfApp39.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp39"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid ShowGridLines="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/><ColumnDefinition/><ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/><RowDefinition/><RowDefinition/>
            </Grid.RowDefinitions>

            <!-- このGridを回転させる -->
            <Grid x:Name="MyGrid" Grid.Row="1" Grid.Column="1" Background="Pink" RenderTransformOrigin="0.5,0.5"
                  IsManipulationEnabled="True"
                  ManipulationDelta="Grid_ManipulationDelta">

                <TextBlock Text="あ" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="50"/>
            </Grid>
        </Grid>
    </Grid>
</Window>

MainWIndow.xaml.cs
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApp39
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            var delta = e.DeltaManipulation;
            Matrix matrix = (MyGrid.RenderTransform as MatrixTransform).Matrix;
            matrix.Translate(delta.Translation.X, delta.Translation.Y);// 指の移動量を指定して対象を移動

            var scaleDelta = delta.Scale.X;//上下と左右に同じ量拡大するときは、Xの拡大率だけとればOK
            var orgX = e.ManipulationOrigin.X;//指の中心点(X)
            var orgY = e.ManipulationOrigin.Y;//指の中心点(Y)
            matrix.ScaleAt(scaleDelta, scaleDelta, orgX, orgY);//中心を指定して対象を拡大
            MyGrid.RenderTransform = new MatrixTransform(matrix);
        }
    }
}

注意点

  • ManipulationDeltaイベントのハンドラをセットしたときは、IsManipulationEnabledを"True"にすることを忘れない。(Trueにしないと、イベントがこない)
  • 移動や拡大をするコントロールに、HorizontalAlignment="Center"RenderTransformOrigin="0.5,0.5"などをつけると、中心点がずれて、ピンチインアウトしたときの拡大縮小の中心がずれてしまうっぽい。(matrixが、左上を基準にしている???)
  • WidthやHeightを指定しても、中心がずれるっぽい。(matrixが、素?の大きさを基準にしている???)

コード

https://github.com/tera1707/WPF-/tree/master/032_ScaleTranslateByMatrix

参考

タッチイベントを処理しよう(かずきさんブログ)
https://blog.okazuki.jp/entry/20101212/1292167120

Matrix 構造体
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.matrix?view=netframework-4.7.2

ManipulationDelta クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.input.manipulationdelta?view=netframework-4.7.2

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

【ASP.NET】マルチテナントサービスにおけるデータベースアクセスのDI

あけましておめでとうございます。

2019年は個人的にクリーンアーキテクチャの年でした。
Webアプリケーションにクリーンアーキテクチャを適用するため試行錯誤をした結果、ようやっとまとまりだしたので、自分用のメモという意味も含め、Qiitaに初記事を投下しようという試みです。
至らない点を許すことなくお付き合いいただけますと幸いです。

注意

この記事ではタイトルに書いたこと以外は極力説明を省きます。
クリーンアーキテクチャの概要や実装方法については尊敬する先達の記事を参照ください。

実装クリーンアーキテクチャ - @nrslib
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8

ソースコードで理解するクリーンアーキテクチャ - Sansan Builders Box
https://buildersbox.corp-sansan.com/entry/2019/07/10/110000

環境

フレームワーク

.Net Framework 4.6.2

DIコンテナ

Simple Injector

EntityFramework等のORMは使用していません。

なお、とあるBtoBアプリケーションのユーザ情報に関する処理という前提で説明します。

概要

前置きが長くなりましたがここから本題です。

クリーンアーキテクチャの概念を自社のサービスに取り入れるにあたって様々な困難がありました。
その中でも頭を悩ませたのがビジネスロジックとデータの永続化を分離する部分で、大きく以下の2点です。

  1. マルチテナントサービスにおけるデータベースアクセスのDI
  2. 複数のリポジトリにまたがるトランザクション

今回はこのうち、マルチテナントサービスにおけるデータベースアクセスのDIに対する実装例をご紹介します。

直面した課題

はじめ、クリーンアーキテクチャで実装をしようと思ってさくっと調べ、見よう見まねで以下のような構成をとろうとしました。
ユーザのIDでユーザ名を取得するような処理を例とします。

UserRepository.cs
using MySql.Data.MySqlClient;

public interface IUserRepository 
{
    string GetUserNameById(int id);
}

public class UserRepository: IUserRepository 
{
    public string GetUserNameById(int id) 
    {
        var name = "";

        using (var connection = new MySqlConnection(SomeConfig.ConnectionString))
        using (var command = connection.CreateCommand())
        {
            connection.Open();
            command.CommandText = "SELECT name FROM user WHERE id = @id";
            command.Parameters.Add(new MySqlParameter("@id", id));
            name = command.ExecuteScalar().ToString();
        }

        return name;
    }
}
GetUserNameUseCase.cs
public interface IGetUserNameByIdUseCase
{
    string Handle(int id);
}

public class GetUserNameByIdUseCase : IGetUserNameByIdUseCase
{
    private readonly IUserRepository _repository;

    public GetUserNameByIdUseCase(IUserRepository repository)
    {
        this._repository = repository;
    }

    public string Handle(int id)
    {
        var name = this._repository.GetUserNameById(id);
        return name;
    }
}
Global.asax
using System.Web.Mvc;
using SimpleInjector;
using SimpleInjector.Integration.Web.Mvc;

public class UserApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        //アプリケーション設定のいろいろ

        var diContainer = new Container();
        diContainer.Register<IUserRepository, UserRepository>();
        diContainer.Verify();

        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(diContainer));
    }
}

今回の問題点として、マルチテナントの構成をとっている場合、UserRepositoryが使用する接続文字列が単一ではなく、リクエスト単位で異なります。そうなるとリクエストに紐づいた接続文字列をUserRepositoryまで渡してやる必要があります。

また、毎回コネクションを開く処理を書くのも冗長です。どげんかせんといかん。

解決方法

Step1. DataAccessContextを作る

まず、データベースへのアクセスはそれ自体が一つの関心ごとになると言えます。
UserRepositoryが複数の関心ごとにとらわれないよう、UserRepositoryから接続の関心ごとを分離しましょう。

以下のような接続に関する処理をまとめたクラスを作成し、処理を委任します。

DataAccessContext.cs
using MySql.Data.MySqlClient;

public class DataAccessContext 
{
    private readonly string _connectionString;

    public DataAccessContext(string connectionString)
    {
        this._connectionString = connectionString;
    }

    public void ExecuteDataAccess(Action<MySqlConnection, MySqlCommand> action)
    {
        using (var connection = new MySqlConnection(this._connectionString))
        using (var command = connection.CreateCommand())
        {
            connection.Open();
            action(connection, command);
        }
    }
} 

Step2. リポジトリのフィールドにDataAccessContextを加える

UserRepository.cs
using MySql.Data.MySqlClient;

public class UserRepository: IUserRepository 
{
    private readonly DataAccessContext _context;

    public UserRepository(DataAccessContext context) 
    {
        this._context = context;
    }

    public string GetUserNameById(int id) 
    {
        var name = "";

        this._context.ExecuteDataAccess((connection, command) => 
        {
            command.CommandText = $"SELECT name FROM user WHERE id = @id";
            command.Parameters.Add(new MySqlParameter("@id", id));
            name = command.ExecuteScalar().ToString();
        });

        return name;
    }
}

すっきりしました。

Step3. Global.asaxで、DataAccessContextをInject

今回はマルチテナントの想定なので、リクエストに基づいた接続文字列をDataAccessContextに渡す必要があります。
ログイン時にセッションへ接続文字列を保存してある想定とすると、現在のセッション情報をもとにオブジェクトを生成することができます。

Global.asax
using System.Web.Mvc;
using SimpleInjector;
using SimpleInjector.Integration.Web.Mvc;

public class UserApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        //アプリケーション設定のいろいろ

        var diContainer = new Container();
        diContainer.Register(typeof(DataAccessContext), () =>
        {
            var connectionString = HttpContext.Current.Session["connectionString"]?.ToString();
            if (connectionString != null)
            {
                return new DataAccessContext(connectionString);
            } 
            else 
            {
                return (DataAccessContext)null;
            }
        });

        diContainer.Register<IUserRepository, UserRepository>();
        diContainer.Verify();

        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(diContainer));
    }
}

以上でマルチテナントサービスでもデータベースアクセスをDIすることができました。

振り返り

振り返ってみると解決法自体はシンプルですね。
今回主につまづいたのはStep3に関する部分であり、なぜつまづいたかをまとめてみました。

  1. Simple Injectorのオブジェクト生成タイミングが理解できていなかった
  2. HttpContext.Currentを忘れてた

どちらにも共通して言えることは、使用するフレームワークに対する知識の浅さが要因ということです。
採用した技術に関して理解を深めるというのは技術を使用する立場としての当然の心得ですが、それを強烈に認識できたことはいい経験になったなぁと思います。

あと、今回書いたコードはQiita用に雑にこしらえたものでありそのままコピペとかでは動かない恐れがあります。ごめんなさい。
余裕があればトランザクションも含めたソースコードも公開したいなと…思って…います…。

次回

次回は複数のリポジトリにまたがったトランザクションについて書きます。

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

【新年】すべてのプログラマの皆様へ新年のご挨拶【ちょっとした小ネタ】

ただの小ネタです。今年はまだ簡単です。
手元で、さくっと動作確認できた言語だけになっています。
ちなみに、BOMは入っていません。

C#とか

byte[] utf8Bytes = { 0xe4, 0xbb, 0x8a, 0xe5, 0xb9, 0xb4, 0xe3, 0x82, 0x82, 0x31, 0xe5, 0xb9, 0xb4, 0xe3, 0x81, 0x8a, 0xe4, 0xba, 0x92, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0xab, 0xe3, 0x80, 0x81, 0xe6, 0x99, 0x82, 0xe3, 0x81, 0xab, 0xe3, 0x81, 0xaf, 0xe8, 0x8b, 0xa6, 0xe3, 0x81, 0x97, 0xe3, 0x81, 0xbf, 0xe3, 0x81, 0xaa, 0xe3, 0x81, 0x8c, 0xe3, 0x82, 0x89, 0xe3, 0x82, 0x82, 0xe3, 0x80, 0x81, 0xe6, 0xa5, 0xbd, 0xe3, 0x81, 0x97, 0xe3, 0x82, 0x93, 0xe3, 0x81, 0xa7, 0xe3, 0x82, 0xb3, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x87, 0xe3, 0x82, 0xa3, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb0, 0xe3, 0x81, 0x97, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0x97, 0xe3, 0x82, 0x87, 0xe3, 0x81, 0x86, 0xef, 0xbc, 0x81 };

Javaとか

byte[] utf8Bytes = { (byte)0xe4, (byte)0xbb, (byte)0x8a, (byte)0xe5, (byte)0xb9, (byte)0xb4, (byte)0xe3, (byte)0x82, (byte)0x82, (byte)0x31, (byte)0xe5, (byte)0xb9, (byte)0xb4, (byte)0xe3, (byte)0x81, (byte)0x8a, (byte)0xe4, (byte)0xba, (byte)0x92, (byte)0xe3, (byte)0x81, (byte)0x84, (byte)0xe3, (byte)0x81, (byte)0xab, (byte)0xe3, (byte)0x80, (byte)0x81, (byte)0xe6, (byte)0x99, (byte)0x82, (byte)0xe3, (byte)0x81, (byte)0xab, (byte)0xe3, (byte)0x81, (byte)0xaf, (byte)0xe8, (byte)0x8b, (byte)0xa6, (byte)0xe3, (byte)0x81, (byte)0x97, (byte)0xe3, (byte)0x81, (byte)0xbf, (byte)0xe3, (byte)0x81, (byte)0xaa, (byte)0xe3, (byte)0x81, (byte)0x8c, (byte)0xe3, (byte)0x82, (byte)0x89, (byte)0xe3, (byte)0x82, (byte)0x82, (byte)0xe3, (byte)0x80, (byte)0x81, (byte)0xe6, (byte)0xa5, (byte)0xbd, (byte)0xe3, (byte)0x81, (byte)0x97, (byte)0xe3, (byte)0x82, (byte)0x93, (byte)0xe3, (byte)0x81, (byte)0xa7, (byte)0xe3, (byte)0x82, (byte)0xb3, (byte)0xe3, (byte)0x83, (byte)0xbc, (byte)0xe3, (byte)0x83, (byte)0x87, (byte)0xe3, (byte)0x82, (byte)0xa3, (byte)0xe3, (byte)0x83, (byte)0xb3, (byte)0xe3, (byte)0x82, (byte)0xb0, (byte)0xe3, (byte)0x81, (byte)0x97, (byte)0xe3, (byte)0x81, (byte)0xbe, (byte)0xe3, (byte)0x81, (byte)0x97, (byte)0xe3, (byte)0x82, (byte)0x87, (byte)0xe3, (byte)0x81, (byte)0x86, (byte)0xef, (byte)0xbc, (byte)0x81 };

C/C++とか

const char utf8Bytes[] = { (char)0xe4, (char)0xbb, (char)0x8a, (char)0xe5, (char)0xb9, (char)0xb4, (char)0xe3, (char)0x82, (char)0x82, (char)0x31, (char)0xe5, (char)0xb9, (char)0xb4, (char)0xe3, (char)0x81, (char)0x8a, (char)0xe4, (char)0xba, (char)0x92, (char)0xe3, (char)0x81, (char)0x84, (char)0xe3, (char)0x81, (char)0xab, (char)0xe3, (char)0x80, (char)0x81, (char)0xe6, (char)0x99, (char)0x82, (char)0xe3, (char)0x81, (char)0xab, (char)0xe3, (char)0x81, (char)0xaf, (char)0xe8, (char)0x8b, (char)0xa6, (char)0xe3, (char)0x81, (char)0x97, (char)0xe3, (char)0x81, (char)0xbf, (char)0xe3, (char)0x81, (char)0xaa, (char)0xe3, (char)0x81, (char)0x8c, (char)0xe3, (char)0x82, (char)0x89, (char)0xe3, (char)0x82, (char)0x82, (char)0xe3, (char)0x80, (char)0x81, (char)0xe6, (char)0xa5, (char)0xbd, (char)0xe3, (char)0x81, (char)0x97, (char)0xe3, (char)0x82, (char)0x93, (char)0xe3, (char)0x81, (char)0xa7, (char)0xe3, (char)0x82, (char)0xb3, (char)0xe3, (char)0x83, (char)0xbc, (char)0xe3, (char)0x83, (char)0x87, (char)0xe3, (char)0x82, (char)0xa3, (char)0xe3, (char)0x83, (char)0xb3, (char)0xe3, (char)0x82, (char)0xb0, (char)0xe3, (char)0x81, (char)0x97, (char)0xe3, (char)0x81, (char)0xbe, (char)0xe3, (char)0x81, (char)0x97, (char)0xe3, (char)0x82, (char)0x87, (char)0xe3, (char)0x81, (char)0x86, (char)0xef, (char)0xbc, (char)0x81 };

Pythonとか

utf8_bytes = [ 0xe4, 0xbb, 0x8a, 0xe5, 0xb9, 0xb4, 0xe3, 0x82, 0x82, 0x31, 0xe5, 0xb9, 0xb4, 0xe3, 0x81, 0x8a, 0xe4, 0xba, 0x92, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0xab, 0xe3, 0x80, 0x81, 0xe6, 0x99, 0x82, 0xe3, 0x81, 0xab, 0xe3, 0x81, 0xaf, 0xe8, 0x8b, 0xa6, 0xe3, 0x81, 0x97, 0xe3, 0x81, 0xbf, 0xe3, 0x81, 0xaa, 0xe3, 0x81, 0x8c, 0xe3, 0x82, 0x89, 0xe3, 0x82, 0x82, 0xe3, 0x80, 0x81, 0xe6, 0xa5, 0xbd, 0xe3, 0x81, 0x97, 0xe3, 0x82, 0x93, 0xe3, 0x81, 0xa7, 0xe3, 0x82, 0xb3, 0xe3, 0x83, 0xbc, 0xe3, 0x83, 0x87, 0xe3, 0x82, 0xa3, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb0, 0xe3, 0x81, 0x97, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0x97, 0xe3, 0x82, 0x87, 0xe3, 0x81, 0x86, 0xef, 0xbc, 0x81 ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#,C++ アプリケーション間で共有メモリに構造体を読み書きしてみる

前置き

当記事は、前回の記事「C++アプリケーションとC#アプリケーションで値をやり取りしてみる」 の続編です。

前回の記事では、動的に確保した共有メモリで直接値を読み書きしましたが、
そのままではかなり不便です。

そこで今回は共有メモリに構造体を書き込み、そこから読み書きしてみたいと思います。

Untitled Diagram (4).png

実践 - C#側

まずは、C# .NET Framework WinForm 側で簡易的なフォームを作成し、単体で実践してみます。
ソースコードは以下の通りです。

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace QuiitaShareMemoryStruct
{
    public partial class Form1 : Form
    {
        ////////////////////////////////////////////////////////////////////////
        // 必要なものを定義
        ////////////////////////////////////////////////////////////////////////
        MemoryMappedFile share_mem = null;
        MemoryMappedViewAccessor accessor = null;

        ////////////////////////////////////////////////////////////////////////
        // 共有メモリ名
        ////////////////////////////////////////////////////////////////////////
        const string sharedMemoryName = "MySharedMemory";

        ////////////////////////////////////////////////////////////////////////
        // 使用する構造体
        ////////////////////////////////////////////////////////////////////////
        public struct _MY_DATA_STRUCT
        {
            public int myInt32;
            public float myFloat;
            public bool myBool;
        }

        ////////////////////////////////////////////////////////////////////////
        // コンストラクタ
        ////////////////////////////////////////////////////////////////////////
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        ////////////////////////////////////////////////////////////////////////
        // 「共有メモリを読む」ボタンをクリックしたときに発生するイベント
        ////////////////////////////////////////////////////////////////////////
        private void btnRead_Click(object sender, EventArgs e)
        {
            var result = ReadSharedMemoryAsStruct<_MY_DATA_STRUCT>(sharedMemoryName);
            MessageBox.Show(string.Format("MyInt32: {0}\nMyFloat: {1}\nMyBool: {2}", result.myInt32, result.myFloat, result.myBool));
        }

        ////////////////////////////////////////////////////////////////////////
        // 「共有メモリに書き込み」ボタンをクリックしたときに発生するイベント
        ////////////////////////////////////////////////////////////////////////
        private void btnWrite_Click(object sender, EventArgs e)
        {
            _MY_DATA_STRUCT myStruct = new _MY_DATA_STRUCT();
            myStruct.myInt32 = (int)numericUpDown1.Value;
            float res;
            myStruct.myFloat = float.TryParse(textBox1.Text, out res) ? res : 999.0f;
            myStruct.myBool = checkBox1.Checked;
            if (!WriteSharedMemoryAsStruct<_MY_DATA_STRUCT>(sharedMemoryName, myStruct, true))
            {
                MessageBox.Show("書き込みに失敗しました。");
            }
            else
            {
                MessageBox.Show("書き込みに成功しました。");
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // 共有メモリを構造体として読み取るジェネリック関数
        ////////////////////////////////////////////////////////////////////////
        private T ReadSharedMemoryAsStruct<T>(string sharedMemoryName, bool createOrOpen = false)
        where T : struct
        {
            //構造体を定義
            T result = new T();

            try
            {
                if (share_mem == null)
                {
                    if(createOrOpen)
                    {
                        share_mem = MemoryMappedFile.CreateOrOpen(sharedMemoryName, 1024 * 10);
                    }
                    else
                    {
                        share_mem = MemoryMappedFile.OpenExisting(sharedMemoryName);
                    }
                }

                if (accessor == null)
                {
                    accessor = share_mem.CreateViewAccessor();
                }

                accessor?.Read(0, out result);
                return result;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("共有メモリが見つかりませんでした");
                return result;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return result;
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // 共有メモリに構造体として書き込むジェネリック関数
        ////////////////////////////////////////////////////////////////////////
        private bool WriteSharedMemoryAsStruct<T>(string sharedMemoryName, T targetStruct, bool createOrOpen = false)
        where T : struct
        {
            try
            {
                if (createOrOpen)
                {
                    share_mem = MemoryMappedFile.CreateOrOpen(sharedMemoryName, 1024 * 10);
                }
                else
                {
                    share_mem = MemoryMappedFile.OpenExisting(sharedMemoryName);
                }

                if (accessor == null)
                {
                    accessor = share_mem.CreateViewAccessor();
                }
                accessor.Write(0, ref targetStruct);

                return true;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("共有メモリが見つかりませんでした");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // リソースの破棄
        ////////////////////////////////////////////////////////////////////////
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            accessor?.Dispose();
            share_mem?.Dispose();
        }
    }
}

解説

ReadSharedMemoryAsStruct() 関数

こちらは、該当共有メモリに対して構造体として読み取る関数です。

まず初めに、関数の結果として出力する構造体メンバ変数を定義します。
今回はジェネリック関数として定義しましたので、ここでいうTは構造体であるということが保証されています。

where T : struct

T result = new T();

次に、MemoryMappedFileMemoryMappedViewAccessornullであれば新規生成し、
アクセサを使用して該当共有メモリに対して書き込みを行います。
第一引数はpositionつまりオフセットです。
今回は構造体のみを書き込むので、0とします。
第二引数で構造体を受け取ります。

accessor.Read(0, out res);

使用例:

var result = ReadSharedMemoryAsStruct<_YOUR_STRUCT>("yourSharedMemoryName");

WriteSharedMemoryAsStruct() 関数

こちらは、該当共有メモリに対して構造体として書き込む関数です。

まず初めに、読み取りと同じようにヌルチェックを行います。

(省略)

アクセサを使用して該当共有メモリに対して書き込みを行います。
第一引数はpositionつまりオフセットです。
今回は構造体のみを書き込むので、0とします。
第二引数には、関数で受け取った構造体T targetStructの参照を渡します。

accessor.Write(0, ref targetStruct);

リソースの開放

使い終わったリソースはきちんと解放してあげます。

accessor?.Dispose();
share_mem?.Dispose();

実行結果

fはパースしてくれないらしく、float.TryParse()に失敗して999が書き込まれちゃってますが、
無事成功しました。

eqhsn-z2z96.gif

実践 - C++側

C++側でも単体で実行してみます。

main.cpp
#include <iostream>
#include <Windows.h>

#define CONSOLE_PAUSE() system("pause")
#define VOID void
#define BOOLEAN bool

////////////////////////////////////////////////////////////////////////
// 構造体
////////////////////////////////////////////////////////////////////////
typedef struct _MY_DATA_STRUCT
{
    int myInt32;
    float myFloat;
    bool myBool;
}MY_DATA_STRUCT;

////////////////////////////////////////////////////////////////////////
// 必要なものを定義
////////////////////////////////////////////////////////////////////////
constexpr auto SHARED_MEMORY_NAME = L"MySharedMemory";
constexpr auto SHARED_MEMORY_SIZE = 1024 * 10;
static HANDLE hSharedMemory = NULL;
MY_DATA_STRUCT* response;

////////////////////////////////////////////////////////////////////////
// 共有メモリを作成する関数
////////////////////////////////////////////////////////////////////////
BOOLEAN CreateSharedMemory(const wchar_t* sharedMemoryName, DWORD size)
{
    if (hSharedMemory)
    {
        return FALSE;
    }

    hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, sharedMemoryName);
    if (!hSharedMemory || hSharedMemory == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// ハンドルの初期化
////////////////////////////////////////////////////////////////////////
BOOLEAN InitializeSharedMemory()
{
    hSharedMemory = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEMORY_NAME);

    if (!hSharedMemory || hSharedMemory == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// ハンドルの破棄
////////////////////////////////////////////////////////////////////////
VOID UnInitializeSharedMemory()
{
    if (hSharedMemory || hSharedMemory != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hSharedMemory);
    }
}

////////////////////////////////////////////////////////////////////////
// 共有メモリの読み取り
////////////////////////////////////////////////////////////////////////
BOOLEAN ReadSharedMemory()
{
    if (!hSharedMemory)
    {
        return FALSE;
    }

    response = (MY_DATA_STRUCT*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, SHARED_MEMORY_SIZE);
    if (response == NULL || !response)
    {
        return FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// 共有メモリの書き込み
////////////////////////////////////////////////////////////////////////
BOOLEAN WriteSharedMemory(_MY_DATA_STRUCT writeit)
{
    if (!response || !hSharedMemory)
    {
        return FALSE;
    }

    *response = writeit;

    return TRUE;
}

int main()
{
    SetConsoleTitleA("Title");

    std::cout << "Hello SharedMemory!\nなにかキーを押してください。";
    std::cin.get();

    if (!CreateSharedMemory(SHARED_MEMORY_NAME, SHARED_MEMORY_SIZE))
    {
        std::cout << "共有メモリの作成に失敗しました。\n";
        goto FINISH;
    }

    if (!InitializeSharedMemory())
    {
        std::cout << "共有メモリの初期化に失敗しました。\n";
        goto FINISH;
    }

    if (!ReadSharedMemory())
    {
        std::cout << "共有メモリの読み取りに失敗しました。\n";
        goto FINISH;
    }

    _MY_DATA_STRUCT s;
    s.myInt32 = 333;
    s.myFloat = 634.f;
    s.myBool = TRUE;

    if (!WriteSharedMemory(s))
    {
        std::cout << "共有メモリの書き込みに失敗しました。\n";
        UnInitializeSharedMemory();
        CONSOLE_PAUSE();
    }

    printf("=== Read ===\nmyInt32: %u\nmyFloat: %f\nmyBool: %u\n", response->myInt32, response->myFloat, response->myBool);

    FINISH:
    UnInitializeSharedMemory();
    CONSOLE_PAUSE();
}

解説

CreateSharedMemory() 関数

CreateFileMapping()でハンドルを生成します。

HANDLE hSharedMemory = 
CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, sharedMemoryName);

InitializeSharedMemory() 関数

OpenFileMapping()でハンドルを開きます。

HANDLE hSharedMemory =
OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEMORY_NAME);

UnInitializeSharedMemory() 関数

リソースの開放です。

CloseHandle(hSharedMemory);

ReadSharedMemory() 関数

共有メモリから構造体を読み取ります。

auto response =
(MY_DATA_STRUCT*)MapViewOfFile
(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, SHARED_MEMORY_SIZE);

WriteSharedMemory() 関数

共有メモリに構造体を書き込みます。

_MY_DATA_STRUCT writeit;
*response = writeit;

実行結果

_MY_DATA_STRUCT s;
s.myInt32 = 333;
s.myFloat = 634.f;
s.myBool = TRUE;

WriteSharedMemory(s);

printf("=== Read ===\nmyInt32: %u\nmyFloat: %f\nmyBool: %u\n", 
response->myInt32, response->myFloat, response->myBool);

無事成功しました。

image.png

C#とC++クロスでやってみる

こちらも問題なく動作しました。

image.png

最後に

最後までご覧いただきありがとうございました。
コードミス/誤字・脱字や間違った情報がございましたら、お手数ですがコメント欄にてご指摘いただけますと幸いです。

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