Programmieren mit C++

Allgemeines

COM

COM Pipes

Was sind COM Pipes?

Frage

DCE RPC kennt ein Konzept namens „Pipes“. Die dahinterstehende Idee besagt, dass Client und Server für den effizienten Datentransfer einen Kanal zwischen sich aufbauen.

RPC Pipes verlangen allerdings einigen Aufwand bei der Programmierung, denn sie erfordern neben der Definition der Pipe und der zu transferierenden Daten auch Speicherallokierungs- und Deallokierungsfunktionen sowie den Code für den tatsächlichen Datentransfer. Dabei gilt es allerdings auseinanderzuhalten, dass RPC Pipes als RPC-Mechanismus definiert und nicht mit dem Netzwerkkonzept der Named Pipes zu verwechseln sind, auch wenn RPC als Layer über Named Pipes implementiert und Named Pipes für den Transport verwendet werden kann.

COM Pipes stehen unter Windows 2000 zur Verfügung und sind das COM-Äquivalent zu DCE Pipes. Sie finden Einsatz für den Transfer größerer Buffer mit BYTE, long oder double Werten, wofür COM drei Interfaces definiert: IPipeByte, IPipeLong und IPipeDouble. COM unterstützt den Marshalling code für diese Interfaces in OLE32.DLL. An der Oberfläche sehen diese Interfaces recht harmlos aus, wie nachfolgend am Beispiel von IPipeByte zu sehen ist. Mit Push() und Pull() werden lediglich zwei Methoden exportiert.

[  object, 
   uuid(DB2F3ACA-2F86-11d1-8E04-00C04FB9989A), 
   async_uuid(DB2F3ACB-2F86-11d1-8E04-00C04FB9989A), 
   pointer_default(unique)]
interface IPipeByte : IUnknown
{
   HRESULT Pull( [out, size_is(cRequest), 
                       length_is(*pcReturned)] BYTE* buf, 
                 [in]  ULONG  cRequest, 
                 [out] ULONG* pcReturned);
   HRESULT Push( [in, size_is(cSent)] BYTE* buf, 
                 [in] ULONG cSent);
}

Als Erstes fällt auf, dass Pipes sowohl synchron als auch asynchron arbeiten können. Ferner sind Pipes bi-direktional, so dass eine einmal aufgebaute Pipe sowohl Daten vom Pipe Implementor zum Datenkonsumer als auch vom Datenkonsumer zum Implementor übertragen kann. Ausgeführt werden die Transporte über die Methoden Pull(), mit der Daten angefordert werden und Push(), die Daten versendet.

Allerdings leisten Pipes noch mehr. Der Pipe Marshalling Code von Windows 2000 implementiert vorausschauendes Lesen. Dies bedeutet, dass COM, nachdem der Piper-Konsument einen Buffer vom Implementor erhalten hat, bereits nach dem nächsten Buffer Ausschau hält, bevor überhaupt eine neue Anforderung vorliegt.

Da in der Regel größere Datenmengen angefordert werden, kann man davon ausgehen, dass der Datenkonsument, nachdem er einen Buffer voll mit Daten erhalten hat, einige Zeit zur Verarbeitung benötigt, bevor die nächste Anforderung erfolgen kann. Die Übertragung des nächsten Datenbuffers über das Netz wird jedoch ebenfalls Zeit in Anspruch nehmen, so dass eine doppelte Laufzeiteinbuße für die Anwendung auftritt: Das erste Performanceloch entsteht durch die Verarbeitungszeit der Daten, während der die Datenquelle Idle ist, da keine weitere Anfrage erfolgt, das zweite entsteht dadurch, dass für die Dauer der Datenübertragung der Datenkonsument blockiert ist, da er nicht weiterarbeiten kann, bevor die Daten eingetroffen sind.

Der COM Pipe Marshalling Code umgeht die geschilderten Engpässe durch vorausschauendes Lesen, so dass während der Bearbeitung der angelieferten Daten durch den Konsumenten COM bereits die nächsten Daten von der Datenquelle anfordert, in der Erwartung, dass der Konsument ein weiteres Datenpaket anfordert, das COM dann bereits für den Konsumenten empfangen hätte und sofort übergeben könnte.

Ein solcher Mechanismus lässt sich natürlich auch auf Anwendungsebene unter Verwendung asynchroner, nicht blo- ckierender COM Calls realisieren, allerdings nicht so elegant.

Zu beachten ist dabei allerdings, dass COM nur den Marshalling Code bereitstellt, nicht jedoch die Implementation der tatsächlichen Pipes. Der Code für Pull() und Push() ist nach wie vor vom Entwickler zu schreiben.

Grundsätzlich gibt es zwei Möglichkeiten, Pipes einzusetzen, abhängig davon, ob die Pipes auf der Client- oder Serverseite implementiert werden. Implementiert der Client die Pipe, benötigt er ein Objekt, das eines der Pipe-Interfaces implementiert, das anschließend an die Serverkomponente in Form eines [in]-Parameters einer Interface-Methode übergeben wird.

