Проблема с методом обратного вызова в SetTimer Windows API, вызванном из кода C # - PullRequest
0 голосов
/ 09 сентября 2010

В настоящее время я участвую в проекте, который переносит старый код VB6 на C # (.Net Framework 3.5).Мой мандат состоит в том, чтобы просто выполнить миграцию;любые функциональные улучшения или рефакторинг должны быть перенесены на более позднюю фазу проекта.Не идеально, но вы идете.

Таким образом, часть кода VB6 вызывает функцию Windows API SetTimer.Я перенес это и не могу заставить его работать.

Перенос проекта строится как DLL;Я создал небольшой тестовый жгут WinForms, который связывается с DLL и вызывает соответствующий код.Очень просто, просто чтобы доказать, что вызов может быть выполнен.

Соответствующий код в перенесенной 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 ();это создает событие с использованием CreateEvent перед вызовом StartTimer (оба вызова ядра Windows), затем вызывает WaitForSingleObject перед продолжением обработки.Функция AsyncObjectCaller установит событие как часть своего выполнения, чтобы разрешить продолжение обработки.

Поэтому моя проблема заключается в следующем: если код вызывается так, как указано выше, происходит сбой.Метод обратного вызова AsyncObjectCaller никогда не запускается, и время ожидания WaitForSingleObject истекает.

Если, однако, я раскомментирую вызов MessageBox.Show в StartTimer, он работает как ожидалось ... вроде.Метод обратного вызова AsyncObjectCaller запускается сразу после вызова MessageBox.Show.Я попытался поместить MessageBox.Show в различные места в коде, и это независимо от того, где я его поместил (до тех пор, пока он вызывается после вызова SetTimer) - функция обратного вызова не срабатывает до тех пор, пока окно сообщения не будет

Я полностью озадачен и не слишком знаком с кодированием VB6 или Windows API, в основном из .Net фона.

Спасибо за любую помощь!

Ответы [ 3 ]

4 голосов
/ 09 сентября 2010

Ваш AsyncObjectCallerDelegate неверен. Он может работать в 32-битном коде, но с треском провалится в 64-битном. Прототип функции Windows API:

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);

Тем не менее, я бы повторил то, что сказал Алекс Фарбер: для этого вам следует использовать один из объектов таймера .NET. Поскольку это не похоже на таймер пользовательского интерфейса (вы передаете 0 для дескриптора окна), я бы предложил System.Timers.Timer или System.Threading.Timer. Если вы хотите, чтобы таймер вызвал событие, используйте System.Timers.Timer. Если вы хотите, чтобы таймер вызывал функцию обратного вызова, используйте System.Threading.Timer.

Обратите внимание, что событие или обратный вызов будут выполняться в потоке пула, а НЕ в основном потоке программы. Поэтому, если обработка будет обращаться к каким-либо общим данным, вам нужно помнить о проблемах синхронизации потоков.

2 голосов
/ 09 сентября 2010

Проблема в том, что ваша программа не качает цикл сообщений или не позволяет потоку пользовательского интерфейса бездействовать.Функция API, такая как SetTimer (), для работы требует цикл обработки сообщений.Application.Run () в проекте Windows Forms, например.Обратный вызов может выполняться только тогда, когда ваш основной поток находится внутри цикла, отправляя сообщения Windows.

Он работает, когда вы используете MessageBox.Show (), это функция, которая качает свой собственный цикл сообщений.Так что окно сообщения может ответить пользователю, нажав кнопку ОК.Но, конечно, это будет работать только до тех пор, пока коробка не будет запущена.

Возможно, вам потребуется реструктурировать вашу программу, чтобы она основывалась на шаблоне проекта Windows Forms.Вызов Application.DoEvents () в цикле - очень несовершенный обходной путь.

0 голосов
/ 09 сентября 2010
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 Forms вместо этого, он оборачивает собственный API SetTimer, и в этих приемах взаимодействия нет необходимости.

...