別スレッド間でのオブジェクトの操作

ActiveBasicでのプログラミングでわからないこと、困ったことなどがあったら、ここで質問してみましょう(質問を行う場合は、過去ログやWeb上であらかじめ問題を整理するようにしましょう☆)。
返信する
メッセージ
作成者
Sunshine

別スレッド間でのオブジェクトの操作

#1 投稿記事 by Sunshine »

 違うスレッドで1つのオブジェクトを共有することができますか?
 ゲームにBGMをつけたいのですが、オブジェクトを作成したスレッドとは別のスレッドからBGM用のオブジェクトを操作することができませんでした。
 ゲームの性質上、ゲーム部分とウィンドウ部分では別スレッドを使う必要があると思ったので。
 やりたいことはこんな感じです。


     ┌――→BGM再生用のオブジェクト←―┐
     │                  │
     │                  │
  ゲームのスレッド           ウィンドウ制御スレッド
  (曲変更などのメッセージを送る)  (BGMを作成したスレッド)
                    (Notifyで制御を返す)
イグトランス
記事: 899
登録日時: 2005年5月31日(火) 17:59
お住まい: 東京都
連絡する:

#2 投稿記事 by イグトランス »

ほかにはCreateThread()の4番目の引数で渡すなどの方法もありますが、良くも悪くもグローバル変数にしてしまうのが一番簡単です。
下手にグローバル変数を使わないように作るより、今のABではグローバル変数を使ってしまうほうが楽です。

ただし、複数のスレッドが1つのオブジェクトに同時にアクセスすることの無い様、クリティカルセクションなどで排他制御する必要があります。
淡幻星
記事: 183
登録日時: 2005年7月19日(火) 07:02
お住まい: 宮城県
連絡する:

排他制御のこと

#3 投稿記事 by 淡幻星 »

横スレ&おせっかい、失礼します。
クリティカルセクションでは、えらい苦労したのでコメントしてみます。
私の場合は、Ver2.xで使おうとしてCRITICAL_SECTION構造体の定義が
分からなくて苦労したってことなんですが・・・、Ver4.xでも
クリティカルセクションのApiの説明はHelpに載っていなかったようなので、
使い方を書いてみます(宣言自体はapi_system.sbpでされてますね)。

プログラムの最初(CreateWnd辺り)で

コード: 全て選択

Dim lpCriticalSection As CRITICAL_SECTION 'グローバル変数として定義
InitializeCriticalSection( lpCriticalSection )
を実行してクリティカルセクションを作成します。
途中、複数スレッドから共有されるグローバル・オブジェクトへアクセスする前に

コード: 全て選択

EnterCriticalSection( lpCriticalSection )
を実行して他スレッドからのアクセスを禁止し、アクセスが終わったら

コード: 全て選択

LeaveCriticalSection( lpCriticalSection )
を実行してアクセス禁止を解除します。
プログラムの最後(DestroyWnd辺り)で

コード: 全て選択

DeleteCriticalSection( lpCriticalSection )
を実行してクリティカルセクションを破棄すればOKです。

これによって、複数スレッドからの同時アクセスによる、
不測の動作やアプリケーション・エラーを防げます。

すでにご存知でしたら、でしゃばり、失礼しました。
ゲスト

#4 投稿記事 by ゲスト »

 グローバルオブジェクトはどうやって作るのですか?

  MainWnd.sbpの先頭部に Dim BGM As BACKGROUNDMUSIC '音楽再生用のクラス
        ↓
  MainWnd_Create関数内でスレッド作成

としてみましたが、うまくいきませんでした。
7
記事: 473
登録日時: 2005年5月31日(火) 18:51
お住まい: 新潟県
連絡する:

#5 投稿記事 by 7 »

グローバルオブジェクトはNew演算子を使ってオブジェクトを生成します。

コード: 全て選択

Dim BGM As *BACKGROUNDMUSIC '音楽再生用のクラスを示すポインタ

Sub MainWnd_Create(ByRef CreateStruct As CREATESTRUCT)
'ウィンドウ作成時にグローバルオブジェクトを作成
	BGM=New BACKGROUNDMUSIC()
End Sub

Sub MainWnd_Destroy()
'ウィンドウ破棄時にグローバルオブジェクトを破棄
	Delete BGM
End Sub
使い方はBGM->hogeになります。
Sunshine

#6 投稿記事 by Sunshine »

