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

ASP.NET Core のWebアプリケーションを Kubernetes で構築する

概要

(※) ASP.NET Core のWebアプリケーションを docker-compose で Dockerアプリケーション として構築する
の続編になります。

ASP.NET CoreのサンプルWebアプリケーションを題材として、Kubernetes の動作環境を構築する例を紹介します。

筆者は単なるアプリ屋ですが、「そろそろ Kubernetes に入門しておかないとヤバイかな…」と思い、
Kubernetes初心者ながら、本記事を書くに至りました。

Kubernetesの概念やリソース等は、ドキュメントや文献を参考にしつつ記載していますが、
間違いや不備があれば、ご指摘いただけると助かります。

環境

  • Windows10
  • Visual Studio 2019
  • ASP.NET Core 3.1
  • Docker for Windows

サンプルアプリケーション

題材にしたサンプルアプリケーションです。
https://github.com/tYoshiyuki/dotnet-core-web-sample

docker-composeの際に利用したToDoのWebアプリケーションを今回も利用します。
トランザクションデータはデータベース (SQL Server) に保存しています。

システム構成

Docker for Windows を利用して、ローカルPC上にKubernetes環境を構築します。
ローカル開発時は、IIS Express と LocalDB で構成していました。これを、下記図のイメージで構築します。

image.png

「Nginx + ASP.NET Coreアプリケーション」 と 「SQL Server Express」 で2つのPodを作成します。

Podとは、Kubernetesにおけるコンテナ集合体の単位になります。
コンテナ単体では扱い辛いため、利用したいコンテナをグルーピングして利用するイメージになります。

次に、「Web + アプリケーション部分を担当する dotnet-core-web-sample」 と
「DB部分を担当する sqlexpress」 の2つのServiceを定義します。

Serviceは、Kubernetesクラスタ内において、Podに対してのアクセス経路を定義するリソースになります。

Kubernetesにおいて、コンテナが実際に動作する環境を Node と言います。
PodはNodeに対して分散配置されるため、Pod間で通信を行うことを考えた場合に、
Podの実IPアドレスを意識したアクセスを行うことは困難です。

そのため、Podに対するアクセスを抽象化して提供する仕組みが Service ということになります、

また、外部からのHTTPアクセス用に Ingress を導入します。
Ingressは、ServiceをL7層のレベルで外部に公開するためのリソースです。
今回は Nginx Ingress Controller を利用します。

解説

Docker for Windows の設定から、Kubernetesを有効化します。
image.png

またGUIツールとして、Visual Studio Codeの拡張機能を利用しました。
Kubernetesのリソースを視覚的に確認できるため、導入しておくと便利です。
image.png

1. app.yaml

まずは、Nginx、ASP.NET Coreアプリケーションの部分の定義を作成します。
Kubernetesのリソースは、YAMLで定義していきます。

app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnet-core-web-sample
  labels:
    app: dotnet-core-web-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dotnet-core-web-sample
  template:
    metadata:
      labels:
        app: dotnet-core-web-sample
    spec:
      containers:
      - name: web
        image: dotnet-core-web-sample_web
        imagePullPolicy: IfNotPresent
        env:
        - name: BACKEND_HOST
          value: localhost:5000  
        ports:
        - containerPort: 80
      - name: app
        image: dotnet-core-web-sample_app
        imagePullPolicy: IfNotPresent
        env:
        - name: ConnectionStrings__DefaultConnection
          value: "Server=sqlexpress;Database=master;User ID=sa;Password=P@ssw0rd;initial catalog=dotnetcorewebsample;MultipleActiveResultSets=True;App=EntityFramework;"
        ports:
        - containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
  name: dotnet-core-web-sample
spec:
  selector:
    app: dotnet-core-web-sample
  ports:
  - name: http
    port: 80

前半部分はDeployment、後半部分はServiceの定義になります。

Deploymentは、Deployment > ReplicaSet > Pod というように、Podを管理するための上位概念になります。
ReplicaSetは、同じ仕様のPodを複数生成・管理するためのリソースになります。
Deploymentは、ReplicaSetを世代管理するためのリソースです。
本記事では触れませんが、デプロイ時のロールバックやRollingUpdateの機能を定義することが出来ます。

Podに配置するコンテナの定義は containers: の部分で記載しています。
Podで利用するdockerイメージは、前回の記事で作成したローカルのイメージを利用します。
imagePullPolicy: IfNotPresentを記載することで、ローカルイメージが存在する場合に、それを利用するようにしています。
dotnet-core-web-sample_webはNginxのdockerイメージ、dotnet-core-web-sample_appはASP.NET Coreのdockerイメージです。

各設定内容は docker-compose とほぼ同じですが、BACKEND_HOST に localhost:5000 を設定します。
これは、同一Podにおける宛先を localhost で解決出来るためです。

Serviceでは、dotnet-core-web-sampleという名称で、80番ポートを公開しています。

2. sqlexpress.yaml

次に、SQL Server Expressの定義です。

sqlexpress.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sqlexpress
  labels:
    app: sqlexpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sqlexpress
  template:
    metadata:
      labels:
        app: sqlexpress
    spec:
      containers:
      - name: sqlexpress
        image: dotnet-core-web-sample_sqlexpress
        imagePullPolicy: IfNotPresent        
        env:
        - name: ACCEPT_EULA
          value: "Y"
        - name: MSSQL_PID
          value: Express
        - name: SA_PASSWORD
          value: P@ssw0rd
        ports:
        - containerPort: 1433
---
apiVersion: v1
kind: Service
metadata:
  name: sqlexpress
spec:
  selector:
    app: sqlexpress
  ports:
  - name: "1433"
    port: 1433
    targetPort: 1433

前半部分はDeployment、後半部分はServiceの定義になります。
構成内容としては、app.yamlとほぼ同様になっています。
Serviceでは、sqlexpressという名称で、SQL Server用の1433番ポートを公開しています。

3. Nginx Ingress Controllerの導入

Ingressを使用するために、Nginx Ingress Controllerを導入します。

Githubのドキュメントを参考にコマンドを実行します。
https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml

ドキュメントには Docker for Mac と書かれていますが、Docker for Windowsでも大丈夫でした。

4. ingress.yaml

最後に、ingress.yamlを作成します。

ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dotnet-core-web-sample
spec:
  rules:
  - host: dotnetcorewebsample.local
    http:
      paths:
      - path: /
        backend:
          serviceName: dotnet-core-web-sample
          servicePort: 80

host: dotnetcorewebsample.localは、バーチャルホスト名になります。
path: /は、httpアクセス時のパス情報になります。

5. 動作確認

これまで作成してきた定義を、Kubernetesに適用させます。
kubectlコマンドを利用します。

> kubectl apply -f .\sqlexpress.yaml
deployment.apps/sqlexpress created
service/sqlexpress created