Abbildung 5/2.7.6.1-1: Client implementiert das Pipe-Objekt und der Server ruft Methoden der Clientseite auf

Andererseits kann auch der Server die Pipe implementieren. In diesem Fall wird das Pipe-Interface über einen [out]-Zeiger an den Client übergeben, so dass der Client die Methoden der Pipe aufrufen kann.

Abbildung 5/2.7.6.1-2: Component implementiert das Pipe-Objekt, und die Clientaufrufe erfolgen auf der Serverseite

Zur Unterstützung des Konzepts der vorausschauenden Leseoperationen muss eine Pipe anzeigen, wenn keine weiteren Daten mehr zu übertragen sind. Dazu muss die Implementation der Methode Pull() die angeforderten Daten in cRequest übernehmen und die Anzahl der tatsächlich übertragenen Datenelemente im Parameter pcReturned mitteilen. COM nimmt dann weitere vorausschauende Aufrufe von Pull() vor, bis in pcReturned der Wert 0 anzeigt, dass keine weiteren Daten vorhanden sind. Dies bedeutet, dass immer ein zusätzlicher Aufruf an das Pipe-Interface notwendig ist, um das Ende der Übertragung festzustellen, da COM nicht automatisch annimmt, dass der Vergleich

*pcReturned != cRequest

für keine weiteren Daten steht. Dies bringt andererseits den Vorteil mit sich, dass nicht immer alle angeforderten Daten geliefert werden müssen. Dies wiederum verhilft der Pipe zu mehr Stabilität, da es durchaus sein kann, dass eine Datenquelle die Daten nicht so schnell bereitstellen kann, wie COM die Daten via Pull() anfordert.

Eine mögliche Implementation der Methode Pull() könnte wie nachfolgend beschrieben aussehen.

HRESULT CMyObj::Pull(BYTE* pBuf, 
                     ULONG cRequest, 
                     ULONG* pcReturned){
  if (bNoMoreData)
  {
    *pcReturned = 0;
    return S_OK;
  }
  *pcReturned = GetDataFromSomewhere(pBuf, cRequest);
  return S_OK;}

Hierbei ist die Methode GetDataFromSomewhere() eine benutzerdefinierte Methode innerhalb der Datenquelle, die die zu übertragenden Daten erzeugt oder bereitstellt.

Im Datenkonsument kann die Abfrage in Form einer Schleife implementiert werden, wobei die nachfolgend eingesetzte benutzerdefinierte Methode ProcessData() hier für das Verarbeiten der Daten zuständig ist. In den Pull()-Aufrufen werden jeweils Blöcke zu 1.000 Bytes angefordert.

Lösung

ULONG ret;
do
{
  pPipe->Pull(buf, 1000, &ret);
  if (ret) 
    ProcessData(buf, ret);
}
while (ret !=0)

Die Methode Push() wird verwendet, um Daten von einem Konsumenten an den Pipe Implementator zu senden. Auch hier managt COM den Netztraffic und puffert die Daten, so dass der Pipe-Aufrufer mit dem Senden fortfahren kann, auch wenn die Pipe-Implementation noch mit dem Verarbeiten der Daten beschäftigt ist.

Eine Implementation von Push() könnte wie folgt aussehen:

Datenabfrage von Konsument

HRESULT CMyObj::Push(BYTE* pBuf, ULONG cSent)
{
  ProcessData(pBuf, cSent);
  return S_OK;
}

Der Konsument kann das Versenden von 1.500 Bytes in 1.000 großen Blöcken über die nachfolgenden Calls realisieren.

Methode Push

pPipe->Push(buf, 1000);
pPipe->Push(buf + 1000, 500);
pPipe->Push(0, 0);

Der abschließende Aufruf von Push() mit den beiden Nullen als Parameter informiert COM darüber, dass keine weiteren Daten gesendet werden. Da Pipes bi-direktional sind, kann der Code der Anwendung sowohl Push() als auch Pull() über die gleiche Pipe einsetzen, beispielsweise um Daten via Push() an einen Service zu senden und das Ergebnis via Pull() wieder abzuholen.

Daten versenden

Die asynchronen Versionen der Pipe-Interfaces erlauben den Transfer größerer Datenmengen, ohne den jeweiligen Thread zu blockieren. Zum Abfragen von Daten muss der Datenkonsument die Übertragung via Begin_Pull() bei COM anmelden, wobei die Anzahl der benötigten Items als Parameter zu spezifizieren ist. Der Code kann dann andere Aufgaben abarbeiten und später via Finish_Pull() die Daten abholen. Dazu kopiert COM die zwischenzeitlich beschafften Daten in den übergebenen Buffer. Analog dazu erfordert die asynchrone Version von Push() einen Begin_Push() Call. Anschließend können Daten übertragen und die Übertragung mittels Finish_Push() beendet werden, wobei dieser Call lediglich zur Bereinigung der COM-internen Ressourcen führt.

Asynchrone Pipes

Ein abschließender Hinweis: Zum Kompilieren von Pipes muss stdafx.h _WIN32_WINNT den Wert 0x500 besitzen.

Header und LIB





Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 01:25:54 von textarchiv.alojado.de