グローバルオブジェクトはNew演算子を使ってオブジェクトを生成します。
 ありがとうございます。
 グローバルオブジェクトはできました。
 しかし、音楽の再生、変更はできませんでした。
 どうやらmciSendCommand関数が失敗するようです。
 どうすればいいでしょうか。
 ちなみに、エラーメッセージを取得したところ、「指定されたMIDIデバイスは既に使用されています。解放されるのを待ち、再実行してください。」と出ました。
ナナシ
記事: 11
登録日時: 2005年6月19日(日) 21:21
お住まい: 石川県
連絡する:

#7 投稿記事 by ナナシ »

MCI系の関数は初期化から解放まで同じスレッドで処理しないと
ダメだった気がします。


         BGM再生用のオブジェクト←―┐
                        │
                        │
  ゲームのスレッド――――――――――→ウィンドウ制御スレッド
  (曲変更などのメッセージを送る)  (BGMを作成したスレッド)
                    (Notifyで制御を返す)


こういう感じで、ウィンドウ制御スレッドが依頼を受け付けてBGM操作をする
ようにはできませんか?
Sunshine

#8 投稿記事 by Sunshine »

こういう感じで、ウィンドウ制御スレッドが依頼を受け付けてBGM操作をする
ようにはできませんか?
 ウィンドウイベントでは変数の変動は取得できなさそうです……。
 ABP ファイルのDo~Loop内に変数の変動をチェックする部分を設ければいいのかな、と思ったのですが、ウィンドウメッセージがないと高速で無限ループするため、クリティカルセクションをつけていたらずっとほかのスレッドがその変数にアクセスできないということになりそうです。
 どうすればいいでしょうか。
ゲスト

#9 投稿記事 by ゲスト »

ウィンドウメッセージを送ることにより、ウィンドウ処理スレッド側に
MCIを操作させるサンプルです。
マルチメディアAPIを使用可能にしたノーマルEXEプロジェクトで
MainWnd.sbpを編集します。

コード: 全て選択


'-----------------------------------------------------------------------------
'  イベント プロシージャ
'-----------------------------------------------------------------------------
' このファイルには、ウィンドウ [MainWnd] に関するイベントをコーディングします。
' ウィンドウ ハンドル: hMainWnd
' TODO: この位置にグローバルな変数、構造体、定数、関数を定義します。
Const MM_MCICONTROL	= (WM_APP+1)

Type MCICONTROL
	dwMciID As DWord
	uMsg As DWord
	fdwCommand As DWord
	lpParam As VoidPtr
End Type

'-----------------------------------------------------------------------------
' ウィンドウメッセージを処理するためのコールバック関数

Function MainWndProc(hWnd As DWord, dwMsg As DWord, wParam As DWord, lParam As DWord) As DWord
	' TODO: この位置にウィンドウメッセージを処理するためのコードを記述します。
	Dim pmc As *MCICONTROL
	Dim tid As DWord

	Select Case dwMsg
	Case WM_CREATE
		CreateThread(ByVal NULL, 0, AddressOf(SubThread), NULL, 0, VarPtr(tid))
	Case MM_MCICONTROL
		pmc = lParam
		mciSendCommand(pmc->dwMciID, pmc->uMsg, pmc->fdwCommand, ByVal pmc->lpParam)
	Case MM_MCINOTIFY
		' MM_MCINOTIFYの処理
	End Select
	' イベントプロシージャの呼び出しを行います。
	MainWndProc=EventCall_MainWnd(hWnd,dwMsg,wParam,lParam)
End Function
	Dim mcic As MCICONTROL

Sub mciSendCommand2(dwMciID As DWord, uMsg As DWord, fdwCommand As DWord, lpParam As VoidPtr)

	mcic.dwMciID	= dwMciID
	mcic.uMsg		= uMsg
	mcic.fdwCommand	= fdwCommand
	mcic.lpParam	= lpParam

	SendMessage(hMainWnd, MM_MCICONTROL, 0, VarPtr(mcic))
End Sub

'-----------------------------------------------------------------------------
' ここから下は、イベントプロシージャを記述するための領域になります。

Sub MainWnd_Destroy()
	Music_DestroyObjects()
	PostQuitMessage(0)
End Sub