> kubectl apply -f .\app.yaml       
deployment.apps/dotnet-core-web-sample created
service/dotnet-core-web-sample created

> kubectl apply -f .\ingress.yaml
ingress.extensions/dotnet-core-web-sample created

稼働確認のため、hostsファイルに下記を記載します。

127.0.0.1 dotnetcorewebsample.local

ブラウザでアクセスすると、無事アクセスすることが出来ました。

image.png

まとめ

Kubernetesの概念は当初複雑に感じていましたが、実際に触ってみると理解が進み易いと感じました。
また、コンテナ技術を扱っている関係上、docker や docker-compose に慣れておくと入門し易いと思います。

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

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

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

はじめに

OpenCV公式が公開している Android用ライブラリを利用して、Xamarin.Forms から C/C++ で OpenCV を操作する方法をまとめました。(Xamarin.Forms ですが iOS については何も書いていません)

今回は動作確認用のサンプルとして、単色の黒/白 2つのMat画像を結合して平均輝度値を求めています。

以降は arm64-v8a アーキテクチャに絞って書いていますので、他の環境の方は適宜読み替えてください。

確認環境

  • OpenCV 4.2.0
  • Windows 10
  • Visual Studio Community 2019 16.4.2
  • Xamarin.Forms 4.4.0.991265
  • Google Pixel 3 (Android 10.0 - API 29)

セットアップ

特に変わったことはしていません。

VisualStudioプロジェクト作成 (折りたたみ)

Xamarin.Formsプロジェクト

モバイルアプリ(Xamarin.Forms) から、"XamaFormsOfficialCv" として作成しました。

xamarin_project.png

NativeLibraryプロジェクト

ダイナミック共有ライブラリ(Android) から、"NativeOpenCv" として作成しました。

native_project.png

先に作成したXamaFormsOfficialCv.Android プロジェクトの [参照の追加] より NativeOpenCv を参照します。

この時点で一旦、動作確認しておくと安全です。

OpenCVライブラリの取得

OpenCVの公式 から取得して、良い感じの場所に展開しておきます。

今回は "C:\opencv\OpenCV-android-sdk" に置きました。

opencv_release.png

自作ライブラリの対応

STL設定

デフォルトでは [LLVM libc++ スタティックライブラリ(c++_static)] になっていたので、[共有ライブラリ (c++_shared)] に変えました。

lib_setting_stl.png

OpenCVインクルード

展開したOpenCVのヘッダフォルダをインクルードします。

C:\opencv\OpenCV-android-sdk\sdk\native\jni\include

include_opencv.png

OpenCVライブラリ

展開したOpenCVの各ライブラリフォルダをインクルードします。

C:\opencv\OpenCV-android-sdk\sdk\native\libs\arm64-v8a
C:\opencv\OpenCV-android-sdk\sdk\native\3rdparty\libs\arm64-v8a
C:\opencv\OpenCV-android-sdk\sdk\native\staticlibs\arm64-v8a

add_opencv.png

OpenCVライブラリ名

上記ディレクトリ内の各ライブラリ名を指定します。今回は全てのライブラリを列挙してみました。

ライブラリ名には、libXXXX.a の XXXX の部分のみを記述するようにして下さい。

ちなみに libXXXX.a とフルで書くと、LINK で "cannot find" になります。VSが気を利かせてくれればハマらずに済んだのですが…

opencv_java4
cpufeatures
IlmImf
ittnotify
libjasper
libjpeg-turbo
libpng
libprotobuf
libtiff
libwebp
quirc
tbb
tegra_hal
opencv_calib3d
opencv_core
opencv_dnn
opencv_features2d
opencv_flann
opencv_highgui
opencv_imgcodecs
opencv_imgproc
opencv_ml
opencv_objdetect
opencv_photo
opencv_stitching
opencv_video
opencv_videoio

add_opencv_lib.png

ソースコード(C++)

デフォルトで作成される [プロジェクト名.h] と [プロジェクト名.cpp] の中身は削除して、以下を追加しました。

サンプルなので、ややこしいことはしていません。

// NativeOpenCv.cpp
#include "NativeOpenCv.h"
#include <opencv2/core.hpp>
#define DllExport extern "C"

DllExport double GetMatMeanY(int black_length, int white_length) {
    int row = 100;

    // 1. 引数で指定された割合で、単色の黒/白 Mat を作成
    cv::Mat brack = cv::Mat::zeros(row, black_length, CV_8UC1);
    cv::Mat white(row, white_length, CV_8UC1, cv::Scalar(255, 255, 255));

    // 2. サイドバイサイドで 2つのMat を結合
    cv::Mat merge;
    hconcat(brack, white, merge);

    // 3. 結合したMatの平均輝度値を求める(単色なので輝度(Y)と呼べない気もする)
    return cv::mean(merge)[0];
}

自作ライブラリの対応は以上です。

Xamarin(Android)の対応

OpenCVライブラリ

Android側のプロジェクトに libs\arm64-v8a フォルダを作成して、OpenCVの共有ライブラリ(*.so) を追加します。(今回は1つだけでした)

  C:\opencv\OpenCV-android-sdk\sdk\native\libs\arm64-v8a\*.so

lib_path.png

追加したライブラリ(*.so) のプロパティを変更します。

  • ビルドアクション:AndroidNativeLirary
  • 出力ディレクトリにコピー:新しい場合はコピーする

lib_setting.png

ソースコード(C#)

とりあえず動作を見るだけなので、デフォルトで作成される MainActivity.cs に自作ライブラリの呼び出し処理(P/Invoke)を追加しました。

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    // ◆追加:ここから1
    [System.Runtime.InteropServices.DllImport("NativeOpenCv")]
    private static extern double GetMatMeanY(int black_length, int white_length);
    // ◆追加:ここまで1

    protected override void OnCreate(Bundle savedInstanceState)
    {
        ~~割愛~~

        // ◆追加:ここから2
        // 黒画像と白画像の割合を指定して、平均輝度値を求める
        var y0 = GetMatMeanY(1, 1);     // 255 * 1/2 = 127.5
        var y1 = GetMatMeanY(2, 1);     // 255 * 1/3 =  85.0
        var y2 = GetMatMeanY(200, 300); // 255 * 3/5 = 153.0
        // ◆追加:ここまで2
    }
    ~~割愛~~
}

対応は以上です。

実行しても何も分かりませんが、ブレークポイントを仕掛ければコメント通りの輝度値が取得できるはずです。

サンプル

今回の紹介に使用したリポジトリは以下で公開しています。

https://github.com/hsytkm/XamaFormsOfficialCv

参考

OpenCV Android

Android C++ ライブラリ サポート

Android プロジェクトへの C / C++ コードの追加

