none
コールバック関数と timeSetEvent関数について RRS feed

  • 質問

  • Visual Studio 2008を用いてMFCの作成を行っています。

    void CADinputView::OnInputsingle() での処理を timeSetEvent関数を用いて1秒毎にコールバックしたいと考えています。

    ただ、コールバック関数と timeSetEvent関数の扱い方がライブラリを参照しても理解できませんでした。解決方法を教えて頂けないでしょうか?

    以下プログラム

    // ADinputView.cpp : CADinputView クラスの実装
    //

    #include "stdafx.h"
    #include "ADinput.h"
    #include "ADinputDoc.h"
    #include "ADinputView.h"
    #include "fbiad.h"
    #include "fbida.h"
    #include <math.h>
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif


    // CADinputView

    IMPLEMENT_DYNCREATE(CADinputView, CView)

    BEGIN_MESSAGE_MAP(CADinputView, CView)
     // 標準印刷コマンド
     ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
     ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
     ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CADinputView::OnFilePrintPreview)
     ON_COMMAND(ID_INPUTSINGLE, &CADinputView::OnInputsingle)
     ON_COMMAND(ID_STOP, &CADinputView::OnStop)
    END_MESSAGE_MAP()

    // CADinputView コンストラクション/デストラクション

    CADinputView::CADinputView()
    {
     // TODO: 構築コードをここに追加します。

    }

    CADinputView::~CADinputView()
    {
    }

    BOOL CADinputView::PreCreateWindow(CREATESTRUCT& cs)
    {
     // TODO: この位置で CREATESTRUCT cs を修正して Window クラスまたはスタイルを
     //  修正してください。

     return CView::PreCreateWindow(cs);
    }

    // CADinputView 描画

    void CADinputView::OnDraw(CDC* /*pDC*/)
    {
     CADinputDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     if (!pDoc)
      return;

     // TODO: この場所にネイティブ データ用の描画コードを追加します。
    }


    // CADinputView 印刷


    void CADinputView::OnFilePrintPreview()
    {
     AFXPrintPreview(this);
    }

    BOOL CADinputView::OnPreparePrinting(CPrintInfo* pInfo)
    {
     // 既定の印刷準備
     return DoPreparePrinting(pInfo);
    }

    void CADinputView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
    {
     // TODO: 印刷前の特別な初期化処理を追加してください。
    }

    void CADinputView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
    {
     // TODO: 印刷後の後処理を追加してください。
    }

    void CADinputView::OnRButtonUp(UINT nFlags, CPoint point)
    {
     ClientToScreen(&point);
     OnContextMenu(this, point);
    }

    void CADinputView::OnContextMenu(CWnd* pWnd, CPoint point)
    {
     theApp.GetContextMenuManager()->ShowPopupMenu(IDR_POPUP_EDIT, point.x, point.y, this, TRUE);
    }


    // CADinputView 診断

    #ifdef _DEBUG
    void CADinputView::AssertValid() const
    {
     CView::AssertValid();
    }

    void CADinputView::Dump(CDumpContext& dc) const
    {
     CView::Dump(dc);
    }

    CADinputDoc* CADinputView::GetDocument() const // デバッグ以外のバージョンはインラインです。
    {
     ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CADinputDoc)));
     return (CADinputDoc*)m_pDocument;
    }
    #endif //_DEBUG


    // CADinputView メッセージ ハンドラ

    void CADinputView::OnInputsingle()
    {
     // TODO: ここにコマンド ハンドラ コードを追加します。
     int nRet;                  // 関数の実行結果
        char cDisp1[200];           // メッセージ配列
        char cDisp2[200];
        char cDisp3[200];
        WORD wData1[1];             // データ配列
        WORD wData2[1];  
        double dVolt1;              // 電圧値
        double dVolt2;  
        ADBOARDSPEC BoardSpec[3];     // ADBOARDSPEC 構造体
        ADSMPLCHREQ SmplChReq[3];     // ADSMPLCHREQ 構造体
        SmplChReq[0].ulChNo = 1;
        SmplChReq[0].ulRange = AD_10V; // インタフェースモジュールの初期化を行います
        SmplChReq[1].ulChNo = 2;
        SmplChReq[1].ulRange = AD_10V; // インタフェースモジュールの初期化を行います

    /////AD_part//////////////////////////////////////////////////////////////////////////////////
        hDeviceHandle = AdOpen ("FBIAD1");
        if (hDeviceHandle == INVALID_HANDLE_VALUE)
            {
            AfxMessageBox("デバイスのオープンに失敗しました", MB_OK, 0);
            return;
            }
    else{
        //デバイスの情報取得
            nRet = AdGetDeviceInfo( hDeviceHandle, &BoardSpec[0] );
            if(nRet != AD_ERROR_SUCCESS)
      {
                AfxMessageBox("デバイスの情報取得に失敗しました", MB_OK, 0);
            }
            //サンプリング1
            nRet = AdInputAD( hDeviceHandle, 1, AD_INPUT_SINGLE, &SmplChReq[0], wData1);
            if(nRet != AD_ERROR_SUCCESS)
      {
                AfxMessageBox("アナログ入力に失敗しました", MB_OK, 0);
            }
      else{
                //電圧算出
       dVolt1 =( 10/pow(2.0, (int)BoardSpec[0].ulResolution)*wData1[0] - 5)*2;
                sprintf_s(cDisp1, "Ch1入力電圧は%1.3fVです", dVolt1);
                AfxMessageBox(cDisp1, MB_OK, 0);
            }
            //サンプリング2
            nRet = AdInputAD( hDeviceHandle, 1, AD_INPUT_SINGLE, &SmplChReq[1], wData2);
            if(nRet != AD_ERROR_SUCCESS)
      {
                AfxMessageBox("アナログ入力に失敗しました", MB_OK, 0);
            }
      else{
                //電圧算出
       dVolt2 =( 10/pow(2.0, (int)BoardSpec[0].ulResolution)*wData2[0] - 5)*2;
                sprintf_s(cDisp2, "Ch2入力電圧は%1.3fVです", dVolt2);
                AfxMessageBox(cDisp2, MB_OK, 0);
            }
    }

     

    int j;
    double result2;
    for (j = 0 ; j < 1 ; j++) {
    result2 = dVolt1+ dVolt2; /* 結果を result に格納している */}
    sprintf_s(cDisp3, "Ch1出力電圧は%1.3fVです", result2);
    AfxMessageBox(cDisp3, MB_OK, 0);

     

    /////DA_part////////////////////////////////////////////////////////////////////////////////////////

    //ボードの初期処理
    hDeviceHandleDA = DaOpen("FBIDA1");
    if(hDeviceHandleDA == INVALID_HANDLE_VALUE)
    {
    AfxMessageBox("デバイスのオープンに失敗しました",MB_OK,0);
    return;
    }
    //アナログ出力設定情報読み出し
    nRet = DaGetSamplingConfig(hDeviceHandleDA,&gConfig);
    if(nRet != DA_ERROR_SUCCESS){
    DaClose(hDeviceHandle);
    AfxMessageBox("アナログ入力更新に失敗しました");
    return;
    }

    //ボードの設定
    nRet = DaSetBoardConfig(hDeviceHandleDA,200,NULL,NULL,0);
    if(nRet != DA_ERROR_SUCCESS){
    AfxMessageBox("ボードの設定に失敗しました");
    return;
    }
    //出力設定
    gConfig.ulSmplRepeat = 1; //繰り返し回数=なし
    gConfig.fSmplFreq = 20000; //出力更新レート= 20kHz
    gConfig.SmplChReq[0].ulChNo = 1; //出力チャンネル= チャンネル1
    gConfig.SmplChReq[0].ulRange = DA_10V; //出力レンジ= ±10V
    //アナログ出力更新設定
    nRet = DaSetSamplingConfig(hDeviceHandleDA,&gConfig);
    if(nRet != DA_ERROR_SUCCESS){
    AfxMessageBox("アナログ出力更新に失敗しました");
    return;
    }
    //バッファ内の出力データをクリアします
    nRet = DaClearSamplingData(hDeviceHandleDA);
    if(nRet != DA_ERROR_SUCCESS){
    AfxMessageBox("バッファのクリアに失敗しました");
    return;
    }


    //出力するデータをバッファにセットします
    nRet = DaSetSamplingData(hDeviceHandleDA, &result2,1);
    if(nRet != DA_ERROR_SUCCESS){
    AfxMessageBox("出力データの登録に失敗しました");
    return;
    }
    //アナログ出力を開始します
    nRet = DaStartSampling(hDeviceHandleDA,FLAG_ASYNC);
    if(nRet != DA_ERROR_SUCCESS){
    AfxMessageBox("アナログ出力に失敗しました");
    return;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////

    }

    2011年3月9日 6:14

回答

  • 以下にSample Codeがあります。

    [Using Multimedia Timers]
      http://msdn.microsoft.com/en-us/library/dd757664(v=VS.85).aspx
      ※上のLinkから読んでいくと良いです。

    timeSetEventは古いAPIであり、制約があります。

    [TimeProc Callback Function]
      http://msdn.microsoft.com/en-us/library/dd757631(v=VS.85).aspx
      Applications should not call any system-defined functions from inside a callback function, except for PostMessage, timeGetSystemTime, timeGetTime, timeSetEvent, timeKillEvent, midiOutShortMsg, midiOutLongMsg, and OutputDebugString.

    制約が結構厳しいので、Worker Threadで処理したほうが良いでしょう。

    [AfxBeginThread]
      http://msdn.microsoft.com/en-us/library/s3w9x78e(v=VS.90).aspx

    timeSetEventによる処理でもそうですが、別Threadで処理が走るため状況によっては同期をとる必要が出てきます。

    • 回答としてマーク 山本春海 2011年4月4日 5:12
    2011年3月11日 13:13
  • どの部分がわからないのでしょう。ちなみにコールバックを使用する場合は
    下の様な感じ(コンパイルしてませんあしからず)。

    class MM_TIMER
    {
      friend void CALLBACK f_TimeOut_MM(
        UINT       uTimerID,
        UINT       uMsg,
        DWORD_PTR  dwUser,
        DWORD_PTR  dw1,
        DWORD_PTR  dw2);
    public: void Start(){
        m_Timer_ID = ::timeSetEvent( 1000, 1, ( LPTIMECALLBACK)f_TimeOut_MM, ( DWORD_PTR)this,
                                      TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
      }
    protected:
      void TimeOut(){
      }
    };
    void CALLBACK f_TimeOut_MM(
      UINT       uTimerID,
      UINT       uMsg,
      DWORD_PTR  dwUser,
      DWORD_PTR  dw1,
      DWORD_PTR  dw2)
    {
      MM_TIMER * Obj = ( MM_TIMER *)dwUser;
      Obj->TimeOut();
    }

    • 回答としてマーク 山本春海 2011年4月4日 5:12
    2011年3月9日 7:38

すべての返信

  • どの部分がわからないのでしょう。ちなみにコールバックを使用する場合は
    下の様な感じ(コンパイルしてませんあしからず)。

    class MM_TIMER
    {
      friend void CALLBACK f_TimeOut_MM(
        UINT       uTimerID,
        UINT       uMsg,
        DWORD_PTR  dwUser,
        DWORD_PTR  dw1,
        DWORD_PTR  dw2);
    public: void Start(){
        m_Timer_ID = ::timeSetEvent( 1000, 1, ( LPTIMECALLBACK)f_TimeOut_MM, ( DWORD_PTR)this,
                                      TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
      }
    protected:
      void TimeOut(){
      }
    };
    void CALLBACK f_TimeOut_MM(
      UINT       uTimerID,
      UINT       uMsg,
      DWORD_PTR  dwUser,
      DWORD_PTR  dw1,
      DWORD_PTR  dw2)
    {
      MM_TIMER * Obj = ( MM_TIMER *)dwUser;
      Obj->TimeOut();
    }

    • 回答としてマーク 山本春海 2011年4月4日 5:12
    2011年3月9日 7:38
  • 回答ありがとうございます。何分プログラミングを初めて日が浅いもので手探りでやっているのが現状です。

    プログラム上の///AD_part///から収録したデータをもとに演算を行ったうえで///DA_part//で出力を行うという作業を一秒毎に行いたいと考えています。

    不勉強で申し訳ありませんが提供していただいた情報を具体的にどのように組み込んでいけばいいのか教えていただけないでしょうか?

     

    2011年3月10日 11:33
  • 不勉強で申し訳ありませんが提供していただいた情報を具体的にどのように組み込んでいけばいいのか教えていただけないでしょうか? 

    どの辺がわからないのでしょうか?
    読んだ上で、どの部分をどう解釈できた、どの部分がどのようにわからないといったことを出していきましょう。

    全くわからないのであれば、実現するレベルに到達していないのかもしれません。
    どこにどのように当てはめれば良いかとその場限りの解決策を求めるのではなく、調べる・試行錯誤することで自分の力を伸ばすようにしましょう。

    調査観点一例:
    static キーワードのついたメンバー関数がどういうものなのか、コールバック関数はなぜ static キーワードのついた関数でなければならないのか、コールバックする際には渡すポインタに this を渡す狙いはなぜなのか、こういった点で調査してみてください。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年3月10日 14:38
    モデレータ
  • 丁寧な回答ありがとうございます。回答していただいた内容を元に自分で調べてみようと思います。

    2011年3月11日 12:16
  • ところで、なぜtimeSetEventのコールバックを使用されたいのでしょうか。
    SetTimerして、OnTimerを書く方が簡単です。
    timeSetEentによるスレッドの分割(UIのブロック阻止)が目的なら、
    ワーカスレッドを作成して、1秒ごとに処理をするループを書くのも手です。


    jzkey
    2011年3月11日 12:39
  • 以下にSample Codeがあります。

    [Using Multimedia Timers]
      http://msdn.microsoft.com/en-us/library/dd757664(v=VS.85).aspx
      ※上のLinkから読んでいくと良いです。

    timeSetEventは古いAPIであり、制約があります。

    [TimeProc Callback Function]
      http://msdn.microsoft.com/en-us/library/dd757631(v=VS.85).aspx
      Applications should not call any system-defined functions from inside a callback function, except for PostMessage, timeGetSystemTime, timeGetTime, timeSetEvent, timeKillEvent, midiOutShortMsg, midiOutLongMsg, and OutputDebugString.

    制約が結構厳しいので、Worker Threadで処理したほうが良いでしょう。

    [AfxBeginThread]
      http://msdn.microsoft.com/en-us/library/s3w9x78e(v=VS.90).aspx

    timeSetEventによる処理でもそうですが、別Threadで処理が走るため状況によっては同期をとる必要が出てきます。

    • 回答としてマーク 山本春海 2011年4月4日 5:12
    2011年3月11日 13:13
  • こんにちは、junglemeji さん。

    MSDN フォーラムのご利用ありがとうございます。オペレーターの山本です。

    みなさんから参考になるアドバイスをいただいているようでしたので、勝手ながら私のほうで回答としてマークさせていただきました。
    アドバイスくださったみなさん、情報ありがとうございます。

    解決に役立った投稿や、参考になる情報など有効な情報をいただいた場合には、その情報に回答としてマークすることをお願いしています。
    今後、同じ問題でこのスレッドを参照される方にも、有効な情報がわかりやすくなるかと思いますので、ご協力のほど、よろしくお願いいたします。

    今後とも、MSDN フォーラムをよろしくお願いいたします。それでは。
                                                                                           
    日本マイクロソフト株式会社 フォーラム オペレーター 山本 春海

    2011年4月4日 5:12