C# Windows Service - Overlapped I/O operation is in progress.

Zhen

Lt. Junior Grade
Registriert
Aug. 2009
Beiträge
299
Hallo Leute,
ich bin gerade wieder ein bisschen am experimentieren und habe da einen Fehlercode den ich nicht gerade nachvollziehen kann...

Ich habe nen einfachen Windows Dienst der beim Starten halt versucht das Handle auf den Desktop zu bekommen.

In meiner Console-Application funktioniert das auch einwandfrei, aber im Service spuckt er mir halt den Fehlercode

997

aus, was für

Overlapped I/O operation is in progress

steht. Hat da jemand eine Idee woran das liegen könnte?
Hier noch der Code ;)

PHP:
private void InitializeDesktop() {
	uint cur_ThreadId = 0;
	IntPtr new_Desktop = IntPtr.Zero;
	IntPtr old_Desktop = IntPtr.Zero;


	// GetDesktopWindow
	try {
		EventLog.WriteEntry("Beginne mit der Ausführung von GetDesktopWindow...");

		new_Desktop = NativeMethods.GetDesktopWindow();

		EventLog.WriteEntry("Ausführung von GetDesktopWindow beendet... !");
		EventLog.WriteEntry("Fehlercode: " + NativeMethods.GetLastError() + "\r\n");
	} catch (Exception ex) {
		EventLog.WriteEntry("Bei der Ausführung von GetDesktopWindow trat ein Fehler auf! \r\n" + ex.Message);
		EventLog.WriteEntry("Fehlercode: " + NativeMethods.GetLastError() + "\r\n");
	}


	// GetCurrentThreadId
	try {
		EventLog.WriteEntry("Beginne mit der Ausführung von GetCurrentThreadId...");

		cur_ThreadId = NativeMethods.GetCurrentThreadId();

		EventLog.WriteEntry("Ausführung von GetCurrentThreadId abgeschlossen... !");
		EventLog.WriteEntry("Fehlercode: " + NativeMethods.GetLastError() + "\r\n");
	} catch (Exception ex) {
		EventLog.WriteEntry("Bei der Ausführung von GetCurrentThread trat ein Fehler auf! \r\n" + ex.Message);
		EventLog.WriteEntry("Fehlercode: " + NativeMethods.GetLastError() + "\r\n");
	}


	// GetThreadDesktop
	try {
		EventLog.WriteEntry("Beginne mit der Ausführung von GetThreadDesktop...");

		old_Desktop = NativeMethods.GetThreadDesktop(cur_ThreadId);

		EventLog.WriteEntry("Ausführung von GetThreadDesktop beendet!");
		EventLog.WriteEntry("Fehlercode: " + NativeMethods.GetLastError() + "\r\n");
	} catch (Exception ex) {
		EventLog.WriteEntry("Bei der Ausführung von GetThreadDesktop trat ein Fehler auf! \r\n" + ex.Message);
		EventLog.WriteEntry("Fehlercode: " + NativeMethods.GetLastError() + "\r\n");
	}


	// SetThreadDesktop
	if (!NativeMethods.SetThreadDesktop(new_Desktop)) {
		EventLog.WriteEntry("\r\nACTION FAILED");
		EventLog.WriteEntry(NativeMethods.GetLastError().ToString());
	} else {
		EventLog.WriteEntry("\r\nDesktop wurde dem neuen Thread erfolgreich zugewiesen!");
	}


	// CloseDesktop
	if (!NativeMethods.CloseDesktop(old_Desktop)) {
		EventLog.WriteEntry("\r\nUNABLE TO CLOSE DESKTOP");
		EventLog.WriteEntry(NativeMethods.GetLastError().ToString());
	}
}

Diese funktion wird in einem eigenen Thread ausgeführt. Der Thread wird in der OnStart-Methode vom Windows Dienst gestartet :)


Hoffe ihr könnt mir da weiterhelfen. Dieser Fehlercode 997 kommt übrigens bei jeder dieser WinAPI-Funktion.
Ich versteh aber wirklich nicht wieso. :(
 
Mein Call zu DsBindWithSpn gibt mir auch immer ERROR_NOT_ENOUGH_MEMORY zurück. Hier mein Code

Code:
Console.Out.WriteLine( "Hello World!" );

Hoffe, du weißt was ich dir sagen möchte ;)
 
