Minneshantering för Server Stub
En introduktion till Server-Stub minneshantering
MIDL-genererade stubs fungerar som gränssnittet mellan en klientprocess och en serverprocess. En klient stub konverterar alla data som skickas till parametrar som har markerats med attributet [in] och skickar dem till serverns stub. Servern stub, när du tar emot dessa data, rekonstruerar anropsstacken och kör sedan motsvarande användarintegrerad serverfunktion. Serverns stub konverterar också parameterdata som har markerats med attributet [out] [out] och returnerar dem till klientprogrammet.
Det 32-bitars konverterade dataformat som används av MSRPC är en kompatibel version av NDR-överföringssyntaxen (Network Data Representation). Mer information om det här formatet finns i Webbplatsen Öppna grupp. För 64-bitarsplattformar kan ett Microsoft 64-bitars tillägg till NDR-överföringssyntax med namnet NDR64 användas för bättre prestanda.
Ta bort koppling av inkommande data
I MSRPC konverterar klienten alla parameterdata som taggats som [in] i en kontinuerlig buffert för överföring till serverns stub. På samma sätt konverterar servern alla data som markerats med attributet [out] i en kontinuerlig buffert för att återgå till klientens stub. Även om nätverksprotokollskiktet under RPC kan fragmentera och paketisera bufferten för överföring, är fragmenteringen transparent för RPC-stubs.
Minnesallokering för att skapa serveranropsramen kan vara en dyr åtgärd. Serverns stub försöker minimera onödig minnesanvändning när det är möjligt, och det antas att serverrutinen inte frigör eller omallokerar data som markerats med [i] eller [in, ut] attribut. Serverns stub försöker återanvända data i bufferten när det är möjligt för att undvika onödig duplicering. Den allmänna regeln är att om det konverterade dataformatet är detsamma som minnesformatet använder RPC pekare till de marshallerade data i stället för att allokera ytterligare minne för identiskt formaterade data.
Till exempel definieras följande RPC-anrop med en struktur vars konverterade format är identiskt med dess minnesinterna format.
typedef struct RpcStructure
{
long val;
long val2;
}
void ProcessRpcStructure
(
[in] RpcStructure *plInStructure;
[out] RpcStructure *plOutStructure;
);
I det här fallet allokerar RPC inte ytterligare minne för de data som refereras av plInStructure; I stället skickar den bara pekaren till de konverterade data till funktionsimplementeringen på serversidan. RPC-serverns stub verifierar bufferten under unmarshaling-processen om stub kompileras med flaggan "-robust" (vilket är en standardinställning i den senaste versionen av MIDL-kompilatorn). RPC garanterar att data som skickas till funktionsimplementeringen på serversidan är giltiga.
Tänk på att minne allokeras för plOutStructureeftersom inga data skickas till servern för det.
Minnesallokering för inkommande data
Det kan uppstå fall där servern stub allokerar minne för parameterdata som markerats med [in] eller [in, ut] attribut. Detta inträffar när det konverterade dataformatet skiljer sig från minnesformatet, eller när de strukturer som utgör de konverterade data är tillräckligt komplexa och måste läsas atomiskt av RPC-serverns stub. Nedan visas flera vanliga fall när minne måste allokeras för data som tas emot av serverns stub.
Data är en varierande matris eller en överensstämmande varierande matris. Det här är matriser (eller pekare till matriser) som har [length_is()] eller [first_is()] attribut inställt på dem. I NDR konverteras och överförs endast det första elementet i dessa matriser. I kodfragmentet nedan kommer till exempel data som skickas i parametern pv att ha allokerat minne för den.
void RpcFunction ( [in] long size, [in, out] long *pLength, [in, out, size_is(size), length_is(*pLength)] long *pv );
Data är en sträng av storlek eller icke-överensstämmande sträng. Dessa strängar är vanligtvis pekare till teckendata taggade med attributet [size_is()]. I exemplet nedan kommer strängen som skickas till funktionen SizeString på serversidan att ha allokerat minne, medan strängen som skickas till funktionen NormalString återanvänds.
void SizedString ( [in] long size, [in, size_is(size), string] char *str ); void NormalString ( [in, string] char str );
Data är en enkel typ vars minnesstorlek skiljer sig från dess konverterade storlek, till exempel uppräkning16 och __int3264.
Data definieras av en struktur vars minnesjustering är mindre än den naturliga justeringen, innehåller någon av ovanstående datatyper eller har avslutande byte-utfyllnad. Till exempel har följande komplexa datastruktur tvingat fram justering med 2 byte och utfyllnad i slutet.
#pragma pack(2) typedef struct ComplexPackedStructure { char c;
long l; justeringen framtvingas vid den andra bytetecken c2; det kommer att finnas en avslutande 1 bytes pad för att hålla 2-byte justering } '''
- Data innehåller en struktur som måste konverteras fält för fält efter fält. Dessa fält innehåller gränssnittspekare som definierats i DCOM-gränssnitt. ignorerade pekare; heltalsvärden som anges med attributet [range]; element i matriser som definierats med [wire_marshal], [user_marshal], [transmit_as] och [represent_as] attribut. och inbäddade komplexa datastrukturer.
- Data innehåller en union, en struktur som innehåller en union eller en matris med fackföreningar. Endast den specifika grenen av facket är sammanställd på tråden.
- Data innehåller en struktur med en flerdimensionell konform matris som har minst en icke-fast dimension.
- Data innehåller en matris med komplexa strukturer.
- Data innehåller en matris med enkla datatyper som uppräkning16 och __int3264.
- Data innehåller en matris med referens- och gränssnittspekare.
- Data har ett [force_allocate] attribut som tillämpas på en pekare.
- Data har ett [allokera(all_nodes)] attribut som tillämpas på en pekare.
- Data har ett [byte_count] attribut som tillämpas på en pekare.
64-bitars data och NDR64-överföringssyntax
Som tidigare nämnts är 64-bitarsdata samlade med hjälp av en specifik 64-bitars överföringssyntax som kallas NDR64. Den här överföringssyntaxen utvecklades för att åtgärda det specifika problem som uppstår när pekare konverteras under 32-bitars NDR och överförs till en server-stub på en 64-bitars plattform. I det här fallet matchar inte en konverterad 32-bitars datapekare en 64-bitars, och minnesallokering sker alltid. För att skapa ett mer konsekvent beteende på 64-bitarsplattformar utvecklade Microsoft en ny överföringssyntax med namnet NDR64.
Ett exempel som illustrerar det här problemet är följande:
typedef struct PtrStruct
{
long l;
long *pl;
}
Den här strukturen, när den konverteras, återanvänds av serverns stub på ett 32-bitarssystem. Men om serverns stub finns i ett 64-bitarssystem är NDR-konverterade data 4 byte långa, men den nödvändiga minnesstorleken blir 8. Därför tvingas minnesallokering och buffertåteranvändning sker sällan. NDR64 löser det här problemet genom att göra den konverterade storleken på en pekare till 64-bitars.
Till skillnad från 32-bitars NDR gör enkla databindpunkter som uppräkning 16 och __int3264 inte en struktur eller matriskomplex under NDR64. På samma sätt gör avslutande pad-värden inte en struktur komplex. Gränssnittspekare behandlas som unika pekare på den översta nivån. Därför anses strukturer och matriser som innehåller gränssnittspekare inte vara komplexa och kräver inte specifik minnesallokering för deras användning.
Initiera utgående data
När alla inkommande data har varit obestridda måste serverns stub initiera de utgående pekare som markerats med attributet [out].
typedef struct RpcStructure
{
long val;
long val2;
}
void ProcessRpcStructure
(
[in] RpcStructure *plInStructure;
[out] RpcStructure *plOutStructure;
);
I anropet ovan måste serverns stub initiera plOutStructure- eftersom den inte fanns i de konverterade data och det är en underförstådd [ref] pekare som måste göras tillgänglig för serverfunktionens implementering. RPC-servern stub initierar och nollar alla referenspekare på den översta nivån med attributet [out]. Alla [ut] referenspekare under den initieras rekursivt också. Rekursionen stoppas vid alla pekare med [unik] eller [ptr] attribut som angetts på dem.
Serverfunktionens implementering kan inte direkt ändra pekarvärden på den översta nivån och kan därför inte omfördela dem. I implementeringen av ProcessRpcStructure ovan är till exempel följande kod ogiltig:
void ProcessRpcStructure(RpcStructure *plInStructure, rpcStructure *plOutStructure)
{
plOutStructure = MIDL_user_allocate(sizeof(RpcStructure));
Process(plOutStructure);
}
plOutStructure är ett stackvärde och dess ändring sprids inte tillbaka till RPC. Serverfunktionens implementering kan försöka undvika allokering genom att försöka frigöra plOutStructure, vilket kan leda till minnesskada. Serverns stub allokerar sedan utrymme för den översta pekaren i minnet (i pekar-till-pekar-fallet) och en enkel struktur på den översta nivån vars storlek på stacken är mindre än förväntat.
Klienten kan under vissa omständigheter ange minnesallokeringsstorleken på serversidan. I följande exempel anger klienten storleken på utgående data i den inkommande storlek parametern.
void VariableSizeData
(
[in] long size,
[out, size_is(size)] char *pv
);
När inkommande data har omfördelats, inklusive storlek, allokerar servern en buffert för pv- med storleken "sizeof(char)*size". När utrymmet har allokerats nollställs bufferten av servern. Observera att i det här fallet allokerar stub minnet med MIDL_user_allocate()eftersom buffertens storlek bestäms vid körning.
Tänk på att midl-genererade stubs inte alls är inblandade när det gäller DCOM-gränssnitt om klienten och servern delar samma COM-lägenhet, eller om ICallFrame- implementeras. I det här fallet kan servern inte vara beroende av allokeringsbeteendet och måste oberoende verifiera minne i klientstorlek.
Funktionsimplementeringar på serversidan och utgående datamarskalkering
Omedelbart efter den omarshalling på inkommande data och initieringen av det minne som allokerats för att innehålla utgående data, kör RPC-servern stub implementeringen på serversidan av funktionen som anropas av klienten. För närvarande kan servern ändra de data som är specifikt markerade med attributet [in, out] och fylla i det minne som allokerats för utgående data (data taggade med [out]).
De allmänna reglerna för manipulering av marshallerade parameterdata är enkla: servern kan bara allokera nytt minne eller ändra det minne som specifikt allokeras av serverns stub. Att omplacera eller frigöra befintligt minne för data kan ha en negativ inverkan på funktionsanropets resultat och prestanda och kan vara mycket svårt att felsöka.
Logiskt sett finns RPC-servern i ett annat adressutrymme än klienten, och det kan vanligtvis antas att de inte delar minne. Därför är det säkert för serverfunktionsimplementeringen att använda data som markerats med attributet [in] som "scratch"-minne utan att det påverkar klientminnesadresserna. Med detta sagt bör servern inte försöka omallokera eller släppa [i] data, vilket lämnar kontrollen över dessa utrymmen till RPC-servern stub själv.
I allmänhet behöver serverfunktionsimplementeringen inte omallokera eller frigöra data som markerats med [in, ut] attribut. För data med fast storlek kan funktionsimplementeringslogik direkt ändra data. För data med variabel storlek får funktionsimplementeringen inte heller ändra det fältvärde som anges till attributet [size_is()]. Ändra fältvärdet som används för att storleksanpassa data resulterar i en mindre eller större buffert som returneras till klienten som kan vara dåligt utrustad för att hantera den onormala längden.
Om det uppstår omständigheter där serverrutinen måste omallokera det minne som används av data som markerats med [in, ut] attribut, är det fullt möjligt att funktionsimplementeringen på serversidan inte vet om pekaren som tillhandahålls av stub är till minne som allokerats med MIDL_user_allocate() eller den konverterade trådbufferten. För att undvika det här problemet kan MS RPC se till att ingen minnesläcka eller skada uppstår om attributet [force_allocate] anges för data. När [force_allocate] anges allokerar serverns stub alltid minne för pekaren, även om förbehållet är att prestandan minskar för varje användning av den.
När anropet returnerar från funktionsimplementeringen på serversidan konverterar servern de data som har markerats med attributet [out] och skickar dem till klienten. Tänk på att stub inte konverterar data om funktionsimplementeringen på serversidan utlöser ett undantag.
Frigöra allokerat minne
RPC-serverns stub frigör stackminnet när anropet har returnerats från funktionen på serversidan, oavsett om ett undantag inträffar eller inte. Servern stub frigör allt minne som allokeras av stub samt allt minne som allokeras med MIDL_user_allocate(). Implementeringen av funktionen på serversidan måste alltid ge RPC ett konsekvent tillstånd, antingen genom att utlösa ett undantag eller returnera en felkod. Om funktionen misslyckas under populationen av komplicerade datastrukturer måste den se till att alla pekare pekar på giltiga data eller är inställda på NULL-.
Under det här passet frigör servern allt minne som inte ingår i den konverterade bufferten som innehåller [i] data. Ett undantag till det här beteendet är data med [allokera(dont_free)] attribut som angetts på dem – server-stub frigör inte minne som är associerat med dessa pekare.
När serverns stub släpper det minne som allokerats av stub- och funktionsimplementeringen anropar stub en specifik meddelandefunktion om [notify_flag] attribut anges för specifika data.
Samla en länkad lista över RPC – ett exempel
typedef struct _LINKEDLIST
{
long lSize;
[size_is(lSize)] char *pData;
struct _LINKEDLIST *pNext;
} LINKEDLIST, *PLINKEDLIST;
void Test
(
[in] LINKEDLIST *pIn,
[in, out] PLINKEDLIST *pInOut,
[out] LINKEDLIST *pOut
);
I exemplet ovan är minnesformatet för LINKEDLIST- identiskt med det konverterade trådformatet. Därför allokerar inte serverstuben minne för hela kedjan med datapekare under pIn. I stället återanvänder RPC trådbufferten för hela den länkade listan. På samma sätt allokerar stub inte minne för pInOut, utan återanvänder i stället trådbufferten som konverteras av klienten.
Eftersom funktionssignaturen innehåller en utgående parameter pOutallokerar serverns stub minne för att innehålla returnerade data. Det allokerade minnet nollställs ursprungligen och pNext inställt på NULL-. Programmet kan allokera minnet för en ny länkad lista och peka pOut–>pNästa till den. pIn- och den länkade lista som den innehåller kan användas som ett scratch-område, men programmet bör inte ändra någon av pNext-pekarna.
Programmet kan fritt ändra innehållet i den länkade listan som pekas på av pInOut, men det får inte ändra något av pNext pekare, för att inte tala om själva länken på den översta nivån. Om programmet bestämmer sig för att förkorta den länkade listan kan det inte veta om någon angiven pNext pekare länkar till en intern RPC-buffert eller en buffert som är specifikt allokerad med MIDL_user_allocate(). Du kan undvika det här problemet genom att lägga till en specifik typdeklaration för länkade listpekare som tvingar användaren att allokeras, enligt koden nedan.
typedef [force_allocate] PLINKEDLIST;
Det här attributet tvingar servern att allokera varje nod i den länkade listan separat, och programmet kan frigöra den förkortade delen av den länkade listan genom att anropa MIDL_user_free(). Programmet kan sedan ange pNext pekaren i slutet av den nyligen förkortade länkade listan till NULL-.