クラスにスレッドを含める

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
shiro4ao
記事: 224
登録日時: 13年前
住所: 広島

クラスにスレッドを含める

#1

投稿記事 by shiro4ao » 13年前

WinsockAPIのラッパークラスのようなものをつくろうとしています。
受信部分をスレッドにしようとしたのですが、
以下のようなエラーが出てきました。
WinVista Borland C++ Compilerにてコンパイルしました
====以下エラー======================================================================
エラー E2034 serv.cpp 76:
 'unsigned long (__stdcall * (_closure )(void *))(void *)' 型は
 'unsigned long (__stdcall *)(void *)' 型に変換できない
  (関数 TcpListener01::StartRecv() )
エラー E2342 serv.cpp 76: パラメータ 'lpStartAddress' は
 unsigned long (__stdcall *)(void *) 型として定義されているので
 void は渡せない(関数TcpListener01::StartRecv() )
===エラーここまで====================================================================

メンバ関数がスレッドのエントリポイントとして使えないことが原因
だと考えています。この問題を、解決する方法はありますでしようか?
それとも設計が根本からおかしいのでしょうか?

コード:

#include    <stdio.h>
#include    <conio.h>
#include    <winsock2.h>


class TcpListener01 {
public:
	WSADATA wsaData;
	DWORD dwParam;
	struct  sockaddr_in addr;
	struct  sockaddr_in client;
	SOCKET sock;
	SOCKET ClientSock;
	char port[10],target[2000];
	HANDLE hThread;

	int InitWSAdata(){
		if(WSAStartup(MAKEWORD(2,0), &wsaData)==0)return -1;
		return 0;
	}
	
	int CreateSocket(){
	    sock= socket(AF_INET,SOCK_STREAM,0);
    		if (sock == INVALID_SOCKET)return -1;
	}

	SOCKET Accept(char *port){
		int nport;
		nport=atoi(port);
		if(nport==0)return -1;
		SOCKET s;

    		addr.sin_family = AF_INET;
    		addr.sin_port = htons(nport);
    		addr.sin_addr.S_un.S_addr = INADDR_ANY;
    		bind(sock, (struct sockaddr *)&addr, sizeof(addr));
    		listen(sock, 5);
    		int len = sizeof(client);
    		ClientSock = accept(sock, (struct sockaddr *)&client, &len);

		return 0;
	}
	


 	 DWORD WINAPI  RecvThread(LPVOID lpvoid) {
		char buf[4096];
		int ret,n,i,id;
		id=(int)lpvoid;
		printf("ThreadOpened\n");
		BOOL Flag=TRUE;

		while(Flag){

			ret=recv(ClientSock,buf,2048,0);
	
			if(ret>0){
				//ゆくゆくはここはコールバックにしたい。
				printf("%s\n",buf);
			}
		
			if(ret==0){
				closesocket(ClientSock);
				Flag=FALSE;
			}
		}

		ExitThread(TRUE);
	}



	void StartRecv(){
		//スレッドを起こす
		hThread=CreateThread(NULL , 0 , RecvThread,(LPVOID)this, 0 , &dwParam);
	}

	//使わない
	int Recv(char *buf,int size,int Flag){
		int ret;
		ret=recv( ClientSock,buf,size,Flag);
		return ret;
	}


	int Send(char *buf,int size,int Flag){
		int ret;
		ret=send(ClientSock,buf,size,Flag);
		return ret;
	}

	int CloseClientSock(){
		closesocket(ClientSock);
	}

	int CloseServSock(){
		closesocket(sock);
	}

} TcpListener01 ;





int main(void)
{ 
    class TcpListener01 serv;
    char    buf[1024],rec[1024];    

    serv.InitWSAdata();
    serv.CreateSocket();
    serv.Accept("5555");

    printf("クライアントがきました");
for(;;)
{
    memset(buf,'\0',sizeof(buf));
    printf("送信メッセージ→");
    gets(buf);
    strcat(buf,"\0");
    serv.Send(buf,strlen(buf),0);
    serv.StartRecv();
    strcat(buf,"\0");
    printf("受信メッセージ→%s\n",buf);

}
    // TCPセッションの終了
    serv.CloseClientSock();
    serv.CloseServSock();

    // winsock2の終了処理

    WSACleanup();
    return 0;
}


最後に編集したユーザー shiro4ao on 2011年2月02日(水) 11:04 [ 編集 1 回目 ]

maru
記事: 150
登録日時: 13年前