Muss zugeben, dass ich gerade etwas auf der Leitung stehe... -_-"
 
Zhen schrieb:
Muss zugeben, dass ich gerade etwas auf der Leitung stehe... -_-"

Oh je ;)
Schau dir mal den Code an, den du oben gepostet hast.
Ausser das du den EventLog zu spamst, sieht man da rein gar nichts.
 
Aso... :D
Naja es geht halt einfach darum, dass ich die Funktionen aufrufe und diese dennoch eine Fehlermeldung ausgeben.

Hier mal der gesamte Code für den Windows Dienst (wie gesagt ist es nur ein Experiment, wenn das funktionieren würde, dann würde ich ja den Code weiterverarbeiten, aber wenn das nicht mal läuft.... )

PHP:
protected override void OnStart(string[] args) {
	try {
		thread = new Thread(new ThreadStart(ThreadFunction));
		thread.Start();
	} catch (Exception ex) {
		EventLog.WriteEntry("OnStart - Exception: " + ex.Message);
	}
}

private void ThreadFunction() {
	InitializeDesktop();

	// Erstellt Screenshots
	TakeScreenshots();
}

private void TakeScreenshots() {
	try {
		string path = "C:\\test\\service\\";

		for (int i = 0; i < 50; i++) {
			Bitmap bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
				Screen.PrimaryScreen.Bounds.Height);

			Graphics g = Graphics.FromImage(bmp);

			EventLog.WriteEntry("Beginne mit der Ausführung von CopyFromScreen...");
			g.CopyFromScreen(0, 0, 0, 0, bmp.Size);
			EventLog.WriteEntry("Ausführung von CopyFromScreen beendet... !");

			g.Flush();
			g.Dispose();

			bmp.Save(path + "image" + i + ".jpg");
		}
	} catch (Exception ex) {
		EventLog.WriteEntry(ex.Message);
	}
}

Ich will halt für den Dienst den Desktop als Parent festlegen so zu sagen, damit ich doch noch meine Screenshots davon erstellen kann :D :D
 
Zhen schrieb:
Aso... :D
Naja es geht halt einfach darum, dass ich die Funktionen aufrufe und diese dennoch eine Fehlermeldung ausgeben.

Hoffnungslos ;)
Die Methode TakeScreenshot interessiert doch niemanden solange dein P/Invoke nicht funktioniert. Mit meinen beiden bisherigen Posts wollte ich eigentlich nur darauf hinweisen, dass du den Code für die nativen Methoden posten sollst.

Aber da du es nun eh gepostet hast. Soweit ich weiß, funktionieren die Klassen aus dem System.Drawing Namespace nicht in Verbindung mit einem Service. Lasse mich da aber gerne eines besseren belehren.
 
Asoooo... :D :D :D

Ja gut also an dem Code für die nativen Methoden ist nichts besonderes...

PHP:
static class NativeMethods {

	[DllImport("kernel32.dll")]
	public static extern uint GetLastError();

	[DllImport("user32.dll")]
	public static extern IntPtr GetDesktopWindow();

	[DllImport("kernel32.dll")]
	public static extern uint GetCurrentThreadId();

	[DllImport("kernel32.dll")]
	public static extern IntPtr GetCurrentThread();
		
	[DllImport("user32.dll")]
	public static extern bool CloseDesktop(IntPtr hDesktop);

	[DllImport("user32.dll")]
	public static extern bool SwitchDesktop(IntPtr hDesktop);

	[DllImport("user32.dll")]
	public static extern bool SetThreadDesktop(IntPtr hDesktop);

	[DllImport("user32.dll")]
	public static extern IntPtr GetThreadDesktop(uint dwThreadId);

