barus's diary

とても真面目なblogですにゃ

マルチスレッド処理(thread)VC++でDLL作成しVC++で利用  for VS2015 Express for Desktop

 マルチスレッドについて調べて試したのでメモる。

 

VC++for VS2015 Express for Desktop(開発環境は無料)

を使いプロジェクトを2つ作成した。

 

Thread_samp1   ・・・・プロジェクトでDLL作成する

        Thread_samp1.h

                             Thread_samp1.cpp

                             その他のファイルはデフォ

        f:id:hatakeka:20170608080029p:plain

usedll        ・・・・上記のDLL利用しマルチ処理。

              プロジェクトでコンソールアプリケーション作成する

        usedll.cpp

                             Thread_samp1.hは、Thread_samp1と同じヘッダーファイル

                             その他のファイルはデフォ

        f:id:hatakeka:20170608075600p:plain

 

usedll側で、DLL側で乱数発生させたダミー処理を10個走らせて

プログレスバーを表示させて100%になったら終了。 

実行は、以下のような感じ。

  

f:id:hatakeka:20170607173352p:plain

 

 

スレッドの途中経過を取得するのに

参照渡しで取得しているが、

もっとうまいやり方があるかもしれない。

 

 

他の方法として

ファイルを吐き出して、メインからファイルを読み込んで

途中経過の情報を得るという方法も思いついたけれども

途中で処理が止まって、再開する際は一時ファイルを吐き出して

おくのもいいかもしれない。

が、今回はファイルを吐き出して、情報を得る方法は試していない。

 

 

 

DLL、コンソールアプリケーション共に

コンパイル時は、マルチバイト文字を利用した。

 

変更方法

プロジェクト>全般>文字セットらへんで

Unicode文字からマルチバイト文字に変更する。

 

f:id:hatakeka:20170608080447p:plain

 

なぜこんなことするかというと・・・

世界中の人が英語だけ使うのではないのでUnicode文字列が生まれた。

 

恐らくこういう流れだと思う。

 

英語圏でコンピューターが産まれる。

 ANSI米国国家規格協会で文字コードが規定される。

  ↓

英語圏以外でもコンピューターが普及するようになる。

 それぞれの国々で自国の文字コードが決まる。

 日本だとJIS規格。(shift-jis等)

  ↓

③それぞれの国が勝手に文字コードを作成していた。

 アラビア語とか中国語とか、インターネットで繋がるようになり

 いろいろ不具合が起きるようになる。(想像)

 急いで、国際的なルールを作りましょう。

 ってなり Unicodeが誕生(爆誕)

 たしか、2000年頃ですかね。

 

