マルチスレッド処理(thread)VC++でDLL作成しC#で利用 for VS2015 Express for Desktop
マルチ処理(thread)VC++でDLL作成し利用 VC++ for VS2015 Express for Desktop - barus's diary
の続き。
VC++でDLLを作成し、C#側でDLLを利用した際のメモ。
button1を押すと、5つのマルチスレッドが走り
[ GUI ] → [DLL]
表示部分はC#の方が、個人的に使いやすいのでC#で作りました。
今回、段階を踏んで作成した。
ステップ1 C#コンソールアプリケーションからVC++作成のDLLを呼び出す。
ステップ2 C#のGUIからVC++作成のDLLを呼び出す。
ステップ3 C#のGUIからVC++作成のDLLをマルチスレッドで呼び出す。
いきなり、ステップ3は難しかったのでステップ1~3を踏まえました。
後日、忘れたころに見直す時に役に立つかと思います。
プロジェクトは3つ作成する。
(1)Csharp_Console ・・・コンソールアプリケーション
(2)Csharp_Thread ・・・GUI
(3)VCplus_DLL ・・・DLL(前回とほぼ同じ)
ファイルは以下のような感じとなっています。
ほとんどのファイルは自動的に作成されるので
修正するファイル以外は触らないでOK。
Csharp_Console ・・・コンソールアプリケーション
Program.csを編集
Csharp_Thread ・・・GUI
Form1.csを編集
VCplus_DLL ・・・DLL
Thread_samp1.cpp を編集
Thread_samp1.h を編集
呼び出すDLLは全て共通です。
Thread_samp1.cpp
// Thread_samp1.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。 // #include "stdafx.h" #include <string.h> #include <iostream> #include <windows.h> #include <time.h> #include <stdlib.h> #include <random> using namespace std; #ifdef __cplusplus #define DLLEXPORT extern "C" __declspec(dllexport) #else #define DLLEXPORT __declspec(dllexport) #endif class worksamp { public: int _id; worksamp(); ~worksamp(); //プロジェクト->プロパティ->構成プロパティ->全般 の中にある //文字セットを[Unicode 文字セットを使用する]から[マルチバイト文字セットを使用する] void hevy_work(int thread_number, int *pintbar, char *pchar); }; worksamp::worksamp() {} worksamp::~worksamp() {} void worksamp::hevy_work(int thread_number, int *pintbar, char *pchar) { int i = 0, j = 0; //------------------------ //一般的な乱数 //------------------------ //srand(time(NULL)); //------------------------ //乱数発生 //------------------------ std::random_device rd; //メルセンヌ・ツイスターの使用 std::mt19937 mt(rd()); int cnt_rnd = mt() % 500 + 1; char buf[100]; double bar=0.0; _id = thread_number; *pintbar = 0; for (i = 0; cnt_rnd > i; i++) { Sleep(5); bar = (double)i/cnt_rnd*100; *pintbar = bar; //----------------------------------- //sprintf使用する際 //プロジェクト>プロパティ>C/C++>全般>SDLチェックをイイエにする。 //----------------------------------- sprintf_s(buf, 100, "pid= %d, i=%3d/%3d(%03.0lf%%)", thread_number, i, cnt_rnd, bar); printf("DLL側の処理 %s\n", buf); strcpy_s(pchar, strlen(buf) + 1, buf); } *pintbar = bar; sprintf_s(buf, 100, "pid= %d, i=%3d/%3d(%03.0lf%%)", thread_number, i, cnt_rnd, bar); strcpy_s(pchar, strlen(buf) + 1, buf); printf("DLL側の処理 pid= %d 処理の終了\n", thread_number); } worksamp work; DLLEXPORT void mydll_main(int thread_number, int *pintbar, char *pchar) { work.hevy_work(thread_number, pintbar, pchar); }
Thread_samp1.h
#pragma once #include <windows.h> #include <string.h> #ifdef __cplusplus #define DLLEXPORT extern "C" __declspec(dllexport) #else #define DLLEXPORT __declspec(dllexport) #endif //------------------------------------------------------------------------------------------------ //VC2015より作成。マルチバイト文字を使用。 //コンパイル時は、プロジェクト>全般>文字セットをUnicode文字からマルチバイト文字に変更した。 //処理をDLL化、thread_numberは作成したID、途中経過の情報をpint,pintbar,pcharより取得 //------------------------------------------------------------------------------------------------ DLLEXPORT void mydll_main(int thread_number, int *pintbar, char *pchar);
作成したDLLは、コンソールアプリケーション、GUIの
実行形式と同じフォルダ に配置して下さい。
C#の場合は、VC++のように、*libや、~hを移動させる必要はありません。
ステップ1 C#コンソールアプリケーションからVC++作成のDLLを呼び出す。
DLL呼び出し部分でVC++と違うのが
intとcharのポインタの型がIntPtrとStringBuilderにかわっていることです。
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices;// DllImportに必要 namespace Csharp_Console { class Program { [DllImport("Thread_samp1.dll", CallingConvention = CallingConvention.Cdecl)] private extern static void mydll_main(int thread_number, IntPtr pintbar, StringBuilder pchar); static void Main(string[] args) { Dirty_Work dirtywork; dirtywork = new Dirty_Work(); dirtywork.dowork(); } } class Dirty_Work { /* //----------------------- 参考:http://rokujo.hatenadiary.com/entry/2015/07/21/135938 参考:http://qiita.com/ask/items/ee2ff5b8706effc0c3d8 参考:Win32 APIやDLL関数に構造体を渡すには? http://www.atmarkit.co.jp/fdotnet/dotnettips/026w32struct/w32struct.html 参考:Win32 APIやDLL関数を呼び出すには? http://www.atmarkit.co.jp/fdotnet/dotnettips/024w32api/w32api.html char* は文字列なので C# 側からは string を渡してやる DLLEXPORT void mydll_main(int thread_number, int *pint, int *pintbar, char *pchar); C#には IntPtr という型があります。これは汎用的なポインタを表す型で、ほぼ void* と同義です。 具体的には、IntPtrの変数に Marshal.AllocHGlobalで必要なサイズのメモリを確保し、それをC++のDLLに渡します。 さらにMarshal.ReadInt16(必要な型によって異なる)などで変換後、 確保したメモリをMarshal.FreeHGlobalで解放する、と3段階の面倒なプロセスを経なければいけません。 short ConvertToShort(string str) { IntPtr buffer = new IntPtr(); buffer = Marshal.AllocHGlobal(2); _ConvertToShort(str, buffer); short sval = Marshal.ReadInt16(buffer); Marshal.FreeHGlobal(buffer); return sval; } //----------------------- */ [DllImport("Thread_samp1.dll", CallingConvention = CallingConvention.Cdecl)] private extern static void mydll_main(int thread_number, IntPtr pintbar, StringBuilder pchar); public Dirty_Work() { } //mydll_main public void dowork() { const int NUM = 1; int[] arr = new int[NUM]; // アンマネージ配列のメモリを確保 IntPtr pintbar = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * NUM); //文字列 StringBuilder pchar = new StringBuilder(); // 引数でポインタを渡す mydll_main(0, pintbar, pchar); // マネージ配列へコピー Marshal.Copy(pintbar, arr, 0, NUM); // アンマネージ配列のメモリを解放 Marshal.FreeCoTaskMem(pintbar); Console.Write("from dll int "); foreach (int n in arr) { Console.Write(n + " "); } Console.WriteLine("\n"); Console.WriteLine("from dll str = {0}", pchar); } }//class }
実行すると以下のような感じ
ステップ2 C#のGUIからVC++作成のDLLを呼び出す。
ステップ1ではC#からDLLの呼び出し動作テストをしたが
ステップ2では、コンソールアプリケーションではない
スレッドでもない場合のDLLの呼び出しテスト
buttron2を押した際の処理は太字部分。
Dirty_Workクラスはbutton1と共に共通。
Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Runtime.InteropServices;// DllImportに必要 using System.Threading; //スレッド namespace CsharpThread { public partial class Form1 : Form { //時間 [DllImport("winmm.dll", EntryPoint = "timeGetTime")] public static extern long timeGetTime(); //スレッドで処理するクラス Dirty_Work Dirty_Work; //スレッド private Thread[] thread_work; //スレッド数 public const int MAX_THREAD = 5; public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { //DLLの関数に投げるintのポインタ IntPtr[] pintbar = new IntPtr[MAX_THREAD]; //DLLの関数に投げるcharのポインタ StringBuilder[] pchar = new StringBuilder[MAX_THREAD]; //autobaibai.set(textBox14.Text, textBox13.Text, txt); thread_work = new Thread[MAX_THREAD]; progressbar_reset();//プログレスバーリセット //------------------------------------------------------- //thread.Suspend(); // スレッドの一時停止 //thread.Resume(); // スレッドの再開 //thread.Join(); // スレッドの処理が終了するまで待つ //thread.Abort(); // スレッドの強制終了 //------------------------------------------------------- //----------------------------- //フォアグラウンド・スレッドとはそのスレッドが動作している限り、 //アプリケーションが終了しないスレッドのことである。 //そして、この逆が「バックグラウンド・スレッド」になる。 //バックグラウンド・スレッドが動作中にメインのスレッドが終了すれば、 //バックグラウンド・スレッドは強制的に終了し、アプリケーションも終了する。 //----------------------------- for (int i = 0; MAX_THREAD > i; i++) { const int NUM = 1; int[] arr = new int[NUM]; // アンマネージ配列のメモリを確保 pintbar[i] = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * NUM); //文字列 pchar[i] = new StringBuilder(); Dirty_Work = new Dirty_Work(i, pintbar[i], pchar[i]); thread_work[i] = new Thread(Dirty_Work.dowork); thread_work[i].IsBackground = true; thread_work[i].Start(); } int cnt = 0; //スレッドの途中経過をモニター for (int i = 0; MAX_THREAD > i; i++) { Stoptime(100); if (thread_work[i].IsAlive) { const int NUM = 1; int[] arr = new int[NUM]; // マネージ配列へコピー Marshal.Copy(pintbar[i], arr, 0, NUM); Console.WriteLine("c########## pid={0}, {1} {2}\n", i, arr[0], pchar[i]); progressbar_show(i, arr[0]); } else { //スレッド終了 cnt++; if (cnt == MAX_THREAD) break; } } Console.WriteLine("button1_Click()\n"); } public void progressbar_reset() { //コントロールを初期化する progressBar1.Minimum = 0; progressBar1.Maximum = 100; progressBar1.Value = 0; label1.Text = "0"; progressBar2.Minimum = 0; progressBar2.Maximum = 100; progressBar2.Value = 0; label2.Text = "0"; progressBar3.Minimum = 0; progressBar3.Maximum = 100; progressBar3.Value = 0; label3.Text = "0"; progressBar4.Minimum = 0; progressBar4.Maximum = 100; progressBar4.Value = 0; label4.Text = "0"; progressBar5.Minimum = 0; progressBar5.Maximum = 100; progressBar5.Value = 0; label5.Text = "0"; } public void progressbar_show(int i, int bar) { switch (i) { case 0: progressBar1.Value = bar; label1.Text = bar.ToString() + "%"; break; case 1: progressBar2.Value = bar; label2.Text = bar.ToString() + "%"; break; case 2: progressBar3.Value = bar; label3.Text = bar.ToString() + "%"; break; case 3: progressBar4.Value = bar; label4.Text = bar.ToString() + "%"; break; case 4: progressBar5.Value = bar; label5.Text = bar.ToString() + "%"; break; } } public void Stoptime(long st) { long lngst; lngst =timeGetTime(); while (timeGetTime() - lngst < st) { Application.DoEvents(); } } //スレッドなしの場合のテスト private void button2_Click(object sender, EventArgs e) { const int NUM = 1; int[] arr = new int[NUM]; // アンマネージ配列のメモリを確保 IntPtr pintbar = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * NUM); //文字列 StringBuilder pchar = new StringBuilder(); Dirty_Work = new Dirty_Work(0, pintbar, pchar); Dirty_Work.dowork(); } } class Dirty_Work { [DllImport("Thread_samp1.dll", CallingConvention = CallingConvention.Cdecl)] private extern static void mydll_main(int thread_number, IntPtr pintbar, StringBuilder pchar); private int _thrednumber; // アンマネージ配列のメモリを確保 IntPtr _pintbar; //文字列 StringBuilder _pchar; public Dirty_Work(int thrednumber, IntPtr pintbar, StringBuilder pchar) { _thrednumber = thrednumber; _pintbar = pintbar; _pchar = pchar; } public void dowork() { const int NUM = 1; int[] arr = new int[NUM]; /* const int NUM = 1; int[] arr = new int[NUM]; // アンマネージ配列のメモリを確保 _pintbar = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * NUM); //文字列 _pchar = new StringBuilder(); */ //DLLの関数 mydll_main(_thrednumber, _pintbar, _pchar); // マネージ配列へコピー Marshal.Copy(_pintbar, arr, 0, NUM); // アンマネージ配列のメモリを解放 Marshal.FreeCoTaskMem(_pintbar); Console.Write("from dll int "); foreach (int n in arr) { Console.Write(n + " "); } Console.WriteLine("\n"); Console.WriteLine("from dll str = {0}", _pchar); } }//class }
button2を押すと、出力は以下のような感じになる。
ステップ3 C#のGUIからVC++作成のDLLをマルチスレッドで呼び出す。
button1を押した際の処理。
ソースコードはステップ2と同じ。
スレッドの途中経過をプログレスバーに表示している。
注目点は
thread_work[i].IsBackground = true;
thread_work[i].IsAlive
IsBackgroundプロパティをTrueにすると、アプリケーションが閉じたら
スレッドのプロセスを強制終了させる。
IsAliveプロパティは、スレッドの生存確認。
C#からDLL呼び出す際、
ポインタを使うのが面倒くさい処理が必要となっているのが
ポイントだろうと思う。
やはり、全てVC++で作ったほうがスッキリするのだろうか
でもGUIの作りやすさは捨てがたい。・・・・
あと、構造体も定義出来るみたいだが、調べるのも面倒くさいw。
実際に動かすと、
終了時に100%で終わらせるようにしたかったが
中途半端な数字で終わるので気持ちが悪い・・・。
まあ、とりあえず以上で
VC++でDLL作成し、C#側でマルチスレッドを動かすことが出来ることを
確認出来た。
以上。