終わりに

2019年冬休みの自分への課題を記事にまとめました。

本当はOpenCVのソースをセルフビルドして利用したかったのですが、CMakeやらLinkやらのエラーが取れないまま休みが終わってしましました…

仕事が始まるとバタバタして熱が冷めてしまいそうですが、時間を見つけてリベンジしたい!

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

NLogを使ってAzure Table Storageにログを書きこむ

NuGetを検索するとAzure Table Storage用のTargetをいくつか見かけますが、.NET Coreへの対応状況や依存ライブラリの最新化状況などの制約があって微妙です。
また、Azure Table StorageまわりのSDKはCosmosDBに切り出されたりしてややこしいので、たかがEntityの追加のみで依存関係を増やしたくありません。
そんなわけで、NLog標準のWebServiceTargetを使った書き込みを試したら結構ハマりどころがあったので共有します。

事前準備

コンソールアプリのプロジェクトを作成

ターゲットフレームワークはNLogがサポートするものであれば何でも良いですが、.NET Core 2.0〜3.0までで動作確認をしました。

NLogのインストール

NuGetからインストールします。この記事では4.6.8の利用を前提としていますが、多少前後しても問題ないと思います。

Azure StorageのSAS URLの作成

SASとはShared Access Signatureの略で、あらかじめリソースやそれに対するアクション(参照、書き込みなど)、利用可能期間を制限したトークンです。これを含むURLを使うことで、リクエスト都度署名をするといった手間も省けるためWebServiceTargetで利用する上では好都合というわけです。

作成は簡単で、Azure Portalで対象ストレージアカウントを選択し、メニューの「Shared Access Signature」に移動します。対象リソースなどを選んで「SASと接続文字列を作成する」ボタンを押下後、表示された「Table service の SAS URL」を控えておいてください。
azureportal.png

NLogの設定

まずは以下の通りWebServiceTargetを設定します。名前は何でもいいですが、ここではAZTBLTargetとしてみました。

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwExceptions="true">

    <targets>
        <target name="AZTBLTarget" xsi:type="WebService" protocol="JsonPost" url="<SAS URL>">
            <parameter name="PartitionKey" layout="${callsite}" />
            <parameter name="RowKey" layout="${ticks}.${sequenceid:padCharacter=0:padding=6}" />
            <parameter name="LocalTimestamp" layout="${date:format=yyyy-MM-ddTHH\:mm\:ss.fffffff}" />
            <parameter name="Level" layout="${level:uppercase=true}" />
            <parameter name="Callsite" layout="${callsite}" />
            <parameter name="Message" layout="${message}" />
            <parameter name="Error" layout="${exception:format=Message, ToString:separator=\n}" />
        </target>
    </targets>

    <rules>
        <logger name="*" minlevel="Trace" writeTo="AZTBLTarget" />
    </rules>
</nlog>

簡単に説明すると、ターゲットの種類としてWebServiceのものを定義していて、その通信手続きとしてJSONデータのPOST、そのJSONのパラメータとしてPartitionKeyRowKeyを含む7項目を送信するようにしています。なおPartitionKeyRowKeyはTable Storageとして必須の項目です。

さて、説明の中でurlアトリビュートの説明を飛ばしていましたが、ここがハマりポイントです。コピペしてurlに貼り付けるだけではうまくいきません。先の手順で取得したSAS URLを、以下の通り修正する必要があります。

生成されたもの
https://accountname.table.core.windows.net/?&sv=2019-02-02&ss=t&srt=sco&sp=wau&se=2030-01-04T08:21:42Z&st=2020-01-04T00:21:42Z&spr=https&sig=xxxxxxxxx%3D
修正後
https://accountname.table.core.windows.net/nlogtable?$format=application/json&amp;sv=2019-02-02&amp;ss=t&amp;srt=sco&amp;sp=wau&amp;se=2030-01-04T08:21:42Z&amp;st=2020-01-04T00:21:42Z&amp;spr=https&amp;sig=xxxxxxxxx%3D

修正内容は以下の通り。

  • URIの末尾に/<テーブル名>をつける。当たり前ですが、コピペでは動かず一瞬「ウッ」てなります。修正後の例は、テーブル名をnlogtableのケースです。
  • &&amp;に置換。これもXMLに一般的な話だと思いますが、エラーになるので小心者の私は「えっえっ」てなりました。
  • $format=application/jsonをつける。Insert Entityの仕様を見る限りAcceptヘッダーは任意なのですが、なぜかこれを渡さないと415エラーとなります。しかしながらNLogのWebServiceTargetではなぜかHTTPヘッダーにAcceptを設定することができませんでした。諦めかけていたところ、Payload format for Table service operationsを読んだら$formatで指定できることを発見したため、これをURLに含んでやります。

ログの書き込み

それでは実際に書き込んでみましょう。本記事のポイントは設定方法ですので、書き込み自体は一般的な手順に従えばOKかと思います。ちょっとハマるかもしれないポイントとしては、最終行のLogManager.Shutdown();を実行しないと、ログ送信前にプロセスが終了してしまいTable Storgeに書き込まれない場合があるところでしょうか。

using NLog;

namespace NLogToAzureTable
{
    class Program
    {
        static void Main(string[] args)
        {
            // NLog.configを出力ディレクトリにコピーするように設定してください。
            // もしくは、以下の通り明示的にパスを指定することもできます。2つめの引数falseはエラーを無視するか否かの設定です(false=無視しない)
            // LogManager.Configuration = new XmlLoggingConfiguration("/path/to/NLog.config", false);

            // ロガーの初期化・取得
            var logger = LogManager.GetCurrentClassLogger();

            // ログの書き込み
            logger.Info("test message for info");
            logger.Warn("test message for warn");

            // ロガーの終了。プロセスを終了する前にログを送信するために必要
            LogManager.Shutdown();
        }
    }
}

非同期書き込みの利用

NLogには便利な非同期処理の仕組みAsyncWrapperターゲットがあります。ログメッセージをキューに貯めてバッチ的に別スレッドで処理してくれるといった便利なものです。
使い方は超簡単で、targetsasyncアトリビュートにtrueを設定するだけで全てのターゲットへの書き込みが非同期に行われます。

NLog.config
:中略
<targets async="true">
:中略

一部の書き込みのみを非同期にしたい場合は、ラッパーの名の通り非同期にしたいターゲットをAsyncWrapperで囲みます。このとき、ruleに設定するnameもラッパーのものにすることに注意です。

NLog.config
    <targets>
        <target name="AZTBLTargetAsync" xsi:type="AsyncWrapper">
            <target name="AZTBLTarget" xsi:type="WebService" protocol="JsonPost" url="<SAS URL>">
                <parameter name="PartitionKey" layout="${callsite}" />
                 :中略
            </target>
        </target>
    </targets>

    <rules>
        <logger name="*" minlevel="Trace" writeTo="AZTBLTargetAsync" />
    </rules>

