Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wprowadzenie do zarządzania pamięcią Server-Stub
Wycinki generowane przez midL działają jako interfejs między procesem klienta a procesem serwera. Klient klasy stub marshaluje wszystkie dane przekazane do parametrów oznaczonych [in] atrybutu i wysyła je do wycinku serwera. Po otrzymaniu tych danych serwer zrekonstruuje stos wywołań, a następnie wykonuje odpowiednią funkcję serwera zaimplementowaną przez użytkownika. W wycinku serwera są również marshaling danych parametrów oznaczonych [out] atrybutu i zwraca je do aplikacji klienckiej.
32-bitowy format danych marshaled używany przez msRPC jest zgodną wersją składni transferu usługi Network Data Representation (NDR). Aby uzyskać więcej informacji na temat tego formatu, zobacz witrynie sieci Web Open Group. W przypadku platform 64-bitowych można użyć rozszerzenia microsoft 64-bitowego do składni transferu NDR o nazwie NDR64 w celu uzyskania lepszej wydajności.
Unmarshaling Inbound Data
W msRPC klient klasy wycinkowej marshaluje wszystkie dane parametrów oznaczone jako [in] w jednym ciągłym buforze na potrzeby transmisji do wycinków serwera. Podobnie klasy wycinkowe serwera przeprowadza marshaling wszystkich danych oznaczonych [out] atrybutu w buforze ciągłym w celu powrotu do wycinku klienta. Podczas gdy warstwa protokołu sieciowego pod RPC może fragmentować i pakować bufor do transmisji, fragmentacja jest przezroczysta dla wycinków RPC.
Alokacja pamięci na potrzeby tworzenia ramki wywołań serwera może być kosztowną operacją. Klaska serwera podejmie próbę zminimalizowania niepotrzebnego użycia pamięci, jeśli jest to możliwe, i zakłada się, że procedury serwera nie będą zwalniać ani ponownie przydzielać danych oznaczonych [in] lub [w] atrybutów. W celu uniknięcia niepotrzebnego duplikowania serwer próbuje ponownie użyć danych w buforze. Ogólna zasada polega na tym, że jeśli format danych marshaled jest taki sam jak format pamięci, RPC będzie używać wskaźników do danych marshalled zamiast przydzielania dodatkowej pamięci dla identycznych sformatowanych danych.
Na przykład następujące wywołanie RPC jest definiowane ze strukturą, której format marshaled jest identyczny z formatem w pamięci.
typedef struct RpcStructure
{
long val;
long val2;
}
void ProcessRpcStructure
(
[in] RpcStructure *plInStructure;
[out] RpcStructure *plOutStructure;
);
W takim przypadku RPC nie przydziela dodatkowej pamięci dla danych, do których odwołuje się plInStructure; zamiast tego po prostu przekazuje wskaźnik do marshaled danych do implementacji funkcji po stronie serwera. Element wycinkowy serwera RPC weryfikuje bufor podczas procesu unmarshaling, jeśli wycink jest kompilowany przy użyciu flagi "-robust" (która jest ustawieniem domyślnym w najbardziej najnowszej wersji kompilatora MIDL). RPC gwarantuje, że dane przekazane do implementacji funkcji po stronie serwera są prawidłowe.
Należy pamiętać, że pamięć jest przydzielana dla plOutStructure, ponieważ żadne dane nie są przekazywane do serwera.
Alokacja pamięci dla danych przychodzących
Mogą wystąpić przypadki, w których klaska serwera przydziela pamięć dla danych parametrów oznaczonych [in] lub [w, out] atrybutów. Dzieje się tak, gdy format danych marshaled różni się od formatu pamięci lub gdy struktury składające się na dane marshalowane są wystarczające i muszą być odczytywane niepodziealnie przez wycinkę serwera RPC. Poniżej wymieniono kilka typowych przypadków, w których pamięć musi zostać przydzielona do danych odebranych przez klasę wycinkową serwera.
Dane są różną tablicą lub zgodną tablicą. Są to tablice (lub wskaźniki do tablic), które mają [length_is()] lub [first_is()] atrybut ustawiony na nich. W usłudze NDR tylko pierwszy element tych tablic jest marshalowany i przesyłany. Na przykład w poniższym fragmencie kodu dane przekazane w parametrze pv będą miały przydzieloną pamięć.
void RpcFunction ( [in] long size, [in, out] long *pLength, [in, out, size_is(size), length_is(*pLength)] long *pv );Dane są ciągiem o rozmiarze lub niezgodnym ciągu. Te ciągi są zwykle wskaźnikami do danych znaków oznaczonych atrybutem [size_is()]. W poniższym przykładzie ciąg przekazany do funkcji SizeString po stronie serwera będzie miał przydzieloną pamięć, natomiast ciąg przekazany do funkcji NormalString zostanie ponownie użyty.
void SizedString ( [in] long size, [in, size_is(size), string] char *str ); void NormalString ( [in, string] char str );Dane są prostym typem, którego rozmiar pamięci różni się od rozmiaru marshaled, takiego jak wyliczenie16 i __int3264.
Dane są definiowane przez strukturę, której wyrównanie pamięci jest mniejsze niż naturalne wyrównanie, zawiera dowolny z powyższych typów danych lub ma dopełnienie bajtów końcowych. Na przykład następująca złożona struktura danych wymusiła wyrównanie 2 bajtów i na końcu zostało wypełnione.
#pragma pack(2) typedef, struktura ComplexPackedStructure { char c;
długi l; wyrównanie jest wymuszane na drugim bajtowym znaku c2; wyrównywanie dwu bajtów na 2 bajtach } ""
- Dane zawierają strukturę, która musi być marshalowana według pola. Te pola obejmują wskaźniki interfejsu zdefiniowane w interfejsach DCOM; ignorowane wskaźniki; Wartości całkowite ustawione za pomocą atrybutu [range]; elementy tablic zdefiniowanych za pomocą [wire_marshal], [user_marshal], [transmit_as] i [represent_as] atrybutów; i osadzone złożone struktury danych.
- Dane zawierają unię, strukturę zawierającą unię lub tablicę związków. Tylko konkretna gałąź związku jest marshaled na drutach.
- Dane zawierają strukturę z wielowymiarową tablicą zgodną, która ma co najmniej jeden wymiar niestałych.
- Dane zawierają tablicę złożonych struktur.
- Dane zawierają tablicę prostych typów danych, takich jak wyliczenie16 i __int3264.
- Dane zawierają tablicę wskaźników ref i interfejsu.
- Dane mają atrybut [force_allocate] zastosowany do wskaźnika.
- Dane mają atrybut [allocate(all_nodes)] zastosowany do wskaźnika.
- Dane mają atrybut [byte_count] zastosowany do wskaźnika.
64-bitowa składnia transferu danych i NDR64
Jak wspomniano wcześniej, dane 64-bitowe są marshalled przy użyciu określonej składni transferu 64-bitowego o nazwie NDR64. Ta składnia transferu została opracowana w celu rozwiązania konkretnego problemu, który pojawia się, gdy wskaźniki są marshalowane w ramach 32-bitowej NDR i przesyłane do wycinku serwera na platformie 64-bitowej. W tym przypadku wskaźnik danych 32-bitowych marshaled nie jest zgodny z 64-bitowym wskaźnikiem, a alokacja pamięci będzie niezmiennie występować. Aby utworzyć bardziej spójne zachowanie na platformach 64-bitowych, firma Microsoft opracowała nową składnię transferu o nazwie NDR64.
Przykład ilustrujący ten problem wygląda następująco:
typedef struct PtrStruct
{
long l;
long *pl;
}
Ta struktura, po zakończeniu marshalingu, zostanie ponownie użyta przez wycinkę serwera w systemie 32-bitowym. Jeśli jednak klaster serwera znajduje się w systemie 64-bitowym, dane marshaled NDR mają długość 4 bajtów, ale wymagany rozmiar pamięci będzie wynosić 8. W związku z tym alokacja pamięci jest wymuszana, a ponowne użycie buforu rzadko występuje. NDR64 rozwiązuje ten problem, tworząc marshaled rozmiar wskaźnika 64-bitowego.
W przeciwieństwie do 32-bitowej NDR proste wiązania danych, takie jak enum16 i __int3264 nie tworzą struktury ani tablicy złożonej w ramach NDR64. Podobnie końcowe wartości konsoli nie tworzą złożonej struktury. Wskaźniki interfejsu są traktowane jako unikatowe wskaźniki na najwyższym poziomie; w rezultacie struktury i tablice zawierające wskaźniki interfejsu nie są uważane za złożone i nie będą wymagały określonej alokacji pamięci do ich użycia.
Inicjowanie danych wychodzących
Po usunięciu wszystkich danych przychodzących należy zainicjować wskaźniki tylko dla ruchu wychodzącego oznaczone atrybutem [out].
typedef struct RpcStructure
{
long val;
long val2;
}
void ProcessRpcStructure
(
[in] RpcStructure *plInStructure;
[out] RpcStructure *plOutStructure;
);
W powyższym wywołaniu element wycinkowy serwera musi zainicjować plOutStructure, ponieważ nie był obecny w danych marshalowanych i jest to implikowany wskaźnik [ref], który musi zostać udostępniony implementacji funkcji serwera. Łącznik serwera RPC inicjuje i wyzeruje wszystkie wskaźniki tylko najwyższego poziomu z atrybutem [out]. Wszystkie [out] wskaźniki odniesienia poniżej są również rekursywnie inicjowane. Rekursja zatrzymuje się we wszystkich wskaźnikach z [unique] lub [ptr] atrybutów ustawionych na nich.
Implementacja funkcji serwera nie może bezpośrednio zmienić wartości wskaźnika najwyższego poziomu i w związku z tym nie może ich ponownie przydzielić. Na przykład w implementacji ProcessRpcStructure powyżej następujący kod jest nieprawidłowy:
void ProcessRpcStructure(RpcStructure *plInStructure, rpcStructure *plOutStructure)
{
plOutStructure = MIDL_user_allocate(sizeof(RpcStructure));
Process(plOutStructure);
}
plOutStructure jest wartością stosu, a jego zmiana nie jest propagowana z powrotem do RPC. Implementacja funkcji serwera może próbować uniknąć alokacji, próbując zwolnić plOutStructure, co może spowodować uszkodzenie pamięci. Następnie wycinka serwera przydzieli miejsce na wskaźnik najwyższego poziomu w pamięci (w przypadku wskaźnika do wskaźnika) i prostą strukturę najwyższego poziomu, której rozmiar na stosie jest mniejszy niż oczekiwano.
Klient może w pewnych okolicznościach określić rozmiar alokacji pamięci po stronie serwera. W poniższym przykładzie klient określa rozmiar danych wychodzących w parametrze dla ruchu przychodzącego.
void VariableSizeData
(
[in] long size,
[out, size_is(size)] char *pv
);
Po unmarshalling danych przychodzących, w tym rozmiar, łącznik serwera przydziela bufor dla pv o rozmiarze "sizeof(char)*size". Po przydzieleniu miejsca serwer wyzeruje bufor. Należy pamiętać, że w tym konkretnym przypadku element wycinkowy przydziela pamięć MIDL_user_allocate(), ponieważ rozmiar buforu jest określany w czasie wykonywania.
Należy pamiętać, że w przypadku interfejsów DCOM wycinki generowane przez midL mogą nie być w ogóle zaangażowane, jeśli klient i serwer współużytkuje ten sam apartament COM lub jeśli ICallFrame jest zaimplementowany. W takim przypadku serwer nie może zależeć od zachowania alokacji i musi niezależnie zweryfikować pamięć o rozmiarze klienta.
Implementacje funkcji po stronie serwera i przeprowadzanie marshalingu danych wychodzących
Natychmiast po unmarshalling na danych przychodzących i inicjalizacji pamięci przydzielonej do przechowywania danych wychodzących, klaska serwera RPC wykonuje implementację funkcji po stronie serwera wywoływanej przez klienta. W tej chwili serwer może zmodyfikować dane oznaczone specjalnie [w, out] atrybutu i może wypełnić pamięć przydzieloną dla danych tylko dla ruchu wychodzącego (dane oznaczone [out]).
Ogólne reguły manipulowania danymi parametrów marshalled są proste: serwer może przydzielić nową pamięć lub zmodyfikować pamięć przydzieloną specjalnie przez wycinkę serwera. Reallokowanie lub zwalnianie istniejącej pamięci dla danych może mieć negatywny wpływ na wyniki i wydajność wywołania funkcji i może być bardzo trudne do debugowania.
Logicznie serwer RPC znajduje się w innej przestrzeni adresowej niż klient i zazwyczaj można zakładać, że nie współużytkują pamięci. W związku z tym implementacja funkcji serwera jest bezpieczna, aby używać danych oznaczonych za pomocą atrybutu [in] jako pamięci "scratch" bez wpływu na adresy pamięci klienta. Oznacza to, że serwer nie powinien podejmować próby ponownego przydziału ani wydania [in] danych, pozostawiając kontrolę nad tymi spacjami do samego wycinku serwera RPC.
Ogólnie rzecz biorąc, implementacja funkcji serwera nie musi ponownie przydzielać ani zwalniać danych oznaczonych [in] atrybutu. W przypadku danych o stałym rozmiarze logika implementacji funkcji może bezpośrednio modyfikować dane. Podobnie w przypadku danych o zmiennym rozmiarze implementacja funkcji nie może modyfikować wartości pola dostarczonej do atrybutu [size_is()]. Zmiana wartości pola używanej do rozmiaru danych powoduje zwrócenie mniejszego lub większego buforu do klienta, który może być źle przygotowany do radzenia sobie z nieprawidłową długością.
Jeśli wystąpią okoliczności, w których procedury serwera muszą ponownie przydzielić pamięć używaną przez dane oznaczone [w, out] atrybutu, jest całkowicie możliwe, że implementacja funkcji po stronie serwera nie będzie wiedziała, czy wskaźnik dostarczony przez wycinku ma pamięć przydzieloną MIDL_user_allocate() lub bufor przewodu marshalowanego. Aby obejść ten problem, ms RPC może upewnić się, że nie ma przecieku pamięci lub uszkodzenia, jeśli [force_allocate] atrybut jest ustawiony na danych. Gdy [force_allocate] jest ustawiona, wciągnik serwera zawsze przydziela pamięć dla wskaźnika, chociaż zastrzeżeniem jest to, że wydajność spadnie dla każdego użycia.
Gdy wywołanie zwraca się z implementacji funkcji po stronie serwera, narzędzie odcięcie serwera przeprowadza marshaling danych oznaczonych [out] atrybutu i wysyła je do klienta. Należy pamiętać, że wycink nie wykonuje marshalingu danych, jeśli implementacja funkcji po stronie serwera zgłasza wyjątek.
Zwalnianie przydzielonej pamięci
Wycinkę serwera RPC zwolni pamięć stosu po powrocie wywołania z funkcji po stronie serwera, niezależnie od tego, czy wystąpi wyjątek. Klaska serwera zwalnia całą pamięć przydzieloną przez wycinkę oraz pamięć przydzieloną MIDL_user_allocate(). Implementacja funkcji po stronie serwera musi zawsze zapewnić spójny stan RPC, zgłaszając wyjątek lub zwracając kod błędu. Jeśli funkcja nie powiedzie się podczas populacji skomplikowanych struktur danych, musi upewnić się, że wszystkie wskaźniki wskazują prawidłowe dane lub są ustawione na wartość null.
Podczas tego przekazywania klaska serwera zwalnia całą pamięć, która nie jest częścią marshalowanego buforu zawierającego [in] danych. Jednym wyjątkiem od tego zachowania są dane z [przydziel(dont_free)] atrybut ustawiony na nich — wcięcie serwera nie zwalnia żadnej pamięci skojarzonej z tymi wskaźnikami.
Po opublikowaniu przez wycinkę serwera pamięci przydzielonej przez wycinkę i implementację funkcji element wycinkowy wywołuje określoną funkcję powiadamiania, jeśli atrybut [notify_flag] jest określony dla określonych danych.
Marshalling a Linked List over RPC -- An Example
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
);
W powyższym przykładzie format pamięci dla LINKEDLIST będzie identyczny z formatem przewodu marshaled. W związku z tym wycinkę serwera nie przydziela pamięci dla całego łańcucha wskaźników danych w pIn. Zamiast tego RPC ponownie używa buforu przewodowego dla całej połączonej listy. Podobnie wycinków nie przydziela pamięci dla pInOut, ale zamiast tego ponownie używa buforu przewodowego marshalowanego przez klienta.
Ponieważ sygnatura funkcji zawiera parametr wychodzący, pOut, w wycinku serwera jest przydzielana pamięć zawierająca zwrócone dane. Przydzielona pamięć jest początkowo wyzerowana, a pNext ustawiona na wartość null. Aplikacja może przydzielić pamięć dla nowej połączonej listy i wskazać pOut —> pNext. pIn i lista połączona, która zawiera, może być używana jako obszar tymczasowy, ale aplikacja nie powinna zmieniać żadnych wskaźników pNext.
Aplikacja może swobodnie zmieniać zawartość połączonej listy wskazywanej przez pInOut, ale nie może zmienić żadnego z wskaźników pNext, nie mówiąc już o samym linku najwyższego poziomu. Jeśli aplikacja zdecyduje się skrócić listę połączoną, nie może wiedzieć, czy dana pNext linki wskaźnika tto bufor wewnętrzny RPC lub bufor przydzielony specjalnie z MIDL_user_allocate(). Aby obejść ten problem, należy dodać konkretną deklarację typu dla połączonych wskaźników listy, które wymuszają alokację użytkowników, jak pokazano w poniższym kodzie.
typedef [force_allocate] PLINKEDLIST;
Ten atrybut wymusza przydzielenie każdego węzła listy połączonej oddzielnie, a aplikacja może zwolnić skróconą część listy połączonej przez wywołanie MIDL_user_free(). Następnie aplikacja może bezpiecznie ustawić wskaźnik pNext na końcu nowo skróconej listy połączonej na null.