Sdílet prostřednictvím


Správa paměti zástupných procedur serveru

Úvod do správy paměti Server-Stub

MidL generované zástupné procedury fungují jako rozhraní mezi procesem klienta a procesem serveru. Klient zařazuje všechna data předaná parametrům označeným atributem [in] a odešle je na zástupný kód serveru. Zástupný kód serveru při přijetí těchto dat rekonstruuje zásobník volání a pak spustí odpovídající uživatelem implementovanou funkci serveru. Zástupný kód serveru také zařadí data parametrů označená atributem [out] a vrátí je do klientské aplikace.

32bitový zařazovaný formát dat používaný msRPC je kompatibilní verze syntaxe přenosu síťového datového reprezentace (NDR). Další informace o tomto formátu naleznete v tématu Web Open Group. U 64bitových platforem je možné pro lepší výkon použít 64bitové rozšíření Microsoftu pro přenos oznámení o nedoručení s názvem NDR64.

Zrušení ohraničení příchozích dat

V MSRPC zařazuje klient všechny parametry data označená jako [in] v jedné souvislé vyrovnávací paměti pro přenos na zástupný kód serveru. Stejně tak server zařazuje všechna data označená atributem [out] v souvislé vyrovnávací paměti, aby se vrátila k zástupným procedurám klienta. Zatímco vrstva síťového protokolu pod RPC může fragmentovat a za paketovat vyrovnávací paměť pro přenos, fragmentace je transparentní pro zástupné procedury RPC.

Přidělení paměti pro vytvoření rámce volání serveru může být nákladnou operací. Zástupný procedura serveru se pokusí minimalizovat nepotřebné využití paměti, pokud je to možné, a předpokládá se, že rutina serveru neobsadí data označená [in] nebo [in, out] atributy. Zástupný procedura serveru se pokusí znovu použít data ve vyrovnávací paměti, kdykoli je to možné, aby nedocházelo k zbytečným duplicitám. Obecné pravidlo je, že pokud je zařazovaný formát dat stejný jako formát paměti, RPC použije ukazatele na zařazovaná data místo přidělení další paměti pro identicky formátovaná data.

Například následující volání RPC je definováno strukturou, jejíž zařazovaný formát je identický s jeho formátem v paměti.

typedef struct RpcStructure
{
    long val;
    long val2;
}

void ProcessRpcStructure
(
    [in]  RpcStructure *plInStructure;
    [out] RpcStructure *plOutStructure;
);

V tomto případě RPC nepřiděluje další paměť pro data odkazovaná plInStructure; místo toho jednoduše předá ukazatel na zařazovaná data do implementace funkce na straně serveru. Procedura serveru RPC ověřuje vyrovnávací paměť během procesu zrušení uspořádání, pokud je zástupný kód zkompilován pomocí příznaku "-robust" (což je výchozí nastavení v nejnovější verzi kompilátoru MIDL nmost). RPC zaručuje platnost dat předaných implementaci funkce na straně serveru.

Uvědomte si, že paměť je přidělena pro plOutStructure, protože pro něj nejsou předána žádná data.

Přidělení paměti pro příchozí data

Mohou nastat případy, kdy zástupný procedura serveru přidělí paměť pro data parametrů označená [in] nebo [in, out] atributy. K tomu dochází, když se zařazovaný datový formát liší od formátu paměti nebo pokud struktury, které obsahují zařazovaná data, jsou dostatečně složité a musí být načteny atomicky pomocí procedur serveru RPC. Níže je uvedeno několik běžných případů, kdy musí být paměť přidělena pro data přijatá serverovou procedurou.

  • Data jsou různá pole nebo vyhovující pole. Jedná se o pole (nebo ukazatele na pole), které mají [length_is()] nebo [first_is()] atribut nastavený na ně. V oznámení o nedoručení jsou zařazovány a přenášeny pouze první prvky těchto polí. Například v následujícím fragmentu kódu budou mít data předaná v parametru pv přidělené paměti.

    void RpcFunction
    (
        [in] long size,
        [in, out] long *pLength,
        [in, out, size_is(size), length_is(*pLength)] long *pv
    );
    
  • Data jsou řetězec velikosti nebo neodpovídající řetězec. Tyto řetězce jsou obvykle ukazatele na znaková data označená atributem [size_is()]. V následujícím příkladu bude mít řetězec předaný funkci SizedString server-side paměť, zatímco řetězec předaný NormalString funkce bude znovu použit.

    void SizedString
    (
        [in] long size,
        [in, size_is(size), string] char *str
    );
    
    void NormalString
    (
        [in, string] char str
    );
    
  • Data jsou jednoduchý typ, jehož velikost paměti se liší od jeho zařazované velikosti, například výčtu 16 a __int3264.

  • Data jsou definována strukturou, jejíž zarovnání paměti je menší než přirozené zarovnání, obsahuje některý z výše uvedených datových typů nebo má koncové bajtové odsazení. Například následující složitá datová struktura má vynucené zarovnání 2 bajtů a má na konci odsazení.

