0

私は現在、古いVB6コードをC#(.Net Framework 3.5)に移行するプロジェクトに携わっています。私の使命は、移行を行うことです機能の強化やリファクタリングは、プロジェクトの後のフェーズにプッシュされます。理想的ではありませんが、そこに行きます。

したがって、VB6コードの一部は、WindowsAPISetTimer関数を呼び出します。これを移行しましたが、動作させることができません。

移行されたプロジェクトはDLLとしてビルドされます。DLLにリンクし、問題のコードを呼び出す小さなWinFormsテストハーネスを作成しました。非常に簡単です。電話をかけることができることを証明するだけです。

移行されたDLLの関連コードは次のとおりです。

[DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public extern static int SetTimer(int hwnd, int nIDEvent, int uElapse, AsyncObjectCallerDelegate lpTimerFunc);

public delegate void AsyncObjectCallerDelegate(int hwnd, int uMsg, int idEvent, int dwTime);



static public int StartTimer( AsyncGeoServer.GeoWrapper AsyncObj)
{
    m_objGeoWrapper = AsyncObj;

    int lngReturn = SetTimer(0, 0, 1, new AsyncObjectCallerDelegate(AsyncObjectCaller));

    // When the line below is removed, the call functions correctly.
    // MessageBox.Show("This is a temp message box!", "Temp Msg Box", MessageBoxButtons.OKCancel);

    return lngReturn;
}

static private void AsyncObjectCaller(int hwnd,  int uMsg,  int idEvent,  int dwTime)
{
    // Perform processing here - details removed for clarity 
}


static public void StopTimer( int TimerID)
{
    try { KillTimer(0, TimerID); }
    catch { }
}

上記の呼び出しは、DLLによって外部のDoProcessing()メソッドでラップされます。これにより、StartTimer(両方のWindowsカーネル呼び出し)を呼び出す前にCreateEventを使用してイベントが作成され、処理を続行する前にWaitForSingleObjectが呼び出されます。AsyncObjectCaller関数は、実行の一部としてイベントを設定し、処理を続行できるようにします。

だから私の問題はこれです:コードが上記のように呼び出された場合、それは失敗します。AsyncObjectCallerコールバックメソッドがトリガーされることはなく、WaitForSingleObject呼び出しがタイムアウトします。

ただし、StartTimerでMessageBox.Show呼び出しのコメントを外すと、期待どおりに機能します...ある種。AsyncObjectCallerコールバックメソッドは、MessageBox.Showの呼び出しの直後にトリガーされます。コード内のさまざまな場所にMessageBox.Showを配置しようとしましたが、どこに配置しても同じです(SetTimerの呼び出し後に呼び出される限り)。コールバック関数は、メッセージボックスがトリガーされるまでトリガーされません。表示されます。

私は完全に困惑しており、主に.Netのバックグラウンドから来ているVB6またはWindowsAPIコーディングにあまり精通していません。

助けてくれてありがとう!

4

3 に答える 3

4

あなたAsyncObjectCallerDelegateは間違っています。32ビットコードでは機能する可能性がありますが、64ビットでは惨めに失敗します。WindowsAPI関数のプロトタイプは次のとおりです。

VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

C#では、次のようになります。

delegate void AsyncObjectCallerDelegate(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime);

また、管理対象のプロトタイプは次のようになります。

static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, AsyncObjectCallerDelegate lpTimerFunc);

そうは言っても、AlexFarberが言ったことをエコーし​​ます。これには.NETタイマーオブジェクトの1つを使用する必要があります。これはUIタイマーではないように見えるので(ウィンドウハンドルに0を渡す)、System.Timers.Timerまたはをお勧めしSystem.Threading.Timerます。タイマーでイベントを発生させる場合は、を使用しますSystem.Timers.Timer。タイマーでコールバック関数を呼び出す場合は、を使用しますSystem.Threading.Timer

イベントまたはコールバックは、プログラムのメインスレッドではなく、プールスレッドで実行されることに注意してください。したがって、処理が共有データにアクセスする場合は、スレッド同期の問題に留意する必要があります。

于 2010-09-09T17:27:47.583 に答える
2

問題は、プログラムがメッセージループをポンピングしていないか、UIスレッドをアイドル状態にさせていないことです。SetTimer()のようなAPI関数が機能するには、メッセージループが必要です。たとえば、WindowsフォームプロジェクトのApplication.Run()。コールバックは、メインスレッドがループ内にあり、Windowsメッセージをディスパッチしている場合にのみ実行できます。

これは、独自のメッセージループをポンプする関数であるMessageBox.Show()を使用するときに機能します。ユーザーが[OK]ボタンをクリックしたときにメッセージボックスが応答できるようにします。しかしもちろん、それは箱が上がっている間だけ機能します。

プログラムがWindowsフォームプロジェクトテンプレートに基づくように、プログラムを再構築する必要があるかもしれません。ループでApplication.DoEvents()を呼び出すことは、非常に不完全な回避策です。

于 2010-09-09T18:34:54.060 に答える
0
public extern static int SetTimer(int hwnd、int nIDEvent、int uElapse、IntPtr lpTimerFunc);

int lngReturn = SetTimer(0、0、1、Marshal.GetFunctionPointerForDelegate(new AsyncObjectCallerDelegate(AsyncObjectCaller)));

あなたの使命は移行を行うことだけだと理解していますが、いずれにせよ、これの代わりにWindowsフォームタイマーを使用する方が良いです。ネイティブのSetTimer APIをラップし、これらの相互運用性のトリックは必要ありません。

于 2010-09-09T17:09:47.650 に答える