Re: クラスにスレッドを含める

#2

投稿記事 by maru » 13年前

shiro4ao さんが書きました:メンバ関数がスレッドのエントリポイントとして使えないことが原因
だと考えています。この問題を、解決する方法はありますでしようか?
原因はそのとおりでしょうね。クラスメンバー関数は元々インスタンスオブジェクトへのポインタが隠れ引数として渡されています。
(そのポインタが this です。)
したがって CreateThread 関数の第三引数である LPTHREAD_START_ROUTINE 型ではないのでエラーになります。
これを回避するには対象を静的関数にしてしまいます。

コード:

static DWORD WINAPI  RecvThread(LPVOID lpvoid) {
// 内部処理
}
静的関数なのでメンバ変数、this ポインタを使えませんが、元々 this を引数に渡しているんだから問題はないと思います。
クラスメンバを使用する場合には引数をクラスポインタにキャストして使用します。

元々 this ポインタが引数になっているんだからスレッド関数を引数なしの関数として定義したら...
ということで

コード:

DWORD WINAPI  RecvThread(void) {
// 内部処理
}
としてもコンパイルを通すことはできません。

なお、以下の行のキャスト (LPVOID) は冗長です。

コード:

        hThread=CreateThread(NULL , 0 , RecvThread,(LPVOID)this, 0 , &dwParam);

アバター
shiro4ao
記事: 224
登録日時: 13年前
住所: 広島

Re: クラスにスレッドを含める

#3

投稿記事 by shiro4ao » 13年前

>maruさん
ありがとうございます。
スレッドの部分を静的関数にして、ClientSockへは引数をキャストして
アクセスしようとしましたが、以下のようなエラーが出てきました。
===以下エラー========================================================================
エラー E2451 serv.cpp 10: 未定義のシンボル TcpListener01(関数 __stdcall RecvThread(void *) )
エラー E2451 serv.cpp 10: 未定義のシンボル p(関数 __stdcall RecvThread(void *) )
エラー E2188 serv.cpp 10: 式の構文エラー(関数 __stdcall RecvThread(void *) )
===エラーここまで=====================================================================

TcpListener01が見つからないようなのです。
試しに、スレッドの部分をTcpListener01クラスよりあとに書いたら、今度は
ReadThreadが見つからないと言われてしまいました。
何か良い方法はありますでしようか?

コード:

#include    <stdio.h>
#include    <conio.h>
#include    <winsock2.h>

 	 static DWORD WINAPI  RecvThread(LPVOID lpvoid) {
		char buf[4096];
		int ret,n,i,id;

		//クラスポインタにキャストしたのですが…
		TcpListener01* p=(TcpListener01*)lpvoid;


		printf("ThreadOpened\n");
		BOOL Flag=TRUE;

		while(Flag){

			ret=recv(p->ClientSock,buf,2048,0);
	
			if(ret>0){
				//ゆくゆくはここはコールバックにしたい。
				printf("%s\n",buf);
			}
		
			if(ret==0){
				closesocket(p->ClientSock);
				Flag=FALSE;
			}
		}

		ExitThread(TRUE);
	}



class TcpListener01 {
public:
	WSADATA wsaData;
	DWORD dwParam;
	struct  sockaddr_in addr;
	struct  sockaddr_in client;
	SOCKET sock;
	SOCKET ClientSock;
	char port[10],target[2000];
	HANDLE hThread;

	int InitWSAdata(){
		if(WSAStartup(MAKEWORD(2,0), &wsaData)==0)return -1;
		return 0;
	}
	
	int CreateSocket(){
	    sock= socket(AF_INET,SOCK_STREAM,0);
    		if (sock == INVALID_SOCKET)return -1;
	}

	SOCKET Accept(char *port){
		int nport;
		nport=atoi(port);
		if(nport==0)return -1;
		SOCKET s;

    		addr.sin_family = AF_INET;
    		addr.sin_port = htons(nport);
    		addr.sin_addr.S_un.S_addr = INADDR_ANY;
    		bind(sock, (struct sockaddr *)&addr, sizeof(addr));
    		listen(sock, 5);
    		int len = sizeof(client);
    		ClientSock = accept(sock, (struct sockaddr *)&client, &len);

		return 0;
	}
	





	void StartRecv(){
		//スレッドを起こす
		hThread=CreateThread(NULL , 0 , RecvThread,(LPVOID)this, 0 , &dwParam);
	}