詳細は公式Wikiを読んでみてください。
https://github.com/nlog/NLog/wiki/AsyncWrapper-target

補足

  • PartitionKeyに設定する値。記事では便宜上出力元メソッドの名前を指定していますが、ユーザーとかデバイスとかを一意に特定できるものを設定した方が調査しやすいと思います・
  • パフォーマンスや負荷は未知数。AsyncWrapperを使うことでメイン処理への影響はないと思いますが、書き込み速度や負荷は計測していません。WebServiceTargetに一般的な議論かと思います。詳しい方アドバイスください!!
  • 出力レベル。そもそもリモートでログを取る時点で常にデバッグ情報が欲しいわけではないと思いますので、minlevelをエラーに設定するなど最適化が必要です。
  • Logging Frameworkについて。実装ライブラリを自由に変更できるようにDIなフレームワークが.NET Coreでは用意されていますが、今回は未考慮です。

おまけ:UnityでNLogを使ってAzure Table Storageに書き込み

そもそも何でAzure Table Storageにログを書き込もうと思ったかというと、Gateboxというデバイス向けのアプリ開発をはじめたことがきっかけです。実機デバッグする際にコンソール出力やファイルファイルを選択できないことから、リモートにログ出力をする方法を探ることにしました。ログのためにサーバーを用意するのもアレなのでAzureに・・・という経緯です。

UnityのConsoleへのログ出力用ターゲットも同梱したファクトリーぽいものを作りましたのでよかったら使ってみてください。

NLogFactoryForUnity
https://github.com/uezo/NLogFactoryForUnity

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

cscの作法 その35

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

アナログ時計。

写真

image

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing.Drawing2D;


class form1: Form {
    form1() {
        Text = "clock";
        Opacity = 0.7;
        ClientSize = new Size(150, 150);
        Timer timer = new Timer();
        timer.Interval = 1000; 
        timer.Tick += new EventHandler(timerTick);
        timer.Start();
    }
    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        g.TranslateTransform(ClientSize.Width / 2, ClientSize.Height / 2, MatrixOrder.Append);
        Pen BPen = new Pen(Color.Blue, 5);
        Pen GPen = new Pen(Color.Green, 5);
        Pen RPen = new Pen(Color.Red, 3);
        Point center = new Point(0, 0);
        DateTime time = DateTime.Now;
        double secAng = 2.0 * Math.PI * time.Second / 60.0;
        double minAng = 2.0 * Math.PI * (time.Minute + time.Second / 60.0) / 60.0;
        double hourAng = 2.0 * Math.PI * (time.Hour + time.Minute / 60.0) / 12.0;
        int r = Math.Min(ClientSize.Width, ClientSize.Height) / 2;
        int secHandLength = (int) (0.9 * r);
        int minHandLength = (int) (0.9 * r);
        int hourHandLength = (int) (0.7 * r);
        Point secHand = new Point((int) (secHandLength * Math.Sin(secAng)), (int) (-secHandLength * Math.Cos(secAng)));
        Point minHand = new Point((int) (minHandLength * Math.Sin(minAng)), (int) (-minHandLength * Math.Cos(minAng)));
        Point hourHand = new Point((int) (hourHandLength * Math.Sin(hourAng)), (int) (-hourHandLength * Math.Cos(hourAng)));
        g.DrawLine(RPen, center, secHand);
        g.DrawLine(GPen, center, minHand);
        g.DrawLine(BPen, center, hourHand);
    }
    void timerTick(object sender, EventArgs e) {
        this.Invalidate();
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}




以上。

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

[Storage] Azure Blob Storage

とりあえずバイナリーファイルを(イメージや動画など)保存するときは Blob Storageに保存するような設計をしています。安価かつコントロールしやすくお勧めです。

サービスサイトはこちら
https://azure.microsoft.com/ja-jp/services/storage/blobs/

必要なもの

  • Azure アカウント
  • Visual Studio

ここでカバーされる範囲

  • Blobの構成
  • クライアントライブラリーのダウンロード
  • Containerの作成、ファイルのアップロード、 Containerの削除
  • ファイルのダウンロード、ファイルの削除
  • Container内のファイル一覧を表示

Blobの構成

Blobの構成は下記のようになっています。
image.png

Accountの基にContainer(複数)があり、そのコンテイナーの中にBlobがあります。

クライアントライブラリーのダウロード

まずはクライアントライブラリー V12 を入手します。

Install-Package Azure.Storage.Blobs -Version 12.1.0

Nugetの詳細はこちら
https://www.nuget.org/packages/Azure.Storage.Blobs/

サンプルコード

では、さっそくサンプルコードを見てみます。

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Azure.Storage;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using NUnit.Framework;

namespace Azure.Storage.Blobs.Samples
{
    /// <summary>
    /// Basic Azure Blob Storage samples
    /// </summary>
    public class Sample01b_HelloWorldAsync : SampleTest
    {
        /// <summary>
        /// Upload a file to a blob.
        /// </summary>
        [Test]
        public async Task UploadAsync()
        {
            // Create a temporary Lorem Ipsum file on disk that we can upload
            string path = CreateTempFile(SampleFileContent);

            // Get a connection string to our Azure Storage account.  You can
            // obtain your connection string from the Azure Portal (click
            // Access Keys under Settings in the Portal Storage account blade)
            // or using the Azure CLI with:
            //
            //     az storage account show-connection-string --name <account_name> --resource-group <resource_group>
            //
            // And you can provide the connection string to your application
            // using an environment variable.
            string connectionString = ConnectionString;

            // Get a reference to a container named "sample-container" and then create it
            BlobContainerClient container = new BlobContainerClient(connectionString, Randomize("sample-container"));
            await container.CreateAsync();
            try
            {
                // Get a reference to a blob
                BlobClient blob = container.GetBlobClient(Randomize("sample-file"));

                // Open the file and upload its data
                using (FileStream file = File.OpenRead(path))
                {
                    await blob.UploadAsync(file);
                }

                // Verify we uploaded some content
                BlobProperties properties = await blob.GetPropertiesAsync();
                Assert.AreEqual(SampleFileContent.Length, properties.ContentLength);
            }
            finally
            {
                // Clean up after the test when we're finished
                await container.DeleteAsync();
            }
        }

