- 投稿日:2020-01-07T23:39:08+09:00
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) を使い、以下の設定でインストールしました。
インストールが完了したら 環境変数 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 ボタンを押します。
Configure
以下を選択して、Next を押します。
次画面の 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 installOpenCVのビルド作業は以上です。
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 等と比べて、傍から見ていて 少し熱が冷めた?落ち着いてきた? 印象を受けますが、まだまだ魅力のある環境だと思っています!
少し出遅れましたが、今年はぼちぼち学習していきたいです!
- 投稿日:2020-01-07T21:42:28+09:00
[xaml/WPF] フリックやピンチインアウトでControlを移動・拡大縮小する(ManipulationDeltaイベントとMatrixTransform)
もくじ
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f回転/拡大縮小/移動の関連記事
- 画面の要素を回転/拡大縮小/移動する(RenderTransform)
- 画面の要素を回転/拡大縮小/移動する(RenderTransformにMatrixTransform)
- フリックやピンチインアウトでControlを移動・拡大縮小する(ManipulationDeltaイベントとMatrixTransform)
- [xaml/C#] 枠の中で画像などを拡大縮小する(マウスと指で移動/拡大)
やりたいこと
タッチ対応のディスプレイを使用しているときに、画面上の画像をフリック・ピンチインアウトで移動・拡大縮小させたい。
やり方
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.csusing 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/1292167120Matrix 構造体
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.matrix?view=netframework-4.7.2ManipulationDelta クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.input.manipulationdelta?view=netframework-4.7.2
- 投稿日:2020-01-07T21:40:40+09:00
【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コンテナ
EntityFramework等のORMは使用していません。
なお、とあるBtoBアプリケーションのユーザ情報に関する処理という前提で説明します。
概要
前置きが長くなりましたがここから本題です。
クリーンアーキテクチャの概念を自社のサービスに取り入れるにあたって様々な困難がありました。
その中でも頭を悩ませたのがビジネスロジックとデータの永続化を分離する部分で、大きく以下の2点です。
- マルチテナントサービスにおけるデータベースアクセスのDI
- 複数のリポジトリにまたがるトランザクション
今回はこのうち、マルチテナントサービスにおけるデータベースアクセスのDIに対する実装例をご紹介します。
直面した課題
はじめ、クリーンアーキテクチャで実装をしようと思ってさくっと調べ、見よう見まねで以下のような構成をとろうとしました。
ユーザのIDでユーザ名を取得するような処理を例とします。UserRepository.csusing 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.cspublic 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.asaxusing 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.csusing 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.csusing 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.asaxusing 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に関する部分であり、なぜつまづいたかをまとめてみました。
- Simple Injectorのオブジェクト生成タイミングが理解できていなかった
- HttpContext.Currentを忘れてた
どちらにも共通して言えることは、使用するフレームワークに対する知識の浅さが要因ということです。
採用した技術に関して理解を深めるというのは技術を使用する立場としての当然の心得ですが、それを強烈に認識できたことはいい経験になったなぁと思います。あと、今回書いたコードはQiita用に
雑にこしらえたものでありそのままコピペとかでは動かない恐れがあります。ごめんなさい。
余裕があればトランザクションも含めたソースコードも公開したいなと…思って…います…。次回
次回は複数のリポジトリにまたがったトランザクションについて書きます。
- 投稿日:2020-01-07T18:25:00+09:00
【新年】すべてのプログラマの皆様へ新年のご挨拶【ちょっとした小ネタ】
ただの小ネタです。今年はまだ簡単です。
手元で、さくっと動作確認できた言語だけになっています。
ちなみに、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 ]
- 投稿日:2020-01-07T05:54:26+09:00
C#,C++ アプリケーション間で共有メモリに構造体を読み書きしてみる
前置き
当記事は、前回の記事「C++アプリケーションとC#アプリケーションで値をやり取りしてみる」 の続編です。
前回の記事では、動的に確保した共有メモリで直接値を読み書きしましたが、
そのままではかなり不便です。そこで今回は共有メモリに構造体を書き込み、そこから読み書きしてみたいと思います。
実践 - C#側
まずは、C# .NET Framework WinForm 側で簡易的なフォームを作成し、単体で実践してみます。
ソースコードは以下の通りです。Form1.csusing 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();次に、
MemoryMappedFileとMemoryMappedViewAccessorがnullであれば新規生成し、
アクセサを使用して該当共有メモリに対して書き込みを行います。
第一引数は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が書き込まれちゃってますが、
無事成功しました。実践 - 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);無事成功しました。
C#とC++クロスでやってみる
こちらも問題なく動作しました。
最後に
最後までご覧いただきありがとうございました。
コードミス/誤字・脱字や間違った情報がございましたら、お手数ですがコメント欄にてご指摘いただけますと幸いです。






