- 投稿日:2020-01-04T23:34:40+09:00
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-sampledocker-composeの際に利用したToDoのWebアプリケーションを今回も利用します。
トランザクションデータはデータベース (SQL Server) に保存しています。システム構成
Docker for Windows を利用して、ローカルPC上にKubernetes環境を構築します。
ローカル開発時は、IIS Express と LocalDB で構成していました。これを、下記図のイメージで構築します。「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を有効化します。
またGUIツールとして、Visual Studio Codeの拡張機能を利用しました。
Kubernetesのリソースを視覚的に確認できるため、導入しておくと便利です。
1. app.yaml
まずは、Nginx、ASP.NET Coreアプリケーションの部分の定義を作成します。
Kubernetesのリソースは、YAMLで定義していきます。app.yamlapiVersion: 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.yamlapiVersion: 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.mdkubectl 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.yamlapiVersion: 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ブラウザでアクセスすると、無事アクセスすることが出来ました。
まとめ
Kubernetesの概念は当初複雑に感じていましたが、実際に触ってみると理解が進み易いと感じました。
また、コンテナ技術を扱っている関係上、docker や docker-compose に慣れておくと入門し易いと思います。
- 投稿日:2020-01-04T23:32:00+09:00
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プロジェクト作成 (折りたたみ)
OpenCVライブラリの取得
OpenCVの公式 から取得して、良い感じの場所に展開しておきます。
今回は "C:\opencv\OpenCV-android-sdk" に置きました。
自作ライブラリの対応
STL設定
デフォルトでは [LLVM libc++ スタティックライブラリ(c++_static)] になっていたので、[共有ライブラリ (c++_shared)] に変えました。
OpenCVインクルード
展開したOpenCVのヘッダフォルダをインクルードします。
C:\opencv\OpenCV-android-sdk\sdk\native\jni\includeOpenCVライブラリ
展開した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-v8aOpenCVライブラリ名
上記ディレクトリ内の各ライブラリ名を指定します。今回は全てのライブラリを列挙してみました。
ライブラリ名には、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ソースコード(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追加したライブラリ(*.so) のプロパティを変更します。
- ビルドアクション:AndroidNativeLirary
- 出力ディレクトリにコピー:新しい場合はコピーする
ソースコード(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
参考
Android プロジェクトへの C / C++ コードの追加
終わりに
2019年冬休みの自分への課題を記事にまとめました。
本当はOpenCVのソースをセルフビルドして利用したかったのですが、CMakeやらLinkやらのエラーが取れないまま休みが終わってしましました…
仕事が始まるとバタバタして熱が冷めてしまいそうですが、時間を見つけてリベンジしたい!
- 投稿日:2020-01-04T21:36:18+09:00
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」を控えておいてください。
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のパラメータとしてPartitionKey
やRowKey
を含む7項目を送信するようにしています。なおPartitionKey
とRowKey
は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&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修正内容は以下の通り。
- URIの末尾に
/<テーブル名>
をつける。当たり前ですが、コピペでは動かず一瞬「ウッ」てなります。修正後の例は、テーブル名をnlogtable
のケースです。&
を&
に置換。これも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
ターゲットがあります。ログメッセージをキューに貯めてバッチ的に別スレッドで処理してくれるといった便利なものです。
使い方は超簡単で、targets
のasync
アトリビュートに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
- 投稿日:2020-01-04T20:49:51+09:00
cscの作法 その35
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
アナログ時計。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-04T20:41:18+09:00
[Storage] Azure Blob Storage
とりあえずバイナリーファイルを(イメージや動画など)保存するときは Blob Storageに保存するような設計をしています。安価かつコントロールしやすくお勧めです。
サービスサイトはこちら
https://azure.microsoft.com/ja-jp/services/storage/blobs/必要なもの
- Azure アカウント
- Visual Studio
ここでカバーされる範囲
Blobの
構成- クライアントライブラリーのダウンロード
Container
の作成、ファイルのアップロード、Container
の削除- ファイルのダウンロード、ファイルの削除
Container
内のファイル一覧を表示Blobの構成
Account
の基にContainer
(複数)があり、そのコンテイナーの中にBlob
があります。クライアントライブラリーのダウロード
まずはクライアントライブラリー V12 を入手します。
Install-Package Azure.Storage.Blobs -Version 12.1.0Nugetの詳細はこちら
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://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet
- 投稿日:2020-01-04T17:03:42+09:00
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で解凍する。ダウンロード自体に数時間かかる。
SQLiteのダウンロード
SQLiteからPrecompiled Binaries for Windowsをダウンロード。PATHは通しておく。
テーブルの作成
DB Browser for SQLiteを使用してテーブルを作成。
password-hashs.dbCREATE 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 passwordhashs30分~
全然終わらない。タスクマネージャーでCSVのファイルサイズ24GBとディスクの書き込み速度6MB/sを見比べる。何年かかるんだ?
実のところ理由はもう出ているのだが全く気付かず模索。SQLite 高速化、書き込み速度、巨大、なんて単語で検索。
PRAGMAを変更すると早くなるらしい。
×ボタンで雑に終了。当然データベースは破損。復旧方法を探す。破損したSQLiteのファイルを修復するを発見。>sqlite3 password-hashs.db .dump | sqlite3 new.dbC#でPRAGMAとやらを設定してデータベースに書き込む。
C#でSQLite3を使ってみるを参考にした。SQL文を直接書いて実行したほうが早いようだ。CsvToDatabaseConvert.cspublic 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.cscommand.ExecuteNonQuery(); command.Transaction.Commit();コミットの辺りですごい時間が掛かってることが判明。CSVの読み込みに時間が掛かってるんじゃないかと調べてたのは無駄でした。
11日後
経過がわかりやすいようにといろいろ改造しつつ、このあたりで8000万件ぐらい。なんだか自分の寿命のほうが先に来そうなことからは必死に目を背ける。この間ずっとHDDがカリカリ言ってる。
10万件入れるのに30分ほど掛かる。14日後
ふとPRIMARY KEYはInsertの度に格納されているデータを全て調べているのではと思い立つ。400万件のテストデータで試してみる。
PK有り PK無し 144806166 114177699 ちょっと早い。PRIMARY KEYを外して試してみる。めっちゃ早い。10万件ごとに打たれるドットがズラーと並ぶ。
結論
password-hashs.dbCREATE 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$)位に下がるのでは? と
夢見て思って試したところ普通に固まりました。
ここで気付くべきだった。 ↩
- 投稿日:2020-01-04T16:09:54+09:00
やらかしコーディング集(フィクション)を作ろうと思ったがまだ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とメモリ関係
・マルチスレッド(書けない気がする)
- 投稿日:2020-01-04T13:28:44+09:00
cscの作法 その34
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
dbpediaにsparql投げてxmlを取得せよ。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-04T08:43:06+09:00
cscの作法 その33
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
マウスで線を描け。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-04T07:52:19+09:00
cscの作法 その32
概要
cscの作法、調べてみた。
tesorflow.jsでxorを学習して、バイアスとウェイトを取り出して、c#で推論してみた。写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-04T01:43:55+09:00
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」に設定
![]()
C#(DLL使用)側
- Visual Studio 2019 を起動
- 新しいプロジェクトの作成
- 【言語】C# でフィルタ
- コンソールアプリ(.NET Framework)
- 「次へ」ボタン選択
- 【プロジェクト名】ConsoleApp1
- 【場所】デスクトップ
- □「ソリューションとプロジェクトを同じディレクトリに配置する」はチェックを外す
- 【フレームワーク】.NET Framework 4.6.1
- 「作成」ボタン選択
プロジェクト名(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 @1DEFファイルについては、関数名を書いて@1は連番のようです。
C#(DLL使用)側
Program.csusing 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が作成できるのではと今回思いました。