        /// <summary>
        /// Download a blob to a file.
        /// </summary>
        [Test]
        public async Task DownloadAsync()
        {
            // Create a temporary Lorem Ipsum file on disk that we can upload
            string originalPath = CreateTempFile(SampleFileContent);

            // Get a temporary path on disk where we can download the file
            string downloadPath = CreateTempPath();

            // Get a connection string to our Azure Storage account.
            string connectionString = ConnectionString;

            // Get a reference to a container named "sample-container" and then create it
            BlobContainerClient container = new BlobContainerClient(connectionString, Randomize("sample-container"));
            await container.CreateAsync();
            try
            {
                // Get a reference to a blob named "sample-file"
                BlobClient blob = container.GetBlobClient(Randomize("sample-file"));

                // First upload something the blob so we have something to download
                await blob.UploadAsync(File.OpenRead(originalPath));

                // Download the blob's contents and save it to a file
                BlobDownloadInfo download = await blob.DownloadAsync();
                using (FileStream file = File.OpenWrite(downloadPath))
                {
                    await download.Content.CopyToAsync(file);
                }

                // Verify the contents
                Assert.AreEqual(SampleFileContent, File.ReadAllText(downloadPath));
            }
            finally
            {
                // Clean up after the test when we're finished
                await container.DeleteAsync();
            }
        }

        /// <summary>
        /// Download our sample image.
        /// </summary>
        [Test]
        public async Task DownloadImageAsync()
        {
            string downloadPath = CreateTempPath();
            #region Snippet:SampleSnippetsBlob_Async
            // Get a temporary path on disk where we can download the file
            //@@ string downloadPath = "hello.jpg";

            // Download the public blob at https://aka.ms/bloburl
            await new BlobClient(new Uri("https://aka.ms/bloburl")).DownloadToAsync(downloadPath);
            #endregion

            Assert.IsTrue(File.ReadAllBytes(downloadPath).Length > 0);
            File.Delete("hello.jpg");
        }

        /// <summary>
        /// List all the blobs in a container.
        /// </summary>
        [Test]
        public async Task ListAsync()
        {
            // Get a connection string to our Azure Storage account.
            string connectionString = ConnectionString;

            // Get a reference to a container named "sample-container" and then create it
            BlobContainerClient container = new BlobContainerClient(connectionString, Randomize("sample-container"));
            await container.CreateAsync();
            try
            {
                // Upload a couple of blobs so we have something to list
                await container.UploadBlobAsync("first", File.OpenRead(CreateTempFile()));
                await container.UploadBlobAsync("second", File.OpenRead(CreateTempFile()));
                await container.UploadBlobAsync("third", File.OpenRead(CreateTempFile()));

                // List all the blobs
                List<string> names = new List<string>();
                await foreach (BlobItem blob in container.GetBlobsAsync())
                {
                    names.Add(blob.Name);
                }

                Assert.AreEqual(3, names.Count);
                Assert.Contains("first", names);
                Assert.Contains("second", names);
                Assert.Contains("third", names);
            }
            finally
            {
                // Clean up after the test when we're finished
                await container.DeleteAsync();
            }
        }

        /// <summary>
        /// Trigger a recoverable error.
        /// </summary>
        [Test]
        public async Task ErrorsAsync()
        {
            // Get a connection string to our Azure Storage account.
            string connectionString = ConnectionString;

            // Get a reference to a container named "sample-container" and then create it
            BlobContainerClient container = new BlobContainerClient(connectionString, Randomize("sample-container"));
            await container.CreateAsync();

            try
            {
                // Try to create the container again
                await container.CreateAsync();
            }
            catch (RequestFailedException ex)
                when (ex.ErrorCode == BlobErrorCode.ContainerAlreadyExists)
            {
                // Ignore any errors if the container already exists
            }
            catch (RequestFailedException ex)
            {
                Assert.Fail($"Unexpected error: {ex}");
            }

            // Clean up after the test when we're finished
            await container.DeleteAsync();
        }
    }
}

コンテイナーの作成

こちらでsample-containerという名前のコンテイナーの作成を行います

 // Get a reference to a container named "sample-container" and then create it
BlobContainerClient container = new BlobContainerClient(connectionString, Randomize("sample-container"));

await container.CreateAsync();

ファイルのアップロード

下記でファイルをアップロードします

// Get a reference to a blob
BlobClient blob = container.GetBlobClient(Randomize("sample-file"));

// Open the file and upload its data
using (FileStream file = File.OpenRead(path))
{
  await blob.UploadAsync(file);
}

そしてファイルがアップロードできているかどうかを確認しています。

// Verify we uploaded some content
BlobProperties properties = await blob.GetPropertiesAsync();
Assert.AreEqual(SampleFileContent.Length, properties.ContentLength);

コンテイナーの削除

そしてコンテイナーを削除します(テストなので)

// Clean up after the test when we're finished
await container.DeleteAsync();

ファイルをダウンロード

では、次にアップロードしたファイルをダウンロードします。

// Download the blob's contents and save it to a file
BlobDownloadInfo download = await blob.DownloadAsync();
using (FileStream file = File.OpenWrite(downloadPath))
{
  await download.Content.CopyToAsync(file);
}

ダウンロードしたコンテンツが正しいものか下記で確認します。

// Verify the contents
Assert.AreEqual(SampleFileContent, File.ReadAllText(downloadPath));

ファイルの一覧

最後にファイルの一覧を参照します

List<string> names = new List<string>();
await foreach (BlobItem blob in container.GetBlobsAsync())
{
  names.Add(blob.Name);
}

ここではわかりやすいようにList<string>に対してloopを回していますが、GetBlibAsync()でリストを取得することができます。

今回は主にサンプルコードのレビューとなりましたが、次回は(未定)Containerの設定管理に関してみていきたいと思います。

フォームからファイルをアップロードする具体的な方法(コード)はこちらを参照ください。
https://qiita.com/syantien/items/a816d4d02b0bd8d6a06d

参照

サンプルコード
https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/storage/Azure.Storage.Blobs/samples/Sample01b_HelloWorldAsync.cs

クイックスタート
https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet

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

Pwned Passwords listをSQLiteでデータベース化してみた

 データベースやSQLの学習のため、データが欲しかった。Pwned Passwords listという5億件を超えるデータがある。よし、データベースに突っ込もう、と思い立った。因みに検索した範囲で扱っている最も大きなデータは8000万件で、それ以上となると殆ど見当たらない。このことを知るのは作業を開始した後である。

 よくよく考えれば当たり前の酷く間抜けなミスをしたが、調べた範囲内にその間抜けなミスを指摘していたものが無かったので他の人のためになればと記録に残しておく。予め書いておくが本当に間抜けなミスである。気付かない方がどうかしてる。

 「データベース初心者に対して教える人」向けにどのような経緯でこのミスをしたのか、気付かなかったのか分かりやすくするため、時系列順に記述します。結論は最後に書くので、結論だけ知りたい方はすっ飛ばして結論だけご覧ください。

前提

データベースに関する知識
り、りろんはしってる
C#に関する知識
最近Streamクラスと言うのを覚えた。
英語に関する知識
グーグル翻訳様万歳

