Fernsteuern eines Programms & Timer
-
Hallo!
ich habe ein kleines C/C++ Programm erstellt welches ein anderes Programm "fernsteuern" soll. Das funktioniert auch wunderbar. Über eine Steuerdatei wird definiert welche tastendrücke in welchen Zeitabständen an das zu fernsteuernde Programm geschickt werden sollen.
Hin und wieder passen die Timings zwar nicht ganz (d.h. eine taste wird zu spät (vllt. auch zu früh?) gedrückt bzw. losgelassen) aber das kommt nicht all zu oft vor.Jedoch habe ich auch ein Programm erstellt welches diese Steuerdateien automatisch erstellen soll in dem es einfach die tastaturaktivität in einem gewissen zeitraum aufzeichnet und diese in die Form der Steuerdatei hinein presst - funktioniert auch wunderbar.
Jedoch passen die Timings dann überhaupt nicht mehr wenn ich das Wiedergabe-Programm die automatisch erstellte Steuerdatei durchlaufen lasse. Dabei ist es auch interessant anzumerken dass für die händisch erstellte Steuerdatei nur eine genauigkeit von Zehntelsekunden verwendet wird (welche anscheinend ausreicht), die automatisch generierte Steuerdatei arbeitet aber mit Millisekunden - und hier reichen diese merkwürdigerweise nicht!
Technischer Hintergund:
- Tastaturaktivität wird mit einem globalen Hook überwacht - die wesentlichen Teile des Codes dafür:hhook=SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hThisInstance, 0); while (GetMessage(&messages, NULL, 0, 0)) { TranslateMessage(&messages); DispatchMessage(&messages); } UnhookWindowsHookEx(hhook);und
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { static int calls=0; calls++; KBDLLHOOKSTRUCT *pKeyBoard = (KBDLLHOOKSTRUCT *)lParam; switch( wParam ) { case WM_KEYUP: { switch( pKeyBoard->vkCode ) { case 'N': p("ende/exit"); myexit(0); break; case LEFT_VK: case RIGHT_VK: case SHIFTR_VK: case SHIFTL_VK: case Z_VK: p("PUP"); p(pKeyBoard->vkCode, calls); pup(pKeyBoard->vkCode); //tastendruck registrieren break; } break; } case WM_KEYDOWN: { switch( pKeyBoard->vkCode ) { case LEFT_VK: case RIGHT_VK: case SHIFTR_VK: case SHIFTL_VK: case Z_VK: p("PDWN"); p(pKeyBoard->vkCode, calls); pdown(pKeyBoard->vkCode); //tastendruck registrieren break; } } default: return CallNextHookEx( NULL, nCode, wParam, lParam ); } return 0; }interessant sind auch die funktionen die die tastendrücke handhaben:
void pdown(DWORD key) { //if (timer[key]!=null) // timer[key] = GetTickCount(); if (!pstate[key]) { //wenn taste nicht gedrückt if (timer==0L) { timer = GetTickCount(); r(downAsoc[key]); // Aktion in der Steuerdatei für den druck dieser taste vermerken } else { unsigned long timer2 = GetTickCount(); r('w',timer2-timer); // Warte-Anweisung in die Steuerdatei schreiben timer=GetTickCount(); //timer=timer2; r(downAsoc[key]); // Aktion in der Steuerdatei für den druck dieser taste vermerken } pstate[key]=true; } } void pup(DWORD key) { if (pstate[key]){ // wenn taste gedrückt unsigned long timer2 = GetTickCount(); r('w',timer2-timer); // Warte-Anweisung in die Steuerdatei schreiben timer=GetTickCount(); //timer=timer2; r(upAsoc[key]); // Aktion in der Steuerdatei für das loslassen dieser taste vermerken pstate[key] = false; } }- auf der anderen Seite (beim wiedergeben) werden die erzeugten anweisungen dann durchgelaufen und mit sleep(zeit) die zeit die beim aufzeichnen zwischen den tastendrücken gemessen worden ist ebenfalls jeweils abgewartet
while(!feof(f)) { i++; fgets(line,7,f); // nächste Zeile/Anweisung der steuerdatei lesen char instr = line[0]; if (instr=='-') { // Kommentar in der Steuerdatei überspringen fgets(c,1000,f); continue; //Kommentar } int param = 0; if ((instr!='j')&&(instr!='J')) { // Anweisungen ohne Parameter char *prm = line+1; param = atoi(prm); } else param = 0; if (instr=='%') { // Timer genauigkeit setzen timeEndPeriod(delay); delay = param; printf("%d/CONFIG: %c/%d \n",i,instr,param); timeBeginPeriod(delay); continue; } printf("%d: %c/%d \n",i,instr,param); hdl(instr,param); //Anweisung ausführen } fclose(f);und die funktion die die Anweisungen handhabt:
inline void hdl(char instr,int param) { static INPUT rid; static INPUT lid; [...] if (instr=='L'){ // Taste für die Anweisung L drücken lid = StartLeft(); } if (instr=='K'){ // Taste für die Anweisung L loslassen EndIndication(lid); } [...] if (instr=='w') { // Warte-Anweisung wait(param); } } inline INPUT StartLeft() { return KeyDown(LEFT_VK, LEFT_SC, hwnd); //LEFT_VK/SC: VirtualKey/ScanCode, hwnd: Zielfenster } inline void EndIndication(INPUT ip) { KeyUp(ip); } inline INPUT KeyDown(WORD vk, WORD sc, HWND hwnd) { INPUT ipSignal; ipSignal.type = INPUT_KEYBOARD; ipSignal.ki.wVk = vk; ipSignal.ki.wScan = sc; ipSignal.ki.dwFlags = 0L; ipSignal.ki.time = 0L; ipSignal.ki.dwExtraInfo = (ULONG_PTR)hwnd; //ipSignal.ki.dwExtraInfo = 0; //Fenster mit dem Fokus SendInput(1, &ipSignal, sizeof(ipSignal)); return ipSignal; } inline INPUT KeyUp(INPUT ip) { ip.ki.dwFlags = KEYEVENTF_KEYUP; SendInput(1, &ip, sizeof(ip)); return ip; } inline void wait(int steps) { Sleep(steps*delay); }Hat wer eine Idee warum die Anweisungen nicht mit den selben Verzögerungen wiedergegeben werden, mit welchen sie aufgenommen wurden?
Habe ich vielleicht einen Fehler gemacht? Bzw. hat wer einen Lösungsvorschlag?Ich hab auch schon versucht mit den Prioritäten im Task-Manager herum zu spielen, das hat jedoch leider nicht wirklich geholfen.
-
@Timmy: keine Ahnung was es da hat. Dein Code ist IMO auch einigermassen schauderhaft. Es gibt einige Kleinigkeiten die mir aufgefallen sind, aber nix wo ich mir jetzt denken würde dass es was bringt. Versuch vielleicht erstmal rauszufinden ob der Fehler bei der Aufnahme oder beim Abspielen entsteht.
Paar Kleinigkeiten:
-
verwende timeGetTime() statt GetTickCount().
-
ruf beim Programmbeginn 1x timeBeginPeriod(2) (oder 1) auf, und bei Programmende 1x timeEndPeriod.
-
IO machst du am besten erst nachdem du die Zeit ermittelt hast, sonst entsteht ein unnötiges Delay zwischen dem Event und dem "auf die Uhr gucken".
-
Stell die Priorität des Replay-Threads mit SetThreadPriority auf THREAD_PRIORITY_HIGHEST oder sogar THREAD_PRIORITY_TIME_CRITICAL.
-
Hier 2x GetTickCount() aufzurufen könnte zu weiteren Ungenauigkeiten führen:
unsigned long timer2 = GetTickCount(); r('w',timer2-timer); // Warte-Anweisung in die Steuerdatei schreiben // timer=GetTickCount(); // nö, so nicht timer=timer2; // so schon- Verwende Zeitangaben die mit 0 = Skriptbeginn anfangen
- Hol dir die aktuelle Zeit, und berechne daraus dein Delay zum Warten
- Verwende ordentliche Funktionsnamen
...
-
-
ohne mir ehrlichgesagt den code durchgelesen zuhaben
ein kleiner tipp am rande. du solltest den highperformance counter für synchronisationszeugs unter windows benutzen. also zb.:LARGE_INTEGER m_CounterFrequency; LARGE_INTEGER m_LastCount; LARGE_INTEGER lCurrent; QueryPerformanceFrequency(&m_CounterFrequency); QueryPerformanceCounter(&m_LastCount); // code to measure QueryPerformanceCounter(&lCurrent); // time in microseconds time = ((float)(lCurrent.QuadPart - m_LastCount.QuadPart)*1000000.0/(float)(m_CounterFrequency.QuadPart));auf meinem system unter windows xp hat dieser timer allerdings immernoch per default 55ms auflösung
! also ggf mit:TIMECAPS tc; timeGetDevCaps(&tc, sizeof(TIMECAPS)); timeBeginPeriod(tc.wPeriodMin);auf die kleinstmögliche auflösung stellen, aber nicht vergessen dies beim beenden des programmes zu revidieren (sonst ist windows irgendwann b0rken :P):
timeEndPeriod(tc.wPeriodMin);um für einen zeitraum zu warten, solltest du nicht sleep() und konsorten benutzen, sondern einen waitable timer, also:
HANDLE timer = NULL; LARGE_INTEGER liDueTime; timer = CreateWaitableTimer(NULL, TRUE, L"timer01"); liDueTime.QuadPart = -10000; // sollte 1 millisekunde sein, wenn ich nicht irre :) SetWaitableTimer(timer, &liDueTime, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE);das alles im allem gibt genügend genauigkeit ohne prioritäten auf critical setzen zu müssen

-
Sry dass es so lang gedauert hat, aber ich hab einiges ausprobiert und das braucht seine zeit...
hustbaer schrieb:
@Timmy: keine Ahnung was es da hat. Dein Code ist IMO auch einigermassen schauderhaft.
Ich würde mich über Anregungen wie es besser aussehen könnte freuen

Paar Kleinigkeiten:
hustbaer schrieb:
- verwende timeGetTime() statt GetTickCount().
Gibt es Unterschiede zw. den beiden Funktionen? ich hab in der MSDN keine finden können
hustbaer schrieb:
- ruf beim Programmbeginn 1x timeBeginPeriod(2) (oder 1) auf, und bei Programmende 1x timeEndPeriod.
mach ich...
hustbaer schrieb:
- IO machst du am besten erst nachdem du die Zeit ermittelt hast, sonst entsteht ein unnötiges Delay zwischen dem Event und dem "auf die Uhr gucken".
ebenfalls schon umgesetzt...
hustbaer schrieb:
- Stell die Priorität des Replay-Threads mit SetThreadPriority auf THREAD_PRIORITY_HIGHEST oder sogar THREAD_PRIORITY_TIME_CRITICAL.
Hab das über den Task-Manager probiert, habe aber keinen Unterschied feststellen können.
hustbaer schrieb:
- Verwende Zeitangaben die mit 0 = Skriptbeginn anfangen
- Hol dir die aktuelle Zeit, und berechne daraus dein Delay zum Warten
ebenfalls schon umgesetzt...
sothis_ schrieb:
ohne mir ehrlichgesagt den code durchgelesen zuhaben
ein kleiner tipp am rande. du solltest den highperformance counter für synchronisationszeugs unter windows benutzen.Danke! Hab zwar nach einem genauen Timer gesucht, aber nur dieses GetTickCount gefunden

sothis_ schrieb:
auf meinem system unter windows xp hat dieser timer allerdings immernoch per default 55ms auflösung
Dieses Prolem hab ich nicht, bei mir ist 1/HighPerformanceFrequency() irgendwo bei 1,6*10^-07 s
sothis_ schrieb:
um für einen zeitraum zu warten, solltest du nicht sleep() und konsorten benutzen, sondern einen waitable timer
Der liefert leider genau so schlechte Ergebnisse wie Sleep

sothis_ schrieb:
das alles im allem gibt genügend genauigkeit
Tut es leider nicht - die Zeiten passen immer noch nicht...
ich hab mir allerdings eine eigene Funktion erstellt GetMyTicks() welche folgendes zurück gibt:
double_zu_long((double)m_LastCount.QuadPart*10000.0/(double)m_CounterFrequency.QuadPart);
dies verwende ich nun statt GetTickCount()
ebenfalls habe ich das warten in Form von einem Busy-Wait umgesetzt (dank Dual-Core nur halb so schlimm, auf "normalen" CPUs hab ich es noch nicht getestet)
long sl = schlafeBisZeitstempel - (GetMyTicks() - programmstartZeitstempel); while (sl > 0) { sl = schlafeBisZeitstempel - (GetMyTicks() - programmstartZeitstempel) }damit schläft das programm (zumindest laut logs) genau so lange wie es soll - allerdings passen die ausgelösten Tastendrücke zeitlich immer noch nicht zusammen (erkennt man am Verhalten der Anwendung)
