WMI-Fehlermeldung RPC_E_CANTCALLOUT_ININPUTSYNCCALL
-
Hi community,
in meinem Programm nutze ich u.a. WMI, um die Namen der seriellen Schnittstellen anzeigen zu können.
Die WMI-Klasse ist "Win32_SerialPort".Wenn ich eine WM_DEVICECHANGE Nachricht bekomme (z.B. wenn ein USB-to-RS232 Konverter eingesteckt wird), mache ich einen Scan der verfügbaren COM-Ports und ermittle dazu die "user-friendly" Namen.
Das Gerüst ist wie folgt aufgebaut:
(Kommentare wurden entfernt, damit die Übersichtlichkeit nicht darunter leidet)hresult = CoInitialize( NULL ); if ( SUCCEEDED( hresult ) ) { hresult = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL ); if ( SUCCEEDED( hresult ) ) { pIWbemLocator = NULL; hresult = CoCreateInstance( CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (void**)(&pIWbemLocator) ); if ( SUCCEEDED( hresult ) ) { pIWbemServices = NULL; hresult = pIWbemLocator->ConnectServer( _bstr_t( "\\\\.\\root\\cimv2" ), NULL, NULL, NULL, 0, NULL, NULL, &pIWbemServices ); if ( SUCCEEDED( hresult ) ) // <-- hier bekomme ich den Fehlercode 0x8001010D. { hresult = CoSetProxyBlanket( pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE ); if ( SUCCEEDED( hresult ) ) { pIEnumWbemClassObject = NULL; hresult = pIWbemServices->CreateInstanceEnum( _bstr_t( "Win32_SerialPort" ), WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &pIEnumWbemClassObject ); if ( SUCCEEDED( hresult ) ) { // ... ab hier Enumerierung von Ports und Verarbeitung der Daten ... } if ( pIEnumWbemClassObject != NULL ) { pIEnumWbemClassObject->Release(); } } } if ( pIWbemServices != NULL ) { pIWbemServices->Release(); } } if ( pIWbemLocator != NULL ) { pIWbemLocator->Release(); } } } CoUninitialize();
Bei der Methode pIWbemLocator->ConnectServer() bekomme ich für hresult jedoch eine Fehlermeldung:
0x8001010D RPC_E_CANTCALLOUT_ININPUTSYNCCALL An outgoing call cannot be made since the application is dispatching an input-synchronous call.
In der MSDN ist z.B. Microsoft KB131056 "PRB: Synch OLE Call Fails in Inter-Process/Thread SendMessage" http://support.microsoft.com/kb/131056 zu finden.
Aber der dortige Lösungsvorschlag "Use PostMessage instead of an inter-process/inter-thread SendMessage." kann ich so bei WMI nicht anwenden.Ich hoffe daß ich das Symptom so richtig verstanden habe:
Durch die Erkennung eines Gerätes startet Windows einen (mir unbekannten) Prozeß.
Dieser Prozeß aktualisiert offensichtlich die WMI-Datenstrukturen und sendet anschließend eine Broadcast-Nachricht WM_DEVICECHANGE.
Meine Applikation empfängt diese Nachricht und versucht sofort die Daten von WMI zu ergattern.
Und genau dies schlägt fehl, da OLE (darauf baut ja WMI auf) synchrone OLE-Calls von einem anderen Prozeß blockt.Daraufhin habe ich mein Gerüst so abgeändert, daß bei Eintreten von RPC_E_CANTCALLOUT_ININPUTSYNCCALL per Timer zu einem späteren Zeitpunkt (z.Zt. auf 4 sek) nochmal versucht wird die Daten von WMI einzulesen:
hresult = pIWbemLocator->ConnectServer( _bstr_t( "\\\\.\\root\\cimv2" ), NULL, NULL, NULL, 0, NULL, NULL, &pIWbemServices ); if ( SUCCEEDED( hresult ) ) //hier Fehlercode 0x8001010D. { ... } else { if ( hresult == RPC_E_CANTCALLOUT_ININPUTSYNCCALL ) { b_serialport_friendlyname_wmi_retry = 1; //Merker setzen, daß WMI später nochmal aufgerufen werden muß. } } ... if ( b_serialport_friendlyname_wmi_retry != 0 ) { SetTimer( hwnd_dialog, ID_TIMER_DIALOG_SETUPCOMPORT, 4000, NULL ) ); //Timer für 4s einrichten. }
Mit diesem Workaround (und Nachricht WM_TIMER) scheint es zu funktionieren.
Allerdings erscheint mir dieser Weg mit dem Timer als unprofessionell, da ich ja normalerweise nicht weiß, ab wann WMI wieder für andere Prozesse verfügbar ist.
Gibt es da andere (zuverlässigere) Möglichkeiten?Martin
-
Hmmm, niemand einen (kleinen) Tipp für mich parat?
Vielleicht ist es ein wenig zu kompliziert das Thema?In der Zwischenzeit habe ich eine vage Möglichkeit über InSendMessage() und ReplyMessage() gefunden, ohne jedoch 100%ig darüber im Klaren zu sein:
Ausgangssituation ist KB131056 (siehe Link oben):
OLE determines if the caller of the synchronous call is a recipient of an input-synchronized call by using the InSendMessage() API. This broad check prevents a synchronous call from being made if the caller is currently a recipient of any inter-process/inter-thread SendMessage.
Auszug aus "Using Messages and Message Queues" http://msdn.microsoft.com/en-us/library/ms644928(VS.85).aspx :
Before processing a message that may have been sent from another thread, a window procedure should first call the InSendMessage function. If this function returns TRUE, the window procedure should call ReplyMessage before any function that causes the thread to yield control, as shown in the following example.
case WM_USER + 5: if (InSendMessage()) ReplyMessage(TRUE); DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc); break;
InSendMessage() liefert mir das korrekte Ergebnis, daß die gerade behandelte Nachricht von einem anderen Thread stammt. Nämlich von diesem (vermuteten) Prozeß welcher die WM_DEVICECHANGE gesendet hat.
Aber ReplyMessage() hat nicht geschafft, den blockierten WMI- bzw. die OLE-Zugriff wieder freizumachen.Was genau bewirkt eigentlich ReplyMessage() ?
Was versteht man hier unter "... that causes the thread to yield control, ..." ?Martin
-
Mmacher schrieb:
Hmmm, niemand einen (kleinen) Tipp für mich parat?
Doch
Ist eigentlich ganz einfach...
Das Problem entsteht dadurch, dass die WM_Irgendwas via RPC in Deinen Prozess kommt... nun arbeitest Du diese Nachricht "synchron" ab, und willst dabei wieder COM-Aufrufe tätigen (was RPC-Aufrufe sind). Dies ist so nicht möglich!
Du kannst in einem RPC-IN_SYNC_CALL keine Out-RPC Calls machen...Ich würde es wie folgt lösen:
Schicke Dir selber eine Nachricht (WM_APP+x), um Dir selber mitzuteilen, dass Du etwas tun musst (kannst ja die Parameter mitgeben, die Du benötigst). Dadurch wird in der MessageQueue aus der RPC-Bearbeitung rausgegangen und Du kannst jetzt wieder RPC-Methoden aufrufen.[/quote]
-
Hallo Jochen,
Danke für die Bestätigung meiner Vermutung.
Jochen Kalmbach schrieb:
Schicke Dir selber eine Nachricht (WM_APP+x), um Dir selber mitzuteilen, dass Du etwas tun musst (kannst ja die Parameter mitgeben, die Du benötigst). Dadurch wird in der MessageQueue aus der RPC-Bearbeitung rausgegangen und Du kannst jetzt wieder RPC-Methoden aufrufen.
Das ist eine plausible Lösung, nach der ich gesucht habe!
Mit PostMessage() und einem "WM_USER + x" kann ich eine solche Nachricht generieren (natürlich nur an mein eigenes Fenster, kein Broadcast).
Das kriege ich hin.Spontan habe ich eine weiterführende Überlegung gefaßt:
Es müßte sogar reichen, daß ich an mein eigenes Fenster erneut PostMessage() mit WM_DEVICECHANGE (mit den originalen wParam und lParam Parametern) sende.
Die Unterscheidung ob originale oder meine interne Nachricht liefert mir dann InSendMessage().Allerdings bin ich mir da nicht sicher, ob das überhaupt zulässig ist.
Daß ich eine solche Systemnachricht an mich selbst simulieren darf.
Gibt es da vielleicht "üble Stolperfallen"?Martin
-
Ich persönlich wäre da etwas vorsichtig
-
Nun, am Wochenende habe ich einige Experimente und genaue Analysen durchgeführt (uff, etliche Stunden um genau zu sein
).
Die Methode, die Jochen mit "WM_APP + x" vorgeschlagen hat, scheint 100%ig zuverlässig zu sein. Nochmal Danke dafür!(Anmerkung: nicht mit "WM_USER + x" wie ich weiter oben geschrieben hab!)
Am besten eine systemweit eindeutige Nachricht mit RegisterWindowMessage() von Windows generieren lassen!Mein anfänglicher Workaround mit der Timer-Variante ist zwar nicht mehr nötig, hab ihn aber für zukünftige Überraschungen (z.B. andere unerwartete WMI-Fehlermeldungen) dringelassen. So quasi als "doppelter Boden".
Übrigens, von meiner (zugegeben gewagten) Idee mit der Systemnachricht WM_DEVICECHANGE an mich selbst zu senden, bin ich wieder abgekommen.
Denn PostMessage() liefert mir den Rückgabewert 0, und GetLastError() den Wert 1159:1159 (0x487) ERROR_MESSAGE_SYNC_ONLY The message can be used only with synchronous operations.
Von der MSDN hätte ich mir ein schon ein wenig mehr Dokumentation in solchen Sonderfällen wie RPC_E_CANTCALLOUT_ININPUTSYNCCALL gewünscht.
So, nun kann ich mich wieder anderen (Alltags-)Aufgaben widmen.
Martin