	[DllImport("user32.dll")]
	public static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit,
		ACCESS_MASK dwDesiredAccess);


	[Flags]
	public enum ACCESS_MASK : uint {
		DELETE = 0x00010000,
		READ_CONTROL = 0x00020000,
		WRITE_DAC = 0x00040000,
		WRITE_OWNER = 0x00080000,
		SYNCHRONIZE = 0x00100000,

		STANDARD_RIGHTS_REQUIRED = 0x000f0000,

		STANDARD_RIGHTS_READ = 0x00020000,
		STANDARD_RIGHTS_WRITE = 0x00020000,
		STANDARD_RIGHTS_EXECUTE = 0x00020000,

		STANDARD_RIGHTS_ALL = 0x001f0000,

		SPECIFIC_RIGHTS_ALL = 0x0000ffff,

		ACCESS_SYSTEM_SECURITY = 0x01000000,

		MAXIMUM_ALLOWED = 0x02000000,

		GENERIC_READ = 0x80000000,
		GENERIC_WRITE = 0x40000000,
		GENERIC_EXECUTE = 0x20000000,
		GENERIC_ALL = 0x10000000,

		DESKTOP_READOBJECTS = 0x00000001,
		DESKTOP_CREATEWINDOW = 0x00000002,
		DESKTOP_CREATEMENU = 0x00000004,
		DESKTOP_HOOKCONTROL = 0x00000008,
		DESKTOP_JOURNALRECORD = 0x00000010,
		DESKTOP_JOURNALPLAYBACK = 0x00000020,
		DESKTOP_ENUMERATE = 0x00000040,
		DESKTOP_WRITEOBJECTS = 0x00000080,
		DESKTOP_SWITCHDESKTOP = 0x00000100,

		WINSTA_ENUMDESKTOPS = 0x00000001,
		WINSTA_READATTRIBUTES = 0x00000002,
		WINSTA_ACCESSCLIPBOARD = 0x00000004,
		WINSTA_CREATEDESKTOP = 0x00000008,
		WINSTA_WRITEATTRIBUTES = 0x00000010,
		WINSTA_ACCESSGLOBALATOMS = 0x00000020,
		WINSTA_EXITWINDOWS = 0x00000040,
		WINSTA_ENUMERATE = 0x00000100,
		WINSTA_READSCREEN = 0x00000200,

		WINSTA_ALL_ACCESS = 0x0000037f
	}
}


Ja mir sagt die "CopyFromScreen" Methode immer "Ungültiges Handle"... deswegen versuche ich auch irgendwie das Handle vom Desktop zu bekommen und das als Parent für den Service zu setzen.

Als würde der Service dann auf den Desktop laufen, damit das ganze mit den Screenshots dann doch funktioniert...

Ich weiß halt nur nicht ob ich da beim richtigen Ansatz bin :D
 
Also, habe die APIs nur kurz überflogen, aber so sollte es gehen:

Code:
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "user32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "GetUserObjectInformationW" )]
[return: MarshalAs( UnmanagedType.Bool )]
static extern bool GetUserObjectInformation( IntPtr hObj, UOI nIndex, StringBuilder pvInfo, int nLength, ref int lpnLengthNeeded );

[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "user32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
public static extern SafeWindowStationHandle GetProcessWindowStation();

        
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "user32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
[return: MarshalAs( UnmanagedType.Bool )]
public static extern bool CloseWindowStation( IntPtr hWinsta );

Code:
public sealed class SafeWindowStationHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public SafeWindowStationHandle()
        : base( true )
    {
    }

    protected override bool ReleaseHandle()
    {
        return CloseWindowStation( handle );
    }
}

Code:
enum UOI : uint
{
    Flags = 1,
    Name = 2,
    Type = 3,
    UserSID = 4,
    HeapSize = 5,
    IO = 6
}

Zuerst brauchst du den Desktop, öffnen tust du ihn durch einen Call zu OpenDesktop. Vorher brauchst du aber den Namen - dazu der Code oben. Verwendung wie folgt:

Code:
using ( var winStaHandle = GetProcessWindowStation() )
{
    if ( winStaHandle.IsInvalid == false )
    {
        IntPtr systemHandle = winStaHandle.DangerousGetHandle();
        StringBuilder stationName = new StringBuilder();
        int lpnLength = 0;
        GetUserObjectInformation( systemHandle, UOI.Name, stationName, 0, ref lpnLength );
        if ( lpnLength > 0 )
        {
            bool success = GetUserObjectInformation( systemHandle, UOI.Name, stationName, lpnLength,
                                                        ref lpnLength );
            if ( success )
            {
                // sollte "WinSta0" sein, weiß aber nicht, ob es dies immer ist.
                string desktopName = stationName.ToString();
                // weiter geht's mit OpenDesktop -> http://msdn.microsoft.com/en-us/library/windows/desktop/ms684303(v=vs.85).aspx
            }
        }
        else
        {
            int hr = Marshal.GetHRForLastWin32Error();
            throw Marshal.GetExceptionForHR( hr );
        }
    }
}