応用情報を持ってるが実践はしたことがない奴と言えば大体合ってる。

そもそもPwned Passwords listとは

555,278,657件の漏洩したパスワードのハッシュと各パスワードの漏洩件数のリストである。Have I Been Pwned: Pwned Passwordsで公開されている。

作業開始

正直、時間が掛かりすぎて検索履歴から作業時間を割り出してるので誤差は大きい。あまり関係ないところは飛ばしている。

0分~

Pwned Passwords listのダウンロード

一番上のをダウンロード。.7zなので7-Zipで解凍する。ダウンロード自体に数時間かかる。
Downloading the Pwned Passwords list.png

SQLiteのダウンロード

SQLiteからPrecompiled Binaries for Windowsをダウンロード。PATHは通しておく。

テーブルの作成

DB Browser for SQLiteを使用してテーブルを作成。

password-hashs.db
CREATE TABLE "passwordhashs" (
    "hash"  TEXT NOT NULL,
    "prevalence"    INTEGER NOT NULL,
    PRIMARY KEY("hash")
);

CSVファイルのインポート

Windows PowerShellを使用してPwned Passwords listをインポートする。区切り記号が:であることに注意。デフォのファイルネームが長いので雑にリネームした。

>sqlite3 password-hashs.db
 >.separator :
 >.import password-hashs.csv passwordhashs

30分~

全然終わらない。タスクマネージャーでCSVのファイルサイズ24GBとディスクの書き込み速度6MB/sを見比べる。何年かかるんだ? 
実のところ理由はもう出ているのだが全く気付かず模索。SQLite 高速化、書き込み速度、巨大、なんて単語で検索。
PRAGMAを変更すると早くなるらしい。
×ボタンで雑に終了。当然データベースは破損。復旧方法を探す。破損したSQLiteのファイルを修復するを発見。

>sqlite3 password-hashs.db .dump | sqlite3 new.db 

C#でPRAGMAとやらを設定してデータベースに書き込む。
C#でSQLite3を使ってみるを参考にした。SQL文を直接書いて実行したほうが早いようだ。