	//使わない
	int Recv(char *buf,int size,int Flag){
		int ret;
		ret=recv( ClientSock,buf,size,Flag);
		return ret;
	}


	int Send(char *buf,int size,int Flag){
		int ret;
		ret=send(ClientSock,buf,size,Flag);
		return ret;
	}

	int CloseClientSock(){
		closesocket(ClientSock);
	}

	int CloseServSock(){
		closesocket(sock);
	}

} TcpListener01 ;





int main(void)
{ 
    class TcpListener01 serv;
    char    buf[1024],rec[1024];    

    serv.InitWSAdata();
    serv.CreateSocket();
    serv.Accept("5555");

    printf("クライアントがきました");
for(;;)
{
    memset(buf,'\0',sizeof(buf));
    printf("送信メッセージ→");
    gets(buf);
    strcat(buf,"\0");
    serv.Send(buf,strlen(buf),0);
    serv.StartRecv();
    strcat(buf,"\0");
    printf("受信メッセージ→%s\n",buf);

}
    // TCPセッションの終了
    serv.CloseClientSock();
    serv.CloseServSock();

    // winsock2の終了処理

    WSACleanup();
    return 0;
}



アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 13年前
住所: 東海地方
連絡を取る:

Re: クラスにスレッドを含める

#4

投稿記事 by softya(ソフト屋) » 13年前

maruさんの言われるstatic(静的)関数というのは、クラス内の静的関数という意味だと思いますけど。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
shiro4ao
記事: 224
登録日時: 13年前
住所: 広島

Re: クラスにスレッドを含める

#5

投稿記事 by shiro4ao » 13年前

>softyaさん
ありがとうございます。

スレッドの部分をクラスにクラス内の静的関数にしてみました。
ところが、ClientSockへアクセスするために作った、TcpListener01クラスへのポインタ
のpが使えないようなエラーが出てきました。
何か解決策はありますでしようか?

===以下エラー========
エラー E2231 serv.cpp 24: メンバー 'TcpListener01::p' は
 オブジェクトなしでは使用できない(関数 __stdcall TcpListener01::RecvThread(void *) )
エラー E2231 serv.cpp 32: メンバー 'TcpListener01::p' は
 オブジェクトなしでは使用できない(関数 __stdcall TcpListener01::RecvThread(void *) )
エラー E2231 serv.cpp 40: メンバー 'TcpListener01::p' は
 オブジェクトなしでは使用できない(関数 __stdcall TcpListener01::RecvThread(void *) )
===エラーここまで====

コード:

class TcpListener01 {
public:
	WSADATA wsaData;
	DWORD dwParam;
	struct  sockaddr_in addr;
	struct  sockaddr_in client;
	SOCKET sock;
	SOCKET ClientSock;
	char port[10],target[2000];
	HANDLE hThread;
	TcpListener01* p;                                  //ポインタ

 	 static DWORD WINAPI  RecvThread(LPVOID lpvoid) {
		char buf[4096];
		int ret,n,i,id;

		p=(TcpListener01*)lpvoid;        //クラスポインタにキャストしたのですが…


		printf("ThreadOpened\n");
		BOOL Flag=TRUE;

		while(Flag){

			ret=recv(p->ClientSock,buf,2048,0);
	
			if(ret>0){
				//ゆくゆくはここはコールバックにしたい。
				printf("%s\n",buf);
			}
		
			if(ret==0){
				closesocket(p->ClientSock);
				Flag=FALSE;
			}
		}

~~~~~以下略~~~~~~~~

} TcpListener01 ;


アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 13年前
住所: 東海地方
連絡を取る:

Re: クラスにスレッドを含める

#6

投稿記事 by softya(ソフト屋) » 13年前

staticメンバ関数から、staticではないメンバ変数であるpは見えませんよ。

[追記]
私なら一旦static関数でスレッドの入り口だけを作っておいて、実際に処理するメンバ関数を別に呼び出しますね。

コード:

    TcpListener01* p = (TcpListener01*)lpvoid;
    p->RecvThreadMain();
こうすれば、あとは普通のメンバ関数RecvThreadMain()で処理できます。
(注)RecvThreadMainはpublicなメンバ関数である必要があります。

[さらに追記]
あと余計なおせっかいですが、メンバ変数が何でもかんでもpublicに成っているので、どうしても外部公開する必要性のある物を除いてprivateにしたほうが良いと思います。
それとWSAStartupはクラス外に出すか、クラス自体をシングルトン・パターンにしてWSACleanup();を含めてコンストラクタ/デストラクタで処理するのが良いかと思いますよ。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

maru
記事: 150
登録日時: 13年前

Re: クラスにスレッドを含める

#7

投稿記事 by maru » 13年前

私の書いた内容がちゃんと理解できていなかったんですね。

さらに、
shiro4ao さんが書きました:>スレッドの部分を静的関数にして、ClientSockへは引数をキャストして
アクセスしようとしましたが、以下のようなエラーが出てきました。
===以下エラー========================================================================
エラー E2451 serv.cpp 10: 未定義のシンボル TcpListener01(関数 __stdcall RecvThread(void *) )
エラー E2451 serv.cpp 10: 未定義のシンボル p(関数 __stdcall RecvThread(void *) )
エラー E2188 serv.cpp 10: 式の構文エラー(関数 __stdcall RecvThread(void *) )
===エラーここまで=====================================================================

TcpListener01が見つからないようなのです。
試しに、スレッドの部分をTcpListener01クラスよりあとに書いたら、今度は
ReadThreadが見つからないと言われてしまいました。
何か良い方法はありますでしようか?
これを見るとc/c++の知識も怪しいようです。
・クラス宣言より前の関数でクラスを使用するすることはできない。
・関数宣言より前でその関数を使用することはできない。
これらの問題に対処するためにヘッダファイルに宣言だけを分離しておきます。

コード:

// TcpListener.h
class TcpListener
{
public:
    WSADATA wsaData;
    DWORD dwParam;
    // 以下メンバー変数は省略
    int InitWSAdata();
    int CreateSocket();
    static DWORD WINAPI  RecvThread(LPVOID lpvoid);
    DWORD RecvThreadMain();
    // 以下のメンバー関数は省略
};
別のソースファイルでその関数の実装を書きます。

コード:

// TcpListener.cpp
#include "TcpListener.h"   // 作成したヘッダファイル
int TcpListener::InitWSAdata()
{
    if(WSAStartup(MAKEWORD(2,0), &wsaData)==0)
    {
        return -1;
    }
    return 0;
}

DWORD WINAPI TcpListener::RecvThread(LPVOID lpvoid)
{
    TcpListener01* p = (TcpListener01*)lpvoid;
    // メンバ関数の呼び出しを行う
    p->RecvThreadMain(); // softyaさんのコードを持ってきました。
}

DWORD TcpListener::RecvThreadMain()
{
// スレッドで行う処理の実装
}
// 以下、他のメンバー関数
また、クラス宣言の最後の余分なものが付いています。

コード:

class TcpListener01 {
// 省略
} TcpListener01; // ここ!
これはTcpListener01型のクラスオブジェクトをTcpListener01と言う名前でグローバルに宣言することになります。
そういう意図で書いたのであればいいのですが、多分違いますよね。これをつけたままヘッダファイルにして複数のソースファイルでインクルードすると、また別のエラーで悩まされることになります。
とりましょう。

コード:

class TcpListener01 {
// 省略
};
[hr][追加]
main関数の先頭に

コード:

    class TcpListener01 serv;
となっているのは class をつけないとエラーになったからではないでしょうか?
その原因は、すべて、クラス宣言後の文字列です。
本来この変数定義は class をつける必要がありません。

コード:

    TcpListener01 serv;
しかし、クラス宣言の最後に同名の変数が宣言されているため、TcpListener01 がクラス名と変数名か区別が付かないのでエラーになります。

アバター
shiro4ao
記事: 224
登録日時: 13年前
住所: 広島

Re: クラスにスレッドを含める

#8

投稿記事 by shiro4ao » 13年前

返信が遅くなりすみません。

>softyaさん
 どの変数が見えて、どの変数が見えないのかが理解がよく出来ていませんでした。
 もうすこし勉強してみようと思います。

 >こうすれば、あとは普通のメンバ関数RecvThreadMain()で処理できます。
   なるほど、いろいろなテクニックがあるのですね。参考になりました。

 なんでもpuclicになっているのは、特に考えていなかったからですが、
 WSAStartupが扱いづらそうだったので、シングルトン・パターンについても勉強してみようと思います。

>maruさん
 クラス以前に、曖昧な部分がたくさんあるようです。
 はじめは混乱しましたが、丁寧な解説でよくわかりました。ありがとうございます。

無事に実装することができましたが、よく理解出来ていない部分が多いのでこれから勉強していこうと思います。
これにて解決にさせていただきたいと思います。
ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る