ページ 1 / 1
別スレッド間でのオブジェクトの操作
Posted: 2005年8月18日(木) 21:39
by Sunshine
違うスレッドで1つのオブジェクトを共有することができますか?
ゲームにBGMをつけたいのですが、オブジェクトを作成したスレッドとは別のスレッドからBGM用のオブジェクトを操作することができませんでした。
ゲームの性質上、ゲーム部分とウィンドウ部分では別スレッドを使う必要があると思ったので。
やりたいことはこんな感じです。
┌――→BGM再生用のオブジェクト←―┐
│ │
│ │
ゲームのスレッド ウィンドウ制御スレッド
(曲変更などのメッセージを送る) (BGMを作成したスレッド)
(Notifyで制御を返す)
Posted: 2005年8月19日(金) 09:49
by イグトランス
ほかにはCreateThread()の4番目の引数で渡すなどの方法もありますが、良くも悪くもグローバル変数にしてしまうのが一番簡単です。
下手にグローバル変数を使わないように作るより、今のABではグローバル変数を使ってしまうほうが楽です。
ただし、複数のスレッドが1つのオブジェクトに同時にアクセスすることの無い様、クリティカルセクションなどで排他制御する必要があります。
排他制御のこと
Posted: 2005年8月19日(金) 18:26
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です。
これによって、複数スレッドからの同時アクセスによる、
不測の動作やアプリケーション・エラーを防げます。
すでにご存知でしたら、でしゃばり、失礼しました。
Posted: 2005年8月19日(金) 23:01
by ゲスト
グローバルオブジェクトはどうやって作るのですか?
MainWnd.sbpの先頭部に Dim BGM As BACKGROUNDMUSIC '音楽再生用のクラス
↓
MainWnd_Create関数内でスレッド作成
としてみましたが、うまくいきませんでした。
Posted: 2005年8月19日(金) 23:22
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になります。
Posted: 2005年8月20日(土) 00:44
by Sunshine
グローバルオブジェクトはNew演算子を使ってオブジェクトを生成します。
ありがとうございます。
グローバルオブジェクトはできました。
しかし、音楽の再生、変更はできませんでした。
どうやらmciSendCommand関数が失敗するようです。
どうすればいいでしょうか。
ちなみに、エラーメッセージを取得したところ、「指定されたMIDIデバイスは既に使用されています。解放されるのを待ち、再実行してください。」と出ました。
Posted: 2005年8月20日(土) 11:13
by ナナシ
MCI系の関数は初期化から解放まで同じスレッドで処理しないと
ダメだった気がします。
BGM再生用のオブジェクト←―┐
│
│
ゲームのスレッド――――――――――→ウィンドウ制御スレッド
(曲変更などのメッセージを送る) (BGMを作成したスレッド)
(Notifyで制御を返す)
こういう感じで、ウィンドウ制御スレッドが依頼を受け付けてBGM操作をする
ようにはできませんか?
Posted: 2005年8月20日(土) 23:12
by Sunshine
こういう感じで、ウィンドウ制御スレッドが依頼を受け付けてBGM操作をする
ようにはできませんか?
ウィンドウイベントでは変数の変動は取得できなさそうです……。
ABP ファイルのDo~Loop内に変数の変動をチェックする部分を設ければいいのかな、と思ったのですが、ウィンドウメッセージがないと高速で無限ループするため、クリティカルセクションをつけていたらずっとほかのスレッドがその変数にアクセスできないということになりそうです。
どうすればいいでしょうか。
Posted: 2005年8月23日(火) 23:55
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の出番と思われますが。)
Posted: 2005年8月23日(火) 23:58
by ナナシ
↑ログインできてませんでした。これは私です。
Posted: 2005年8月24日(水) 01:03
by Sunshine
サンプル、ありがとうございます。
ウィンドウにMM_MCICONTROLを送る方法があったなんて、気づきませんでした(僕はMainWndProc関数内をいじったことなんてなかったので……)。
このサンプルを元に、いろいろと自作クラスを改良していこうと思います。
本当にありがとうございました。
Posted: 2005年8月25日(木) 00:22
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
ところで、クリティカルセクションはどのタイミングで呼び出せばいいでしょうか。質問ばかりですみません。