CsvToDatabaseConvert.cs
   public class CsvToDatabaseConvert {
        const int MAX_READ_LINE_LENGTH = 100000;
        const string DATABASE_NAME = @"./password-hashs.db";
        const string CSV_NAME = @"./password-hashs.csv";
        static void Main(string[] args) {
            try {
                var prg = new CsvToDatabaseConvert();
                Console.WriteLine("Start");
                prg.Go(DATABASE_NAME, CSV_NAME);
            } catch(Exception e) {
                Console.WriteLine(e.ToString());
                Console.ReadKey();
            }
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
        private void Go(string dbName, string csvName) {
            //SQLite設定
            var builder = new SQLiteConnectionStringBuilder() {
                DataSource = dbName,
                Version = 3,
                LegacyFormat = false,
                SyncMode = SynchronizationModes.Off,
                JournalMode = SQLiteJournalModeEnum.Memory
            };

            //Database,CSVFileオープン
            using(var db = new SQLiteConnection(builder.ToString()))
            using(var file = new FileStream(csvName, FileMode.Open, FileAccess.Read, FileShare.Read, 65536))
            using(var csv = new StreamReader(file)) {
                db.Open();
                SkipCSV(db, csv);
                WriteDatabase(db, csv);
                db.Close();
            }
        }

        //DBから入力済みの行数を取得し、読み飛ばす。
        private void SkipCSV(SQLiteConnection db, StreamReader csv) {
            long len = context.GetTable<PwnedPasswordsRow>().Count();
            for(long i = 0; i < len; i++) {
                csv.ReadLine();
            }
        }

        private void WriteDatabase(SQLiteConnection db, StreamReader csv) {
            //CSVFile終端まで繰り返し
            while(!csv.EndOfStream) {
                InsertRowsMultiple(db, csv);
                Task.Run(() => Console.Write('.'));
            }
        }
        private void InsertRowsMultiple(SQLiteConnection db, StreamReader csv) {
            using(var command = new SQLiteCommand(db)) {
                command.Transaction = db.BeginTransaction();
                var sb = new StringBuilder();
                sb.Append("insert into passwordhashs values");

                long i = 0;
                while(i < MAX_READ_LINE_LENGTH && !csv.EndOfStream) {
                    var row = new PwnedPasswordsRow(csv.ReadLine().Split(':'));
                    if(row.hash == null)
                        continue;
                    sb.Append("(" + $"'{row.hash}', {row.prevalence}),");
                    i++;
                }
                sb.Remove(sb.Length-1,1);
                sb.Append(";");
                command.CommandText = sb.ToString();
                command.ExecuteNonQuery();
                command.Transaction.Commit();
            }
            return;
        }
    }
    [Table(Name = "passwordhashs")]
    public class PwnedPasswordsRow {
        public PwnedPasswordsRow(string[] v) {
            hash = v[0];
            prevalence = int.Parse(v[1]);
        }
        public PwnedPasswordsRow() {}
        [Column(Name = "hash", CanBeNull = false, DbType = "TEXT", IsPrimaryKey = true)]
        public string hash { get; set; }

        [Column(Name = "prevalence", CanBeNull = false, DbType = "INT")]
        public int prevalence { get; set; }
    }

8時間後~21時間後

適当に放置。思い立ったタイミングで、中断処理だったり経過時間を表示したりを追加。全然終わらない。ディスクの書き込み速度は0.3MB/sまで低下。なぜだ。
CSVファイルを読み飛ばすためにCountで行数を調べているので更新のため中断する度一々O(n)時間掛かる1テキストファイルに読み込んだ行を書き込んでおくことを思いつく。

1日後

ようやくどこの処理に時間が掛かってるのか調べる。

CsvToDatabaseConvert.cs
                command.ExecuteNonQuery();
                command.Transaction.Commit();

コミットの辺りですごい時間が掛かってることが判明。CSVの読み込みに時間が掛かってるんじゃないかと調べてたのは無駄でした。

11日後

経過がわかりやすいようにといろいろ改造しつつ、このあたりで8000万件ぐらい。なんだか自分の寿命のほうが先に来そうなことからは必死に目を背ける。この間ずっとHDDがカリカリ言ってる。
10万件入れるのに30分ほど掛かる。

14日後

ふとPRIMARY KEYはInsertの度に格納されているデータを全て調べているのではと思い立つ。400万件のテストデータで試してみる。

PK有り PK無し
144806166 114177699

ちょっと早い。PRIMARY KEYを外して試してみる。めっちゃ早い。10万件ごとに打たれるドットがズラーと並ぶ。

結論

password-hashs.db
CREATE TABLE "passwordhashs" (
    "hash"  TEXT NOT NULL,
    "prevalence"    INTEGER NOT NULL,
    PRIMARY KEY("hash")
);

PRIMARY KEY("hash")が全ての原因でした。2週間以上掛かったのは何気なくチェックボックスをクリックしたせいです。PRIMARY KEYはInsertするたびに同じフィールドがないか調べます。つまりO($n^2$)時間かかるのです。
PKのチェックボックスを外して試してみると、30分程で終わりました。

因みに

PRIMARY KEYの設定をInsert終了後にやったらヒープソートのO($nlogn$)位に下がるのでは? と夢見て思って試したところ普通に固まりました。


  1. ここで気付くべきだった。 

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

やらかしコーディング集(フィクション)を作ろうと思ったがまだ1個しかできていない件

こんなコード書いたら不具合になるよなーと思ったコードを羅列してみようと思ったが、
あまりネタがなかった&不具合再現させようとしたら案外面倒だったので記事作成が進まない。。。
が、せっかく書いたので投稿してみる。

何がダメか考えてから解説に行ってみましょう。
C#で書いていますが、大体の言語で当てはまるかと。

NGコード#01

現在時刻を取得して文字列にしようとしている。

static string GetNow()
{
    int s = DateTime.Now.Second;
    int ms = DateTime.Now.Millisecond;
    return string.Format("{0:00}.{1:000}", s, ms);
}

解説へ

NGコード#02 予定地

NGコード#03 予定地

NGコード#04 予定地

NGコード#05 予定地

#01 - 変化するデータを扱うときは気を付けよう

ダメな点:DateTime.Nowが2回呼ばれる間に、DateTime.Nowの値が変化してしまう。
何が起きるか:例えば 12.999~13.000秒の間でこのメソッドを実行したとき、"12.000" を出力してしまう可能性がある。

修正前コードの不具合再現用コード
不具合発生再現用コード
using System;
using System.Diagnostics;
using System.Threading;

class DateTimeTest
{
    static string GetNow(out int os, out int oms)
    {
        int s = DateTime.Now.Second;
        int ms = DateTime.Now.Millisecond;
        os = s;
        oms = ms;
        return string.Format("{0:00}.{1:000}", s, ms);
    }

    [STAThread]
    static void Main(string[] args)
    {
        string prevStr = "no data";
        int prevTs;
        int prevTms;
        int ts;
        int tms;

        Console.WriteLine("started.");
        for (int i=0;i<1000;i++) {
            string nowStr = GetNow(out ts, out tms);
            prevStr = nowStr;
            while ( true ) {
                prevTs = ts;
                prevTms = tms;
                nowStr = GetNow(out ts, out tms);
                Console.Write(tms);
                Console.Write("\r");
                if ( (ts!=prevTs) && tms == 0 ) { // xx.xxx から yy.000 になったときに表示
                    Console.Write("i:");
                    Console.WriteLine(i);

                    Console.WriteLine(prevStr);
                    Console.WriteLine(nowStr);
                    break;
                }
                prevStr = nowStr;
            }
        }
    }
}


※色々コードが付加されているのはご容赦ください。

出力結果抜粋
i:20
05.999
06.000
i:21
06.000   <-- ここで6.999と出力すべきところが、millisecのほうが進みすぎて6.000となっている
07.000
i:22
07.999
08.000

#01 - 対応例

変化するデータを使うときは、一時変数なりに一度代入しよう。
※マルチスレッドや割り込みなどで値が変化するデータを扱う場合は、これだけでは不十分。

修正版コード
static string GetNow()
{
    var t = DateTime.Now;
    return string.Format("{0:00}.{1:000}", t.Second, t.Millisecond);
}

ねたメモ

・サロゲートペア
・Win32APIとメモリ関係
・マルチスレッド(書けない気がする)

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

cscの作法 その34

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

dbpediaにsparql投げてxmlを取得せよ。

写真

image.png

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Net;
using System.Web;
using System.IO;
using System.Text;
using System.Xml;


class form1: Form {
    form1() {
        Text = "dbpedia";
        ClientSize = new Size(200, 200);
        Button btn1 = new Button();
        btn1.Location = new Point(50, 50);
        btn1.Text = "test";
        btn1.Click += btn1_Click;
        Controls.AddRange(new Control[] {
            btn1
        });
    }
    void btn1_Click(object sender, System.EventArgs e) {
        WebRequest request = WebRequest.Create("http://ja.dbpedia.org/sparql");
        request.Method = "POST";
        string postData = "SELECT DISTINCT * WHERE { dbpedia-ja:デ・トマソ dbpedia-owl:abstract ?abstract .}";
        postData = "query=" + HttpUtility.UrlEncode(postData);
        Console.WriteLine(postData);
        byte[] byteArray = Encoding.UTF8.GetBytes(postData);
        request.ContentType = "application/x-www-form-urlencoded";
        request.ContentLength = byteArray.Length;
        Stream dataStream = request.GetRequestStream();
        dataStream.Write(byteArray, 0, byteArray.Length);
        dataStream.Close();
        WebResponse response = request.GetResponse();
        Console.WriteLine(((HttpWebResponse) response).StatusDescription);
        using (dataStream = response.GetResponseStream()) {
            StreamReader reader = new StreamReader(dataStream);
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(reader);
            XmlElement rootElement = xmlDoc.DocumentElement;
            XmlNodeList nodelist = rootElement.GetElementsByTagName("literal");
            MessageBox.Show(nodelist.Item(0).InnerText);
        }
        response.Close();
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

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

cscの作法 その33

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

マウスで線を描け。

写真

image.png

サンプルコード

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

class form1: Form {
    int px = 0;
    int py = 0;
    Bitmap bmp;
    form1() {
        Text = "oe";
        ClientSize = new Size(300, 300);
        bmp = new Bitmap(300, 300);
        this.MouseMove += new MouseEventHandler(mouseMove);
        this.MouseDown += new MouseEventHandler(mouseDown);
    }
    void mouseDown(object sender, MouseEventArgs e) {
        if (e.Button == MouseButtons.Left)
        {
            px = e.X;
            py = e.Y;
        }
    } 
    void mouseMove(object sender, MouseEventArgs e) {
        if (e.Button == MouseButtons.Left)
        {
            Text = "mouse: (" + e.X + ", " + e.Y + ")";
            Graphics g = Graphics.FromImage(bmp);
            Pen p0 = new Pen(Color.Red);
            p0.Width = 3;
            g.DrawLine(p0, px, py, e.X, e.Y);
            px = e.X;
            py = e.Y;
            Invalidate();
        }
    }
    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        g.DrawImage(bmp, 0, 0);
        base.OnPaint(e);
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

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

cscの作法 その32

概要

cscの作法、調べてみた。
tesorflow.jsでxorを学習して、バイアスとウェイトを取り出して、c#で推論してみた。

写真

image.png

サンプルコード

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

class form1: Form {
    form1() {
        Text = "nn";
        ClientSize = new Size(200, 200);
        double[][] w1 = {
            new double[] {-0.8971094, 1.4029436, -1.3633214, -0.775598, 1.2531579, 1.0529767, 1.3460118, -1.5597397},
            new double[] {1.57735, -1.1888924, 1.6184694, -0.5399966, -0.9783241, 0.9480417, 0.9573314, -1.1738404}
        };
        double[] b1 = {0.428151, 0.7863605, 0.8015493, 0.8869765, 0.6552361, 0.3099377, 0.1825127, 0.0495548};
        double[][] w2 = {
            new double[] {-1.7588012},
            new double[] {-1.5193572},
            new double[] {-1.1239196},
            new double[] {0.8726832},
            new double[] {-1.7272931},
            new double[] {1.4907871},
            new double[] {1.1091278},
            new double[] {-0.9800876}
        };
        double[] b2 = {0.1271933};
        string res = "";
        for (int i = 0; i < 4; i++)
        {
            int k;
            int l;
            double[] x = {0, 0};
            double[] y = {0};
            double[] h = {0, 0, 0, 0, 0, 0, 0, 0};
            if (i == 0)
            {
                x[0] = 1.0;
                x[1] = 0;
                res += "1, 0 = ";
            }
            if (i == 1)
            {
                x[0] = 0;
                x[1] = 1.0;
                res += "0, 1 = ";
            }
            if (i == 2)
            {
                x[0] = 1.0;
                x[1] = 1.0;
                res += "1, 1 = ";
            }
            if (i == 3)
            {
                x[0] = 0;
                x[1] = 0;
                res += "0, 0 = ";
            }
            for (k = 0; k < 2; k++)
            {
                for (l = 0; l < 8; l++)
                {
                    h[l] += x[k] * w1[k][l];
                }
            }
            for (l = 0; l < 8; l++)
            {
                h[l] += b1[l];
                h[l] = tanh(h[l]);
            }
            for (k = 0; k < 8; k++)
            {
                for (l = 0; l < 1; l++)
                {
                    y[l] += h[k] * w2[k][l];
                }
            }
            for (k = 0; k < 1; k++)
            {
                y[k] += b2[k];
                y[k] = sigmoid(y[k]);
            }
            res += y[0].ToString() + "\r\n";
        }
        MessageBox.Show(res);
    }
    double tanh(double x) {
        double a = Math.Exp(x);
        double b = Math.Exp(-x);
        return (a - b) / (a + b);
    }
    double sigmoid(double x) {
        return 1.0 / (1.0 + Math.Exp(-x));
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}




以上。

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

C++のDLLをC#から呼び出しHello World !

はじめに

C++ で作成したライブラリを C# で操作したいと思い、調べました。
C++のライブラリはlibとdllの2種類があります。
C# からは dll のみ呼び出せますので、dll にします。

環境構築

  • Visual Studio Community 2019
    • 下記2つがインストール済み
      .NETデスクトップ開発
      C++によるデスクトップ開発

前提

C++、C# ともに「x86」でビルドします。
「x86」じゃないとビルドできないケースもあるので。
(昨今、理由がなければx64で合わせるで良いと思いますが)

C++ 側がReleaseでないと気づけない不具合が多いので、C++ / C# のどちらもReleaseで統一します。

セットアップ

C++(DLL生成)側

  • Visual Studio 2019 を起動
  • 新しいプロジェクトの作成
  • 【言語】C++ でフィルタ
  • Windowsデスクトップウィザード
  • 「次へ」ボタン選択
  • 【プロジェクト名】MyUtils
  • 【場所】デスクトップ
  • □「ソリューションとプロジェクトを同じディレクトリに配置する」はチェックを外す
  • 「作成」ボタン選択
  • アプリケーションの種類:ダイナミックリンクライブラリ(.dll)
  • ■ 空のプロジェクトにチェック
  • 「OK」ボタン選択
  • 「Release」「x86」に設定
    release.png

C#(DLL使用)側

  • Visual Studio 2019 を起動
  • 新しいプロジェクトの作成
  • 【言語】C# でフィルタ
  • コンソールアプリ(.NET Framework)
  • 「次へ」ボタン選択
  • 【プロジェクト名】ConsoleApp1
  • 【場所】デスクトップ
  • □「ソリューションとプロジェクトを同じディレクトリに配置する」はチェックを外す
  • 【フレームワーク】.NET Framework 4.6.1
  • 「作成」ボタン選択
  • 「Release」「Any CPU」に設定

    release2.png

  • プロジェクト名(ConsoleApp1)を右クリック>プロパティ>ビルド>【対象プラットフォーム】x86 に変更し保存(DLL側と合わせるため)。

詳細

C++(DLL生成)側

Sample.h
#pragma once
extern bool Hello(char* buffer);
Sample.cpp
#include <stdio.h>
#include "Sample.h"

extern bool Hello(char* buffer)
{
    sprintf_s(buffer, 256, "Hello World");
    return true;
}
MyUtils.def(プロジェクト名.def)
LIBRARY MyUtils
EXPORTS
    Hello @1

DEFファイルについては、関数名を書いて@1は連番のようです。

C#(DLL使用)側

Program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        // .NetFramework 4.0 からは、Cdeclを指定しないとVSから実行時に警告になる
        [DllImport("MyUtils.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool Hello(StringBuilder str);

        static void Main(string[] args)
        {
            StringBuilder buffer = new StringBuilder(256);
            bool ret = Hello(buffer);
            Console.WriteLine("【" + ret + "】" + buffer.ToString());
            Console.ReadLine();
        }
    }
}

文字列をDLLからC#に渡すには、StringBuilderで参照渡しのようです。

出力結果

DLL側(MyUtilsプロジェクト)をReleaseビルドして生成された「MyUtils.dll」ファイルを
「ConsoleApp1\bin\Release\」配下にコピーして、C#側をReleaseビルド及び実行をします。

【True】Hello World

参考文献

DEFファイルに気づくまでに10分くらいかかってしまったが、下記サイトで気づけました。
http://light11.hatenadiary.com/entry/2019/05/25/235638

.NET 4.0 からDLL importの仕方を変えないと、警告される際に参考にしました。
http://csharpblues.jugem.jp/?eid=8

終わりに

C++ の関数定義に「"C"」と「__stdcall」は省略しても動いたので、省略しました。
(Static Libraryで作成したプロジェクト(lib)をDLLに一時的に変換が元々やりたいことなので)
LibをDllに変換する際、DEFファイルを追加して、「構成の種類」を「ダイナミックライブラリ(.dll)」にちょっと変えるだけでDLLが作成できるのではと今回思いました。

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