Wie im Kommentar bereits geschrieben. Sobald du den Namen hast, kannst du den Desktop öffnen. Siehe dazu MSDN. Bei Problemen kannste dich ja nochmal melden.
 
Zuletzt bearbeitet:
Also ich bin echt langsam am verzweifeln... ich glaub ich muss mir wohl einen gänzlich anderen Weg für diesen Mist suchen...

Habe jetzt alles so implementiert wie du es gesagt hast, hab auch die OpenDesktop-Methode benutzt und dennoch klappt es nicht.

1.) als Desktop-Namen liefert er mir völligen unfug: Service-0x0-3e7$
2.) wenn ich gleich WinSta0 hernehme, dann kommt wieder Fehlercode 997

Dieses Aufruf steht direkt unter der Variable "desktopName"
PHP:
NativeMethods.OpenDesktop(desktopName, 0x0001, true,
	NativeMethods.ACCESS_MASK.DESKTOP_CREATEMENU |
	NativeMethods.ACCESS_MASK.DESKTOP_CREATEWINDOW |
	NativeMethods.ACCESS_MASK.DESKTOP_ENUMERATE |
	NativeMethods.ACCESS_MASK.DESKTOP_HOOKCONTROL |
	NativeMethods.ACCESS_MASK.DESKTOP_WRITEOBJECTS |
	NativeMethods.ACCESS_MASK.DESKTOP_READOBJECTS |
	NativeMethods.ACCESS_MASK.DESKTOP_SWITCHDESKTOP |
	NativeMethods.ACCESS_MASK.GENERIC_WRITE);

Und deklariert wird es in der NativeMethods-Klasse.
PHP:
[DllImport("user32.dll")]
public static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit,
	ACCESS_MASK dwDesiredAccess);


EDIT: habs auch schon mit anderen Optionen für ACCESS_MASK ausprobiert, aber trotzdem der selbe Schmarn. Muss auch zugeben ich weiß nicht mal was ich da überhaupt angeben soll :D
 
Aber nein, ich mach ja die Native Calls nur aus einem einzigen Grund... ich versuche so doch noch meine Screenshots vom Desktop zu bekommen.

Wenn es anders ginge, dann würde ich es auch anders machen ;-) :D
 
Hast du den Service auch wie im Link beschreiben installiert und konfiguriert?

Es müsste eigentlich so wie holy geschreiben hat funktionieren, aber nur dann wenn die Konfiguration passt...
 
Ehm... ne über Installation hab ich nichts gelesen gehabt... (bis jetzt)...

Ich hab den so installiert wie immer mit "InstallUtil.exe /i meinService.exe".
Werd es aber gleich mal morgen nachholen und alles in Ruhe durchlesen. Heute gehts leider nicht mehr. :(
 
Hab eben nochmal die API gelesen. Der Code, den ich dir gepostet habe wird so nicht funktionieren. Erstmal ist der Desktop "WinSta0" falsch. "WinSta0" ist die Desktop-Station mit der der Desktop verknüpft ist. Ist nur ein/kein User angemeldet, gibt es eine Station mit einem Desktop, der "Default" heißt. Btw. heißt der Desktop immer "Default", es sei denn, du erzeugst selber welche (z.b. Dexpot, welches mehrere Desktops pro Session zur Verfügung stellt).

(Wieso mein Code nun "WinSta0" zurück gibt, weiß ich grade selber nicht, hab aber nicht sonderlich viel Lust es rauszufinden)

Was du also machen musst, ist erstmal die richtige Desktop-Station zu finden. Wenn du diesen Handle hast, musst du deinem Prozess mit SetProcessWindowStation diesen zuweisen, andernfalls kannst du den Desktop nicht öffnen (der Desktop, den du öffnen möchtest muss zur aktuellen Window-Station gehören; das Security-Model erfordert es so).
Alles, was du brauchst, findest du hier. Lesen musst du schon selber.

Was du da mit den AccessMasks machst ist übrigens totaler Blödsinn. 1 | 1 bleibt 1. Entweder du machst dir Gedanken über die Berechtigungen, die du brauchst oder du nimmst MAXIMUM_ALLOWED (0x02000000). Unter Umständen erfodert das aber deutlich mehr Code, da du dich mit dem Security Descriptor für deinen Service rumschlagen darfst. Auch hier hilft wieder nur MSDN lesen (GetSecurityInfo, SetSecurityInfo).

Wie auch immer. Mir fällt grade so ein, dass du doch neulich schonmal so einen Thread offen hattest, oder? Wenn ich mich recht entsinne, wolltest du das damals remote lösen und da habe ich dich auf die WTS API verwiesen :) Da du es nun lokal machst, vermute ich mal, dass du das Projekt eingestampft hast? :)