Sub SubThread()
	Dim mop As MCI_OPEN_PARMS
	Dim mpp As MCI_PLAY_PARMS

	Sleep(5000)
	
	mop.lpstrDeviceType  = MCI_DEVTYPE_SEQUENCER
	mop.lpstrElementName = "battle.mid"
	mciSendCommand2(NULL, MCI_OPEN, MCI_WAIT or MCI_OPEN_TYPE or MCI_OPEN_TYPE_ID or MCI_OPEN_ELEMENT, VarPtr(mop))

	mpp.dwCallback = hMainWnd
	mciSendCommand2(mop.wDeviceID, MCI_PLAY, MCI_NOTIFY, VarPtr(mpp))

	Sleep(10000)
	
	mciSendCommand2(mop.wDeviceID, MCI_STOP, MCI_WAIT, NULL)
	mciSendCommand2(mop.wDeviceID, MCI_CLOSE, MCI_WAIT, NULL)

	SendMessage(hMainWnd, WM_CLOSE, 0, 0)
End Sub
このサンプルでは起動して5秒後にbattle.midを演奏し、10秒後終了します。
MIDIファイルは適当なものをリネームするなどしてご用意ください。

ウィンドウメッセージを介すため、MIDIのBGMくらいは平気ですが
アクションゲームの効果音の再生では反応に遅れが出るかもしれません。
(そこまでするならDirectXの出番と思われますが。)
ナナシ
記事: 11
登録日時: 2005年6月19日(日) 21:21
お住まい: 石川県
連絡する:

#10 投稿記事 by ナナシ »

↑ログインできてませんでした。これは私です。
Sunshine

#11 投稿記事 by Sunshine »

 サンプル、ありがとうございます。
 ウィンドウにMM_MCICONTROLを送る方法があったなんて、気づきませんでした(僕はMainWndProc関数内をいじったことなんてなかったので……)。
 このサンプルを元に、いろいろと自作クラスを改良していこうと思います。
 本当にありがとうございました。
Sunshine

#12 投稿記事 by Sunshine »

 ナナシさんのサンプルを元に、クラスを改造してみました。

コード: 全て選択

Class BACKGROUNDMUSIC
	Private
		mop As MCI_OPEN_PARMS
		mpp As MCI_PLAY_PARMS
		msp As MCI_SET_PARMS
		dwCallback As DWord
		bErr As DWord
		' mciSendMessage と MCIControl 用
		mfilename As BytePtr
		mfrom As DWord
		mhCallBackWnd
	Public
		Sub GetError(pszText As BytePtr, cchText As Long)
			mciGetErrorString(bErr, pszText, cchText)
		End Sub
		Sub Stop()
			bErr = mciSendCommand(mop.wDeviceID,MCI_STOP,MCI_WAIT,dwCallback)

			mciSendCommand(mop.wDeviceID,MCI_CLOSE,MCI_WAIT,dwCallback)
			mop.wDeviceID=0
		End Sub
		Sub Play(hCallBackWnd As HWND, filename As BytePtr, from As DWord)
			mpp.dwFrom = from
			If mop.wDeviceID Then Stop()

			mop.lpstrElementName = filename
			mop.dwCallback = hCallBackWnd
			bErr=mciSendCommand(0,MCI_OPEN,MCI_OPEN_ELEMENT,mop)

			msp.dwTimeFormat = MCI_FORMAT_MILLISECONDS
			mciSendCommand(mop.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, msp)

			mpp.dwCallback = hCallBackWnd
			bErr=mciSendCommand(mop.wDeviceID,MCI_PLAY,MCI_NOTIFY,mpp)
		End Sub
		Sub Notify(flags As Long, DevID As Long)
			If flags = MCI_NOTIFY_SUCCESSFUL Then
				bErr=mciSendCommand(mop.wDeviceID,MCI_PLAY,MCI_NOTIFY or MCI_FROM,mpp)
			End If
		End Sub
		Function mciSendMessage(hCallBackWnd As HWND, msg As DWord, filename As BytePtr, from As DWord) As Long
			mfilename = filename
			mfrom = from
			mhCallBackWnd = hCallBackWnd
			mciSendMessage = SendMessage(hCallBackWnd, MM_MCICONTROL, msg, 0)
		End Function
		Sub MCIControl(msg As DWord)
			'"msg" は wParam
			Select Case msg
				Case BGM_PLAY
					Play(mhCallBackWnd, mfilename, mfrom)
				Case BGM_STOP
					Stop()
			End Select
		End Sub
End Class
 ところで、クリティカルセクションはどのタイミングで呼び出せばいいでしょうか。質問ばかりですみません。
返信する