#pragma pack(2) typedef struktury ComplexPackedStructure { char c;
dlouhý l; zarovnání je vynuceno na druhém bajtovém znaku c2; bude obsahovat koncovou jednobajtůovou podložku, která zajistí zarovnání 2 bajtů } '''

  • Data obsahují strukturu, která musí být zařazována pole podle pole. Tato pole zahrnují ukazatele rozhraní definované v rozhraních modelu DCOM; ignorované ukazatele; celočíselné hodnoty nastavené atributem [range]; prvky polí definovaných [wire_marshal], [user_marshal], [transmit_as] a [represent_as] atributy; a vložené složité datové struktury.
  • Data obsahují sjednocení, strukturu obsahující sjednocení nebo pole sjednocení. Na drátě je zařazována pouze konkrétní větev sjednocení.
  • Data obsahují strukturu s vícerozměrným polem, které má alespoň jednu nepevného rozměru.
  • Data obsahují pole složitých struktur.
  • Data obsahují pole jednoduchých datových typů, například výčtu a __int3264.
  • Data obsahují pole ukazatelů ref a rozhraní.
  • Data mají [force_allocate] atribut použitý u ukazatele.
  • Data mají [allocate(all_nodes)] atribut použitý u ukazatele.
  • Data mají [byte_count] atribut použitý u ukazatele.

64bitová syntaxe přenosu dat a oznámení NDR64

Jak už bylo zmíněno dříve, 64bitová data se zařaďují pomocí konkrétní 64bitové syntaxe přenosu označované jako NDR64. Tato syntaxe přenosu byla vyvinuta pro řešení konkrétního problému, ke kterému dochází, když jsou ukazatele zařazovány do 32bitového oznámení o nedoručení a přenášeny na 64bitovou platformu na serverovou proceduru. V tomto případě zařazovaný 32bitový datový ukazatel neodpovídá 64bitovému ukazateli a přidělení paměti bude vždy probíhat. Microsoft vyvinul novou syntaxi přenosu s názvem NDR64, aby vytvořil konzistentnější chování na 64bitových platformách.

Příklad ilustrující tento problém je následující:

typedef struct PtrStruct
{
  long l;
  long *pl;
}

Tato struktura při zařazování bude opakovaně použita serverovou zástupný procedurou v 32bitovém systému. Pokud se však zástupný procedura serveru nachází v 64bitovém systému, zařazovaná data o nedoručení mají délku 4 bajty, ale požadovaná velikost paměti bude 8. V důsledku toho je přidělení paměti vynucené a opakované použití vyrovnávací paměti bude zřídka probíhat. NDR64 tento problém řeší tím, že nastaví zařazovanou velikost ukazatele 64 bitů.

Na rozdíl od 32bitového oznámení o nedoručení se jednoduché datové tyes, jako jsou výčtu 16 a __int3264, nedělají strukturu nebo pole složité v rámci oznámení o nedoručení64. Stejně tak koncové hodnoty na konci nedělají strukturu složitou. Ukazatele rozhraní jsou považovány za jedinečné ukazatele na nejvyšší úrovni; v důsledku toho se struktury a pole obsahující ukazatele rozhraní nepovažují za složité a nebudou pro jejich použití vyžadovat konkrétní přidělení paměti.

Inicializace odchozích dat

Po zrušení všech příchozích dat musí server inicializovat ukazatele pouze pro odchozí spojení označené atributem [out].

typedef struct RpcStructure
{
    long val;
    long val2;
}

void ProcessRpcStructure
(
    [in]  RpcStructure *plInStructure;
    [out] RpcStructure *plOutStructure;
);

Ve výše uvedeném volání musí zástupný procedura serveru inicializovat plOutStructure, protože nebyla přítomna v zařazovaných datech, a jedná se o implicitní [odkaz] ukazatel, který musí být zpřístupněn implementaci funkce serveru. Server RPC inicializuje a vynuluje všechny ukazatele jen pro referenci nejvyšší úrovně s atributem [out]. Všechny [out] odkazové ukazatele pod ním se rekurzivně inicializují. Rekurze se zastaví u všech ukazatelů s [jedinečný] nebo [ptr] atributy nastavené na ně.

Implementace funkce serveru nemůže přímo změnit hodnoty ukazatele nejvyšší úrovně, a proto je nemůže relokovat. Například při implementaci ProcessRpcStructure výše je následující kód neplatný:

void ProcessRpcStructure(RpcStructure *plInStructure, rpcStructure *plOutStructure)
{
    plOutStructure = MIDL_user_allocate(sizeof(RpcStructure));
    Process(plOutStructure);
}

plOutStructure je hodnota zásobníku a její změna se nešíruje zpět do RPC. Implementace funkce serveru se může pokusit vyhnout přidělení pokusem o uvolnění plOutStructure, což může vést k poškození paměti. Zástupný kód serveru pak přidělí místo pro ukazatel nejvyšší úrovně v paměti (v případě ukazatele na ukazatel) a jednoduchou strukturu nejvyšší úrovně, jejíž velikost v zásobníku je menší, než se čekalo.

Klient může za určitých okolností určit velikost přidělení paměti na straně serveru. V následujícím příkladu klient určuje velikost odchozích dat v příchozí velikosti parametru.

void VariableSizeData
(
    [in] long size,
    [out, size_is(size)] char *pv
);

Po zrušení omezení příchozích dat, včetně velikosti, server zástupný kód přidělí vyrovnávací paměť pro pv s velikostí "sizeof(char)*size". Po přidělení místa se zástupný znak serveru vynuluje vyrovnávací paměť. Všimněte si, že v tomto konkrétním případě zástupný procedura přiděluje paměť MIDL_user_allocate(), protože velikost vyrovnávací paměti je určena za běhu.

Mějte na paměti, že v případě rozhraní DCOM nemusí být midL generované zástupné procedury vůbec zapojeny, pokud klient a server sdílejí stejný byt MODELU COM, nebo pokud ICallFrame je implementována. V takovém případě nemůže server záviset na chování přidělení a musí nezávisle ověřit paměť velikosti klienta.

Implementace funkcí na straně serveru a zařazování odchozích dat

Okamžitě po zrušení uzavření příchozích dat a inicializace paměti přidělené k zahrnutí odchozích dat provede procedura serveru RPC implementaci funkce na straně serveru volaného klientem. V tuto chvíli může server upravit data, která jsou výslovně označena atributem [in, out] a může naplnit paměť přidělenou pro odchozí data (data označená [out]).

Obecná pravidla pro manipulaci s daty s zařazovanými parametry jsou jednoduchá: Server může přidělit pouze novou paměť nebo upravit paměť určenou zástupným kódem serveru. Přidělení nebo uvolnění stávající paměti pro data může mít negativní dopad na výsledky a výkon volání funkce a může být velmi obtížné ladit.

Server RPC logicky žije v jiném adresní prostoru než klient a obecně se dá předpokládat, že nesdílí paměť. V důsledku toho je pro implementaci funkce serveru bezpečné používat data označená [in] atribut jako "pomocné" paměti, aniž by to mělo vliv na adresy klientské paměti. To znamená, že by se server neměl pokoušet o skutečné přidělení nebo uvolnění [in] dat, a ponechat kontrolu nad těmito mezerami na samotném serveru RPC.

Obecně platí, že implementace funkce serveru nemusí relokovat ani uvolnit data označená [in, out] atributu. U dat s pevnou velikostí může logika implementace funkce data přímo upravit. Podobně u dat s proměnlivou velikostí nesmí implementace funkce upravovat hodnotu pole zadanou do [size_is()] atributu. Změňte hodnotu pole, která slouží k nastavení velikosti dat, výsledkem je menší nebo větší vyrovnávací paměť vrácená klientovi, která může být špatně vybavená k řešení neobvyklé délky.

Pokud dojde k okolnostem, kdy rutina serveru musí relokovat paměť používanou daty označenými [in, out] atribut, je zcela možné, že implementace funkce na straně serveru nebude vědět, zda ukazatel poskytnutý zástupným kódem je paměť přidělena MIDL_user_allocate() nebo zařazované vyrovnávací paměti drátu. Chcete-li tento problém vyřešit, MS RPC může zajistit, aby nedošlo k nevrácení paměti nebo poškození, pokud je u dat nastaven atribut [force_allocate]. Při nastavení [force_allocate] server vždy přidělí paměť pro ukazatel, i když je upozornění, že výkon se sníží pro každé použití.

Když se volání vrátí z implementace funkce na straně serveru, server zařadí data označená atributem [out] a odešle je klientovi. Mějte na paměti, že zástupný procedura nezařazuje data, pokud implementace funkce na straně serveru vyvolá výjimku.

Uvolnění přidělené paměti

Zástupný kód serveru RPC uvolní paměť zásobníku poté, co se volání vrátí z funkce na straně serveru, bez ohledu na to, jestli dojde k výjimce nebo ne. Zástupný procedura serveru uvolní veškerou paměť přidělenou zástupným procedurou a také veškerou paměť přidělenou MIDL_user_allocate(). Implementace funkce na straně serveru musí vždy poskytovat konzistentní stav RPC buď vyvoláním výjimky nebo vrácením kódu chyby. Pokud funkce selže během základního souboru složitých datových struktur, musí zajistit, aby všechny ukazatele ukazovaly na platná data nebo jsou nastaveny na NULL.

Během tohoto průchodu server uvolní veškerou paměť, která není součástí zařazované vyrovnávací paměti obsahující [in] dat. Jednou z výjimek tohoto chování jsou data s atributem [allocate(dont_free)] nastaveným atributem – zástupný kód serveru nevyvolá žádnou paměť přidruženou k těmto ukazatelům.

Jakmile zástupný procedura serveru uvolní paměť přidělenou zástupným procedurou a implementací funkce, volání konkrétní oznamovací funkce, pokud je pro konkrétní data zadán atribut [notify_flag].

Zařazování propojeného seznamu přes RPC – příklad

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
);

Ve výše uvedeném příkladu bude formát paměti pro LINKEDLIST shodný s formátem zařazovaného drátu. V důsledku toho zástupný procedura serveru nepřiděluje paměť pro celý řetězec datových ukazatelů pod pIn. Místo toho RPC znovu použije vyrovnávací paměť pro celý propojený seznam. Podobně zástupný procedura nepřiděluje paměť pro pInOut, ale místo toho znovu použije vyrovnávací paměť přenosu zařazovanou klientem.

Vzhledem k tomu, že podpis funkce obsahuje odchozí parametr, pOut, server zástupný kód přidělí paměť, která bude obsahovat vrácená data. Přidělená paměť se zpočátku vynuluje, přičemž pNext nastavena na NULL. Aplikace může přidělit paměť pro nový propojený seznam a bod pOut–>pNext. pIn a propojený seznam, který obsahuje, lze použít jako pomocné oblasti, ale aplikace by neměla změnit žádný z ukazatelů pNext.

Aplikace může volně změnit obsah propojeného seznamu, na který odkazuje pInOut, ale nesmí změnit žádný z pNext ukazatelů, a to samotné propojení nejvyšší úrovně. Pokud se aplikace rozhodne zkrátit propojený seznam, nemůže vědět, zda některá daná pNext ukazatel odkazuje tto interní vyrovnávací paměť RPC nebo vyrovnávací paměť přidělená speciálně MIDL_user_allocate(). Chcete-li tento problém vyřešit, přidáte konkrétní deklaraci typu pro odkazované ukazatele seznamu, které vynutí přidělení uživatele, jak je vidět v kódu níže.

typedef [force_allocate] PLINKEDLIST;

Tento atribut vynutí, aby zástupný procedura serveru přidělila každý uzel propojeného seznamu samostatně a aplikace může uvolnit zkrácenou část propojeného seznamu voláním MIDL_user_free(). Aplikace pak může bezpečně nastavit pNext ukazatel na konci nově zkráceného propojeného seznamu na NULL.