yxterwd schrieb:
Ich halte es aber für sehr gewagt einen Service in .Net zu schreiben (vorallem wenn du nur native Calls machst)...

Mir ist neulich sogar zu Ohren gekommen, dass Microsoft sich von CLR-Services distanziert ;)
 
Hmm... na gut werd ich gleich mal ausprobieren. :)

PS: Dein Code gibt ja nicht WinSta0 zurück. Er gibt mir "Service-0x0-3e7$" zurück! Ich dachte nur ich probier selber der OpenDesktop-Methode "WinSta0" als String zu übergeben und das hat eben nicht geklappt ;)
 
Das mit dem "WinSta0" bezog sich auch auf meinen Code ;)
Wenn ich raten müsste, würde ich sagen, dass "Service-0x0-3e7$" der Name der WindowStation ist, in dessen Kontext dein Service läuft.
Versuch mal den Handle für "WinSta0" zu bekommen. Wenn du diesen hast, weis deinem Service diesen zu und versuch nochmal den Desktop zu öffnen.
 
Also ich wollte darauf hinweisen das ein Service in einer Standardkonfiguration nicht mit dem Desktop interagieren darf

By default, services use a noninteractive window station and cannot interact with the user. However, an interactive service can display a user interface and receive user input.

Das muss explizit erlaubt werden:

However, note that the following registry key contains a value, NoInteractiveServices, that controls the effect of SERVICE_INTERACTIVE_PROCESS:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows

The NoInteractiveServices value defaults to 1, which means that no service is allowed to run interactively, regardless of whether it has SERVICE_INTERACTIVE_PROCESS. When NoInteractiveServices is set to a 0, services with SERVICE_INTERACTIVE_PROCESS are allowed to run interactively.

Und dann noch:
Important All services run in Terminal Services session 0. Therefore, if an interactive service displays a user interface, it is visible only to the user who connected to session 0. Because there is no way to guarantee that the interactive user is connected to session 0, do not configure a service to run as an interactive service under Terminal Services or on a system that supports fast user switching (fast user switching is implemented using Terminal Services).
 
Hallo Leute,
habe erfreuliche Neuigkeiten...

Ich hab es endlich geschafft meine Screenshots vom Desktop zu bekommen (und das ohne irgendwelche Einstellungen vom Service zu ändern ;) )...

Also bei holy muss ich mich herzlich für seine Hilfe bedanken und den kleinen Rest der mir noch gefehlt hat zum Ziel... tja... da hat mir dieser Artikel hier auf CodeProject verdammt weitergeholfen :D :D

http://www.codeproject.com/Articles/23176/Create-a-system-tray-icon-and-a-dialog-for-a-Windo

Also falls jemand ebenfalls irgendwie solche Probleme hat, schaut euch die Schnipsel hier und natürlich in dem Artikel an. :)


EDIT: Ich habe es allerdings nicht unter Windows Vista/7 testen können. Also unter XP läuft es auf jeden fall und ich denke unter Windows 7 sollte es auf diese Weise auch keine Probleme geben ;)
 
Also es war ein rießen, rießen Irrtum...

seit einer Woche versuche ich nun schon den selben Dienst unter diesem verdammten Windows 7 zum Laufen zu bringen, aber völlig ohne Ergebnisse!!


Habt ihr vielleicht eine Idee oder Tipps was ich anstellen könnte, damit das ganze auch unter Windows 7 läuft? XP ist ja wie gesagt kein Problem... es verbindet, es macht Screenshots und die Eingaben funktionieren auch...

Unter Windows 7 hab ich das Problem, dass ich mich zwar über das Netzwerk zum Dienst connecten kann, aber er mir die Screenshots nicht macht/anzeigt (ist nur ein schwarzes Bild) und die Benutzereingaben führt dieser auch nicht aus!!

Jemand eine Idee oder einen Plan? Bin wirklich am verzweifeln :heul:
 
Zurück
Oben