ってな具合で、文字型が沢山誕生した(と思う。すげー適当

 

本当はUnicode文字列の開発環境でやるといいかもしれませんが

昔ながらの関数が使いづらいので、昔風のマルチバイト文字が

個人的に使いやすいのでマルチバイト文字をココでは使用する。

 

 

char マルチバイト文字型
WCHAR ワイド文字型
TCHAR 汎用文字型

 

LPSTR char*
LPCSTR const char*
LPWSTR WCHAR*
LPCWSTR const WCHAR*
LPTSTR TCHAR*
LPCTSTR const TCHAR*

 

参考

VC++メモ:マルチバイト文字列(char*)とワイド文字列(WCHAR*)の変換 | フィロの村note

 

以上のように文字列型だけで沢山あるので嫌になりますね

それぞれの変換とか考え出すと深淵の闇にハマるので

軽く受け流しましょう・・・(;´Д`)

 

 

マルチスレッド処理の簡単なサンプル

 

いきなり、DLLを利用したマルチスレッド作成の前に単純な

マルチスレッドサンプルを作成する。

コンソールアプリケーションでプロジェクトを作成する

参考URL http://qiita.com/termoshtt/items/d3cb7fe226cdd498d2ef

         

   f:id:hatakeka:20170608082218p:plain

 

// hidouki.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <vector>
#include <future>
#include <iostream>

using namespace std;

double long_calc();
void do_another_things();
/*
C++11では<future>ヘッダが導入され、
簡単に非同期の処理が実装できるようになった。
以下では用途毎に実装方法をまとめる。
thread間での同期の話はせず、あくまで完全に非同期な処理についてまとめる。
*/
int main(int argc, char const *argv[]) {


	/*
	戻り値が必要ない場合(Thread-per-message pattern)
	とりあえず非同期に実行できればいい場合。
	実行時間を短縮するために複数の独立な処理を並列に実行するなどの応用が考えられる。
	std::threadを使用する。最も基本的な使い方は以下の通りである:

	std::threadの引数に実行したい関数を渡す。
	関数に引数を与えたい場合は、std::thread(func, arg)のように行う。
	プログラムが終了するまでにはjoin()しないといけない。
	std::threadの引数のlambda式はキャプチャする事もできる:
	*/
	double a;
	auto th1 = std::thread([&a] { a = long_calc(); });
	do_another_things();
	th1.join();
	std::cout << a << std::endl; // 1.0


	//------------------------------------------------
	//std::vector<std::thread>のようにコンテナに入れる事もできる。
	//------------------------------------------------
	std::vector<int> v(10);
	std::vector<std::thread> threads;
	for (int i = 0; i < 10; ++i) {
		threads.push_back(std::thread([i, &v] { v[i] = i * i; }));
	}
	for (std::thread &th : threads) {
		th.join();
	}
	for (int i : v) {
		std::cout << i << std::endl;
	}

	//------------------------------------------------
	//結果を取得したい場合
	//上述の方法でも戻り値を参照でキャプチャした変数に代入する事ができているが、
	//これでは不便な場合も多い。
	//そこで登場するのがstd::asyncである。
	//
	//resultの型はstd::future<T>(Tはlong_calc()の戻り値の型)である。
	//result.get()ではじめて作成したスレッドをjoin()するので、
	//do_another_things()はthreadの生成直後に(long_calc()の実行を待たずに)実行される。
	//------------------------------------------------
	auto result = std::async(std::launch::async, [] { return long_calc(); });
	do_another_things();
	std::cout << result.get() << std::endl;


	//------------------------------------------------
	//非同期処理をどうやって実行するか(Policy)
	//^
	//^第一引数のstd::launch::asyncはpolicyを指定しており、
	//^別スレッドを開始してlong_calc()を計算する、という指定である。
	//^Policyは次の4通りの指定ができる
	//^std::launch::async : 別スレッドで実行
	//^std::launch::deferred : 遅延評価
	//^std::launch::async | std::launch::deferred : 上記のいずれか(実装依存)
	//^指定なし : 両方指定した場合と同様
	//^std::launch::deferredを指定した場合は単に遅延評価になる。
	//^つまり最初にresult.get()が呼ばれたタイミングでlong_calc()を評価する。
	//^次回以降にresult.get()が呼ばれたら最初の時に計算した値を返す。
	//^std::async(std::launch::async | std::launch::deferred, func, arg)のように両方指定する場合、
	//^あるいは単にstd::async(func, arg)のように省略した場合は、
	//^async/deferredのどちらになるか実装依存である。
	//^vectorを初期化する場合は以下のようにする:
	//--------------------------------------------------
	std::vector<std::future<int> > v2;
	for (int i = 0; i < 10; ++i)
	{
		v2.push_back(std::async([i] { return i * i; }));
	}
	for (auto &val : v2)
	{
		std::cout << val.get() << std::endl;
	}

	return 0;
}

double long_calc()
{
	int i, j, k;
	int cnt = 3;
	for (i = 0; cnt>i; i++)
		for (j = 0; cnt>j; j++)
			for (k = 0; cnt>k; k++)
				cout << "long_calc() i=" << i << ",j=" << j << ",k=" << k << endl;


	return 11;

}

void do_another_things()
{
	int i, j, k;
	int cnt = 2;
	for (i = 0; cnt>i; i++)
		for (j = 0; cnt>j; j++)
			for (k = 0; cnt>k; k++)
				cout << "do_another_things() i=" << i << ",j=" << j << ",k=" << k << endl;


}


実行すると以下のような感じになる。f:id:hatakeka:20170608082543p:plain

 

 

 


以上を踏まえて、マルチスレッドのDLL化をしてみた。 
 

なお、古い開発環境では、メルセンヌ乱数とかが使えないので

最新の開発環境推奨です。

 

sprintfとかで、エラー怒られる場合。

//プロジェクト>プロパティ>C/C++>全般>SDLチェックをイイエにする。

にして下さい。

 

脆弱性が生まれるから推奨されていない

かと思いますが、個人使用で使用する範囲は構わないでしょう。

  

 

PlayStation VR PlayStation Camera同梱版

PlayStation VR PlayStation Camera同梱版

 

 

 

.

 プロジェクトでDLL作成する。

 

f:id:hatakeka:20170608080029p:plain

 

 

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 *pint, int *pintbar, char *pchar);
};

worksamp::worksamp() {}
worksamp::~worksamp() 
{
}

void worksamp::hevy_work(int thread_number, int *pint, int *pintbar, char *pchar)
{
	int i = 0, j = 0;

	//------------------------
	//一般的な乱数 
	//------------------------
	//srand(time(NULL));

	//------------------------
	//乱数発生
	//------------------------
	std::random_device rd;
	//メルセンヌ・ツイスターの使用
	std::mt19937 mt(rd());



	int cnt_rnd = mt() % 10000 + 1;

	char buf[100];
	
	
	double bar=0.0;

	*pint = thread_number; //スレッド番号
	_id   = thread_number;

	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,100, buf);

	}


	printf("DLL側の処理 pid= %d 処理の終了\n", thread_number);

}


worksamp work;


DLLEXPORT void  mydll_main(int thread_number, int *pint, int *pintbar, char *pchar)
{
	work.hevy_work(thread_number, pint, 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 *pint, int *pintbar, char *pchar);

 

 

コンパイルすると以下のようなファイルが出来る。

 

f:id:hatakeka:20170608082809p:plain

 

ここまでがDLL

 

以下は、上記で作成したDLLの利用

プロジェクトはコンソールアプリケーションで作成

 f:id:hatakeka:20170608075600p:plain

 

DLL作成した際に出来たライブラリーファイルThread_samp1.libを使用するので

メニュウより

プロジェクト>usedllのプロパティ>リンカ―>入力>追加の依存ファイル

 

f:id:hatakeka:20170608083031p:plain

 

追加の依存ファイルの編集でThread_samp1.libを追加する。

 

f:id:hatakeka:20170608083225p:plain

 

Thread_samp1.lib と Thread_samp1.h を usedll.cppと同じフォルダに置く。

 

f:id:hatakeka:20170608083433p:plain

 

作成したDLLファイル(Thread_samp1.dll)は実行形式(usedll.exe)と同じフォルダに置く。

f:id:hatakeka:20170608083639p:plain

 

DLL側の修正した際、

Thread_samp1.dll    ・・・DLL

Thread_samp1.lib    ・・・ライブラリー

Thread_samp1.h     ・・・ヘッダーファイル

は、usedll側のプロジェクトに毎回上書きする必要がある。

 

 

 

usedll.cpp

 

// usedll.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "Thread_samp1.h"

#include <string>
#include <iostream>
#include <vector>
#include <windows.h> 
#include <future> //thread
#include <fstream>      // std::ifstream



using namespace std;
void work_dll(int thread_number, int *pint, int *pintbar, char *pchar);

#define MAX_THREAD 10 //スレッド数

int main()
{
	int i=0;
	
	int p[MAX_THREAD];
	int pbar[MAX_THREAD];
	char *str[MAX_THREAD];


	std::vector<std::thread> threads;
	for (i = 0; i < MAX_THREAD; ++i)
	{
		str[i] = new char[100]; str[i][0] = '\0';
		threads.push_back(std::thread(work_dll, i, &p[i],  &pbar[i], str[i]));

	}
	

	//------------------------
	//スレッドの途中情報
	//------------------------
	for (;;)
//	for (std::thread &th : threads)
	{
		Sleep(150);
		int endcnt = 0;
		for (i = 0; i < MAX_THREAD; ++i)
		{
			string buff;
			int process = (pbar[i] / 10)+1;
			for (int j = 0; process > j; j++)buff += "|";//処理の進行状況
			printf("呼び出し側 pid=%d p=%d, (%03d) %s\n", i, p[i], pbar[i]+1, buff.c_str());

			if (process == 10)endcnt++;
		}
		//終了判断
		if (endcnt == MAX_THREAD)break;
	}

	for (std::thread &th : threads) {
		th.join(); //スレッドの処理が終了するまで待機
	}

    return 0;
}


void work_dll(int thread_number, int *pint, int *pintbar, char *pchar)
{

	HINSTANCE hDLL;
	if ((hDLL = LoadLibrary("Thread_samp1.dll")) == NULL)printf("LoadLibrary is failed.\n");
	

	//DLLの利用
	mydll_main(thread_number, pint, pintbar, pchar);


}


 

 

 最新の開発環境ではマルチスレッドが簡単に作れました。

 

 

 

以上。