Spis treści

Tagi i taglisty

Taglista to tablica par „klucz-wartość”. Kluczem jest zawsze 32-bitowa liczba całkowita i nazywana jest tagiem. Wartość ma również rozmiar 32 bitów. Może to być liczba całkowita, lub wskaźnik na dowolną strukturę, czy obiekt. Taglisty są powszechnie używane w API MorphOS-a do przekazywania zmiennej liczby argumentów, zazwyczaj listy atrybutów wraz z ich wartościami. Kilka specjalnych wartości kluczy jest używanych do zakończenia taglisty, przeskakiwania elementów i łączenia taglist. Biblioteka utility.library zawiera zestaw funkcji do przeglądania, filtrowania, przeszukiwania, kopiowania i innych operacji na taglistach.

Każdy element taglisty (para klucz-wartość) jest strukturą TagItem, zdefiniowaną w pliku nagłówkowym <utility/tagitem.h>:

struct TagItem
{
  ULONG ti_Tag;
  ULONG ti_Data;
};

Taglista jest zwykłą tablicą języka C, żeby była samoopisująca się, musi posiadać jakiś sposób terminacji (oznaczenia końca). Idea jest zbliżona do terminacji łańcuchów tekstowych bajtem 0. Struktura TagItem oznaczająca koniec taglisty ma pole ti_Tag ustawione na stałą TAG_END i tak się składa, że ta stała jest równa 0. Pole ti_Data kończącego elementu jest ignorowane i zwykle również bywa ustawiane na 0. Poniższa ilustracja przedstawia prostą taglistę:


Przykładowa taglista.

Taglistę tę można zbudować następującym kodem:

double x = 3.438872763e+17;
Object *obj = NewObject( /* ... */ );

struct TagItem taglista[] = {
  { Tag1, 2837 },
  { Tag2, (ULONG)&x },
  { Tag3, (ULONG)"Leż, dość próźnych kłamstw, rąb fuzję, giń!" },
  { Tag4, (ULONG)obj },
  { TAG_END, 0 }
};

Taglisty jako argumenty funkcji

Tworzenie taglist jako zmiennych globalnych, czy lokalnych nie jest zbyt wygodne. Dlatego prawie każda funkcja z API MorphOS-a przymująca taglistę jako jeden ze swoich argumentów, ma dwie formy. Pierwsza z form akceptuje jako argument wskaźnik na taglistę. Druga forma jest funkcją o zmiennej liczbie argumentów i tworzy taglistę z argumentów dynamicznie, umieszczając ją chwilowo na stosie procesu. Takie pary funkcji nazywane są zgodnie z jedną z dwóch poniższych konwencji:

Kontynuując przykład z poprzedniego podrozdziału, przykładową taglistę można przekazać do JakiejsFunkcji() na dwa sposoby:

JakasFunkcjaTagList(taglista);

JakasFunkcjaTags(
  Tag1, 2837,
  Tag2, (ULONG)&x,
  Tag3, (ULONG)"Leż, dość próżnych kłamstw, rąb fuzję, giń!",
  Tag4, (ULONG)obj,
TAG_END);

Rzecz jasna, w drugim przypadku zmienna taglista nie musi być w ogóle zdefiniowana. Warto zauważyć, że dla każdej funkcji używającej taglisty, taglista jest ostatnim parametrem. Przed nią mogą być inne, zwykłe argumenty. Powszechną praktyką przy dynamicznym tworzeniu taglisty z argumentów funkcji jest pomijanie wartości (ti_Data), dla ostatniego, kończącego taglistę elementu (tak czy inaczej wartość ta jest ignorowana).

Tagi kontrolne

Tagi kontrolne pozwalają na uniknięcie kopiowania obszarów pamięci przy manipulowaniu taglistami. Ponieważ taglisty to zwykłe tablice, operacje takie jak wstawianie elementu, czy łączenie taglist wymagają kopiowania ich dużych fragmentów. Korzystając z tagów kontrolnych można takie operacje wykonywać zmieniając jedynie pojedyncze tagi. Niestety, coś za coś – manipulując taglistami z tagami kontrolnymi powinniśmy używać odpowiednich funkcji z utility.library, albo też obsłużyć tagi kontrolne samodzielnie. Trzeba pamiętać o tym, że nawet jeżeli swoje taglisty tworzymy ręcznie i nie używamy w nich tagów kontrolnych, mogą być one później dodane, gdy użyjemy tych taglist jako argumentów funkcji systemowych. Dlatego zawsze bezpieczniej jest operować na taglistach funkcjami z utility.library, w szczególności przeglądać taglisty funkcją NextTagItem().

TAG_END (TAG_DONE)

O tym tagu już wspomniałem, służy do zakończenia taglisty. Nie ma różnicy między TAG_END i TAG_DONE, oba są zdefiniowane jako 0. W swoich publikacjach i kodzie używam TAG_END, po prostu jest krótszy.

TAG_IGNORE

Tag kontrolny używany do logicznego usunięcia taga z taglisty, bez konieczności przemieszczania danych leżących za usuwanym tagiem. Po prostu usuwany tag (a dokładniej klucz) jest zastępowany kluczem TAG_IGNORE. Każda funkcja przetwarzająca taglisty pominie tego taga, przechodząc natychmiast do następnego.


TAG_IGNORE

TAG_MORE

Podstawowym zastosowaniem tego taga jest łączenie taglist. W przypadku zwykłych tablic łączenie wymaga policzenia rozmiarów łączonych części, rezerwacji obszaru pamięci i kolejnego skopiowania łączonych tablic do tego obszaru. Połączenie taglist jest znacznie prostsze. Wystarczy terminator TAG_END pierwszej taglisty zastąpić kluczem TAG_MORE, jako wartość podając wskaźnik na drugą taglistę. Funkcje przeglądające taką taglistę po napotkaniu TAG_MORE przejdą do kolejnego taga pod podanym adresem. W ten sposób można logicznie połączyć dowolną ilość taglist w jedną.


TAG_MORE

TAG_SKIP

Ten dość rzadko używany tag służy do logicznego pominięcia większego fragmentu taglisty. Gdy iterator (funkcja przeglądająca) napotka ten tag, pomija go, oraz kolejne tagi w ilości wskazanej przez wartość taga.


TAG_SKIP

Używając TAG_SKIP należy uważać, żeby nie „skoczyć” tym tagiem poza terminator taglisty. Funkcje przetwarzające taglisty nie są zabezpieczone przed tym błędem i spróbują interpretować przypadkowe dane, z nieprzewidywalnym skutkiem.

TAG_USER

W zasadzie TAG_USER nie jest tagiem kontrolnym, wspominam jednak tu o nim, bo można go znaleźć w wielu kodach źródłowych. Tag ten jest zdefiniowany jako liczba szesnastkowa $80000000, więc dzieli 32-bitowy zakres tagów na dwie połowy. Idea tego jest taka, że tagi systemu operacyjnego są umieszczone w połowie dolnej, a tagi aplikacji w górnej. W rzeczywistości to wyłącznie umowna konwencja, która nie jest ani w jakikolwiek sposób wymuszona, ani też specjalnie przestrzegana (na przykład wszystkie tagi MUI, a nawet tagi Intuition znajdują się w obszarze tagów użytkownika). Bierze się to stąd, że zwykłe tagi (nie kontrolne) są zawsze interpretowane w określonym kontekście, więc niebezpieczeństwo konfliktu nie istnieje. Oczywiście tagi kontrolne nie mogą być używane w roli zwykłych, zatem wartości 0 - 3 są zabronione (a potraktowanie jako zarezerwowanych wartości do 255 włącznie na pewno nie jest złym pomysłem).

Przeglądanie taglist funkcją NextTagItem()

Funkcja NextTagItem() z biblioteki utlilty.library jest podstawową funkcją do przeglądania taglist (czyli iteratorem). Ponieważ automatycznie obsługuje wszystkie tagi kontrolne, właśnie jej powinniśmy używać do przeglądania taglist. Każde wywołanie tej funkcji zwraca adres kolejnego taga (struktury TagItem). Rzecz jasna tagi kontrolne nie są zwracane, tylko „wykonywane”. Argumentem funkcji jest adres zmiennej będącej wskaźnikiem na ostatnio zwrócony tag. Przed pierwszym wywołaniem trzeba go ręcznie zainicjalizować adresem taglisty (jej pierwszego elementu), a później nie powinien być modyfikowany. Podstawowa pętla iterująca taglistę może być zorganizowana w następujący sposób:

struct TagItem *tag, *tagptr;

tagptr = my_taglist;  // inicjalizacja na początek taglisty

while (tag = NextTagItem(&tagptr))
{
  /* zrób coś z 'tag'-iem */
}

NextTagItem() zwraca NULL po napotkaniu terminatora TAG_END.

Przetwarzanie taglist

Taglistę można przetworzyć w dowolny sposób posługując się jedynie iteratorem NextTagItem() i manipulując kolejnymi tagami ręcznie. Z drugiej strony wiele typowych operacji da się zrobić gotowymi funkcjami z utility.library. Dzięki temu oszczędzamy czas, skracamy kod programu i unikamy błędów, jakie moglibyśmy zrobić we własnym kodzie. Typowe programy rzadko wymagają jakiegoś szczególnie zaawansowanego przetwarzania taglist, ale opisane niżej funkcje mogą się przydać, gdy stosujemy taglisty do przechowywania danych.

Tu należy zrobić uwagę, że ten artykuł nie jest w stanie (i nie jest to jego zadaniem) zastąpić dokumentacji utility.library zawartej w SDK. Dokumentacja ta, odnosząc ją od dokumentacji z AmigaOS 3.x, została napisana całkowicie od nowa, przy tym w zasadniczy sposób rozszerzona i uzupełniona. Ten artykuł zatem jest jedynie przeglądem możliwości utility.library w zakresie operowania taglistami. Szczegóły znajdują się w dokumentacji.

Wyszukiwanie tagów i danych

Jednym z prostszych zadań jest wyszukanie w tagliście określonego taga. Służy do tego funkcja FindTagItem(). Jej wynikiem jest adres pierwszego napotkanego wystąpienia taga w tagliście. Jeżeli spodziewamy się, że tag może się powtarzać, funkcję można wywoływać w pętli, używając zwróconego adresu jako argumentu następnego wywołania.

struct TagItem *tag = taglista;

while (tag = FindTagItem(JAKIS_TAG, tag))
{
  /* rób coś z 'tag'-iem */

  tag++;  /* kontynuuj od taga następnego po właśnie znalezionym */
}

Jak wszystkie opisywane tu funkcje, FindTagItem() interpretuje automatycznie tagi kontrolne, a więc nie są one nigdy zwracane użytkownikowi. Dlatego zwiększenie wskaźnika o jeden tag w powyższej pętli jest zawsze bezpieczne.

W wielu przypadkach (np. w konstruktorach klas MUI) ważna jest wartość danego taga, a także niezbędna jest wartość domyślna, jeżeli tag w tagliście nie wystąpi. Można to zagadnienie rozwiązać jednym wywołaniem funkcji GetTagData(). Jej argumentami są: tag, wartość domyślna i taglista. Jeżeli tag zostanie odnaleziony w tagliście, wynikiem funkcji jest przypisana mu wartość. Jeżeli nie – wynikiem jest wartość domyślna. Oto przykład, następujący kod:

if (tag = FindTagItem(JAKIS_TAG, taglista)) wartosc = tag->ti_Data; 
else wartosc = DOMYSLNA;

możemy zredukować do:

wartosc = GetTagData(JAKIS_TAG, DOMYSLNA, taglista);

Ostatnia funkcja związana z wyszukiwaniem tagów to TagInArray(). Nie operuje ona jednak na tagliście. Sprawdza po prostu czy podany tag (sam klucz) znajduje się w podanej tablicy kluczy. Tablica nie jest taglistą, jest zwykłą tablicą ULONG-ów zakończoną zerem.

Tworzenie i kopiowanie taglist

Najprostszym sposobem stworzenia taglisty jest jej zadeklarowanie jako tablicy, czy to globalnej, czy lokalnej. Pokazałem to już wcześniej w tym rozdziale. Jeżeli taglista ma być stworzona dynamicznie można jej przydzielić pamięć za pomocą dowolnej funkcji alokacji pamięci z exec.library. Oto przykład z użyciem AllocVec().

struct TagItem *taglist;    /* taglista z 7 elementami (licząc z terminatorem) */

taglist = (struct TagItem*)AllocVec(7 * sizeof(struct TagItem), MEMF_ANY);

W bibliotece utility.library znajdziemy też dedykowaną funkcję do alokacji taglist, nazwaną AllocateTagItems(). W rzeczywistości jest ona zwykłą nakładką na funkcję alokacji ogólnego przeznaczenia, z tym, że przy okazju zeruje przydzielony blok, a więc może on być traktowany jako pusta taglista (jako że TAG_END jest zerem). Taglisty zaalokowane funkcją AllocateTagItems() powinny być zwalniane funkcją FreeTagItems().

struct TagItem *fresh_list;

if (fresh_list = AllocateTagItems(2))
{
  fresh_list[0].ti_Tag = SOME_TAG;   /* Inicjalizacja terminatora w elemencie [1] jest    */
  fresh_list[0].ti_Data = 123456;    /* zbędna, bo cała taglista jest wstępnie wyzerowana */

  /* dalsze operacje na 'fresh_list' */

  FreeTagItems(fresh_list);
}

Kopia istniejącej taglisty może być utworzona funkcją CloneTagItems(). Ta funkcja nie jest już prostą nakładką dlatego, że taglista zawierająca tagi kontrolne (szczególnie TAG_MORE) nie może być skopiowana jako ciągły blok pamięci. Aby zatem wykonać poprawną kopię taglisty, funkcja najpierw przegląda oryginał, zliczając zwykłe tagi. Następnie rezerwuje blok pamięci na kopię i przepisuje zwykłe tagi jeden po drugim. Kolejność tagów jest oczywiście zawsze zachowana, więc iterowanie kopii przez NextTagItem() da dokładnie ten sam rezultat co iterowanie oryginału. Zasadniczą różnicą natomiast jest to, że kopia jest zawsze oczyszczona z tagów kontrolnych. Wszystkie tagi usunięte z oryginału za pomocą TAG_IGNORE lub TAG_SKIP są pomijane przy tworzeniu kopii. Bloki połączone ze sobą przez TAG_MORE są łączone w jeden ciągły blok pamięci. Jedynym tagiem kontrolnym, jaki znajdziemy w kopii jest oczywiście TAG_END. Ponieważ CloneTagItems() jest tak naprawdę funkcją przydziału pamięci, zawsze należy sprawdzić, czy nie zwróciła przypadkiem NULL-a. Kopie stworzone tą funkcją powinny być zwalniane przez wywołanie FreeTagItems(), podobnie jak to było w przypadku AllocateTagItems().

struct TagItem *original, *copy;

if (copy = CloneTagItems(original))
{
  /* dalsze operacje na 'copy' */

  FreeTagItems(copy);
}

Do dyspozycji programisty stoi również funkcja kopiująca taglistę do wskazanego – przygotowanego wcześniej – bufora. Dość myląco została nazwana przez autorów systemu RefreshTagItemClones(). W rzeczywistości funkcja działa dokładnie tak samo jak CloneTagItems(), z tym, że nie alokuje docelowego bufora, a używa bufora podanego jako jeden z argumentów. Tworzona kopia taglisty jest oczyszczana z tagów kontrolnych i defragmentowana, podobnie jak robi to CloneTagItems(). Programista musi być świadomy tego, że RefreshTagItemClones() w żaden sposób nie sprawdza rozmiarów bufora docelowego. Jeżeli kopia taglisty nie zmieści się w buforze, zostanie uszkodzony obszar pamięci znajdujący się zaraz za buforem.

struct TagItem copy[7];  /* Jestem święcie przekonany, że oryginał nie ma więcej niż 6 zwykłych  */
                         /* tagów + terminator. Jeżeli okaże się, że więcej, nastąpi apokalipsa. */

RefreshTagItemClones(copy, original);

Wykonanie tej funkcji zawsze się udaje, więc nie zwraca żadnego wyniku.

Filtrowanie i mapowanie

W utility.library znajdziemy cztery funkcje poświęcone filtrowaniu i mapowaniu tagów i ich wartości. Dwie z nich, mianowicie FilterTagItems() i MapTagItems() operują na tagach biorąc jedynie pod uwagę ich klucze. Dwie pozostałe, ApplyTagChanges() i FilterTagChanges() operują na tagach biorąc pod uwagę również wartości.

Filtrowanie tagów według kluczy

Funkcja FilterTagItems() umożliwia usunięcie z taglisty tagów z kluczami podanymi w tablicy kluczy. Usunięcie tagów jest przeprowadzone przez zastąpienie ich kluczy kluczem kontrolnym TAG_IGNORE. Funkcja ma dwa tryby pracy. Tablica kluczy może być użyta albo jako tablica kluczy dozwolonych (pozostałe zostaną odfiltrowane) albo jako tablica kluczy zabronionych (pozostałe będą pozostawione). Zilustrowano to na poniższych diagramach. Klucze tagów są oznaczone kolorami. Po lewej znajduje się oryginalna taglista (ta sama w obu przykładach). Na środku mamy tablicę kluczy. Nie zawiera ona struktur TagItem, a tylko same klucze, zatem jest po prostu tablicą ULONG-ów, zakończoną zerem. Oryginalna taglista po wykonaniu operacji pokazana jest po prawej.


Filtrowanie taglisty w trybie TAGFILTER_AND. Usuwane są tagi nieobecne w tablicy filtra.

Filtrowanie taglisty w trybie TAGFILTER_NOT. Usuwane są tagi obecne w tablicy filtra.

Mapowanie tagów

W procesie mapowania tagów określony zestaw kluczy jest zastąpiony innym zestawem zgodnie z tzw. mapą. Tagi nie znalezione w mapie mogą być albo pozostawione bez zmian, albo usunięte. Mapa ma postać taglisty. Klucze tagów w tej tagliście to klucze do zastąpienia, a ich wartości to klucze zastępujące. W całym procesie pola wartości w tagach taglisty źródłowej nie są modyfikowane. Proces mapowania zilustrowano poniżej. Zestaw kluczy czerwonych jest mapowany na zestaw kluczy zielonych. Pozostałe klucze są albo pozostawiane bez zmian, albo usuwane przez zmianę na TAG_IGNORE, w zależności od argumentów funkcji MapTags(). Po lewej widzimy taglistę przed mapowaniem, na środku mapę, po prawej zmapowaną taglistę.


Mapowanie kluczy czerwonych na zielone z pozostawieniem reszty (MAP_KEEP_NOT_FOUND).

Mapowanie kluczy czerwonych na zielone z usunięciem reszty (MAP_REMOVE_NOT_FOUND).

Interesującą możliwością jest mapowanie tagów zwykłych na kontrolne. Tagów kontrolnych nie da się zmapować, ponieważ MapTags() czyta mapowaną taglistę przy pomocy NextTagItem(), zatem tagi kontrolne są interpretowane i „nie dochodzą” do etapu mapowania. Najbardziej użyteczną opcją jest mapowanie na TAG_IGNORE, przy którym MapTags() działa tak jak FilterTags(), ale można jednocześnie mapować wybrany zestaw tagów i usuwać inny wybrany zestaw (mapując na TAG_IGNORE). Mapowanie na TAG_SKIP czy TAG_MORE jest przydatne nieco rzadziej, ale w szczególnych przypadkach może się przydać. Przykładowo można w ten sposób zastępować pojedynczy tag grupą tagów, albo wyłączać grupy tagów. Funkcja MapTags() nie pozwala mapować na TAG_END, próba takiego mapowania daje w efekcie mapowanie na TAG_IGNORE.

Filtrowanie danych

Funkcja ApplyTagChanges() pracuje na dwóch taglistach. Pierwsza z nich jest taglistą wzorcową, druga taglistą zmian. Każdy tag w tagliście wzorcowej jest odszukiwany w tagliście zmian. Jeżeli zostanie znaleziony, wartość taga z listy zmian jest wstawiana do taga w liście wzorcowej. Funkcja FilterTagChanges() z kolei to bardziej zaawansowana wersja poprzedniej. Zastosowanie zmian do listy wzorcowej jest tutaj opcjonalne (kontrolowane argumentem funkcji). Dodatkowo modyfikowana jest lista zmian. Tagi których wartości w liście zmian są takie same jak w liście wzorcowej (czyli tagi nie specyfikujące zmiany) są usuwane z listy zmian przez zastąpienie klucza kluczem kontrolnym TAG_IGNORE.

Poniższy rysunek objaśnia działanie funkcji ApplyTagChanges(). Tagi niebieskie nie podlegają zmianom. Tagi czerwone mogą być poddane zmianom (a dokładniej ich wartości). Wartości, które są różne w tagliście wzorcowej i tagliście zmian, są oznaczone kolorem zielonym.


Aplikowanie zmian wartości.

Podobny rysunek wyjaśnia działanie FilterTagChanges(). Tu sprawa jest nieco bardziej złożona, bo mamy dodatkowy parametr (TRUE/FALSE) kontrolujący naniesienie zmian wartości w tagliście wzorcowej. Jeżeli zmiany nie są nanoszone, dowiadujemy się jedynie o różnicach, bez ich wprowadzania w życie.


Filtrowanie zmian wartości z opcją ich wprowadzenia.

Konwersja danych

Funkcje konwersji danych automatyzują wymianę danych między taglistami a strukturami języka C i polami bitowymi. Pola bitowe są prostsze, a więc od nich zaczniemy.

Pola bitowe

Pole bitowe jest bardzo oszczędnym sposobem przechowywania danych logicznych (TRUE lub FALSE). Jedna struktura TagItem zajmuje 64 bity, ta sama informacja w polu bitowym – jeden bit. Także sprawdzenie ustawienia bitu jest znacznie szybsze, niż odszukanie taga. Funkcja PackBoolTags() konwertuje dane z taglisty w pole bitowe. Jej działanie jest nieco podobne do MapTags(), a więc mamy źródłową taglistę i mapę. Tym razem jednak wartościami mapy są maski bitowe odpowiadające kluczom. Konwersja rozpoczyna się od wpisania do pola bitowego wartości początkowej. Następnie każdy klucz z mapy jest odszukiwany w liście źródłowej. Jeżeli zostanie znaleziony, jego wartość logiczna (TRUE albo FALSE) decyduje o sposobie zaaplikowania maski bitowej z mapy do docelowego pola. Wartość TRUE ustawia wszystkie jedynki z maski w polu bitowym, wartość FALSE kasuje w polu bitowym jedynki z maski.


Pakowanie taglisty do pola bitowego.

W typowym przypadku każda maska ma ustawiony tylko jeden bit, a więc tagi są mapowane do pojedynczych bitów pola bitowego. W ogólnym przypadku można używać masek wielobitowych, nawet nakładających się na siebie. Trzeba jednak pamiętać, że jeżeli maski się nakładają, to ostateczna wartość pola bitowego zależy od kolejności występowania tagów w tagliście źródłowej i w mapie.

Z pewnym zaskoczeniem można odkryć, że biblioteka utility.library nie oferuje funkcji odwrotnej do PackBoolTags(). Jest po temu kilka powodów:

Struktury

Para funkcji: PackStructureTags() i UnpackStructureTags() zapewnia dwukierunkową konwersję danych między taglistą a strukturą (w rozumieniu języka C/C++). Funkcje te były bardzo słabo opisane w dokumentacji AmigaOS 3.x, stąd były bardzo rzadko stosowane. SDK MorphOS-a zawiera pełną, napisaną od podstaw dokumentację tych funkcji, bazującą na zebranej wiedzy i obszernych testach, zarówno implementacji tych funkcji w AmigaOS 3.x jak i w MorphOS-ie (obie są całkowicie ze sobą zgodne).

Funkcja PackStructureTags() przenosi dane z taglisty do struktury. Proces sterowany jest tablicą z instrukcjami pakowania. Tablica zawiera zakodowane instrukcje takie jak rozmiary pól struktury i ich przesunięcia względem jej początku, oraz klucze tagów powiązanych z danymi polami struktury. Obsługiwane rozmiary pól to pojedyncze bity oraz pola 8, 16 i 32-bitowe, ze znakiem lub bez. Tablicę pakowania najwygodniej zdefiniować korzystając z ułatwiających sprawę makr zdefiniowanych w pliku <utility/pack.h>. Makra automatycznie generują instrukcje pakujące, wyliczają przesunięcia pól itd., automatycznie uwzględniając szczegóły takie jak puste miejsca między polami struktur wstawiane automatycznie przez kompilator. Jeżeli z jakichś powodów użycie makr jest niemożliwe, szczegółowy opis składni instrukcji pakujących znajduje się w SDK, w dokumentacji do funkcji PackStructureTags().

Klucze tagów pakowanych danych muszą zaczynać się od jakiejś zdefiniowanej wartości, zwanej bazą. Definicja bazy jest pierwszą instrukcją tablicy pakowania i jest generowana makrem PACK_STARTTABLE. Zakłada się, że wszystkie pakowane tagi mają klucze w zakresie 1024 kolejnych liczb od bazy. W tablicy pakowania są zapisane tylko różnice między kluczami a bazą, zajmując 10 bitów zamiast 32. Dzięki temu instrukcja pakowania jednego taga mieści się w pojedynczym ULONG-u. Jeżeli z takich czy innych powodów niemożliwe jest zmieszczenie wszystkich kluczy w przedziale 1024 kolejnych liczb, można w środku tablicy pakowania zmienić bazę kluczy makrem PACK_NEWOFFSET. Tablica pakowania jest zawsze zakończona makrem PACK_ENDTABLE.

Każdy element struktury (z wyjątkiem pojedynczego bitu) i odpowiadający mu tag jest zdefiniowany makrem PACK_ENTRY, mającym 5 agrumentów. Pierszy to baza kluczy, drugi to klucz taga zawierającego dane dla definiowanego pola. 10-bitowe przesunięcie jest obliczane automatycznie, oszczędzając programiście zabawy z bitami. Następne dwa argumenty definiują element struktury, poprzez podanie nazwy struktury i nazwy elementu z definicji struktury. Przesunięcie pola od początku struktury jest wyliczane automatycznie. Wyliczone przesunięcie uwzględnia bajty wyrównawcze wstawiane niejawnie przez kompilator, działa również poprawnie ze strukturami zadeklarowanymi z __attribute__ ((packed)), często używanymi przy ładowaniu danych z pliku (nagłówków czy rekordów). Ostatni, piąty argument zawiera flagi definiujące rozmiar pola.

struct Foo
{
  ULONG Bar;
  WORD Blah;
  BYTE Xyz;
};

#define FOO_TAGBASE  TAG_USER
#define FOO_BAR      (FOO_TAGBASE + 0)
#define FOO_BLAH     (FOO_TAGBASE + 1)
#define FOO_XYZ      (FOO_TAGBASE + 2)

ULONG PackTable[] = {
  PACK_STARTTABLE(FOO_TAGBASE),
  PACK_ENTRY(FOO_TAGBASE, FOO_BAR,  Foo, Bar,  PKCTRL_ULONG | PKCTRL_PACKUNPACK),
  PACK_ENTRY(FOO_TAGBASE, FOO_BLAH, Foo, Blah, PKCTRL_WORD | PKCTRL_PACKUNPACK),
  PACK_ENTRY(FOO_TAGBASE, FOO_XYZ,  Foo, Xyz,  PKCTRL_BYTE | PKCTRL_PACKUNPACK),
  PACK_ENDTABLE
}

Powyższy przykład definiuje prostą strukturę, zestaw kluczy tagów i tablicę pakowania do wymiany danych między strukturą a taglistą. W tym przypadku ta sama tablica może służyć do wymiany danych w obu kierunkach, bo została zadeklarowana w pełni symetrycznie (wszystkie elementy mają flagę PKCTRL_PACKUNPACK). Dla każdego taga z osobna można zezwolić jedynie na pakowanie do struktury (PKCTRL_PACK), albo tylko na wypakowywanie do taglisty (PKCTRL_UNPACK). Pożądane wartości domyślne można zapisać do struktury przed pakowaniem. Jeżeli klucz przypisany danemu polu struktury nie zostanie znaleziony w tagliście, pole nie zostanie zmienione. Gdy używamy tej samej tablicy do pakowania i rozpakowywania taglisty, trzeba zwrócić uwagę na znak pól. Przy pakowaniu do struktury nie ma to w zasadzie znaczenia, bo gdy wartość taga (zawsze 32-bitowa) zapisywana jest do pola 8- lub 16-bitowego, starsze bity są po prostu obcinane. W czasie rozpakowywania do taglisty natomiast pola oflagowane jako „ze znakiem” zostaną poprawnie rozszerzone znakowo do liczby 32-bitowej, podczas gdy pola bez znaku bedą po prostu rozszerzone zerami.

Konwersji mogą podlegać również pojedyncze bity pól struktury. W przeciwieństwie do PackBoolTags(), PackStructureTags() nie pozwala na maski wielobitowe, ponieważ w instrukcji pakującej zapamiętywany jest numer bitu, a nie maska. Z drugiej strony można używać bitów w polach 8- i 16-bitowych. Mamy tu też większe możliwości operowania bitami. Bit może być ustawiony na wartość logiczną wybranego taga (z opcjonalną negacją), może też być ustawiony lub skasowany tylko na bazie obecności taga w tagliście (wartość taga jest wtedy ignorowana). Programista steruje tym zachowaniem flagami PACK_BIT / PACK_FLIPBIT i PSTF_EXISTS:

Konwersję bitów definiuje się makrami PACK_BYTEBIT, PACK_WORDBIT i PACK_LONGBIT, w zależności od rozmiaru pola struktury. Oto prosty przykład:

struct FooBits
{
  ULONG LongField;    // pola bitowe powinny być
  UBYTE ShortField;   // zawsze bez znaku
};

#define FOOBITS_TAGBASE   TAG_USER
#define FOO_LongA         (FOOBITS_TAGBASE + 0)
#define FOO_LongB         (FOOBITS_TAGBASE + 1)
#define FOO_ShortC        (FOOBITS_TAGBASE + 2)
#define FOO_ShortD        (FOOBITS_TAGBASE + 3)

ULONG PackTable[] = {
{
  PACK_STARTTABLE(FOOBITS_TAGBASE),
  PACK_LONGBIT(FOOBITS_BASE, FOO_LongA, FooBits, LongField,
    PKCTRL_PACKUNPACK | PKCTRL_BIT, 23),
  PACK_LONGBIT(FOOBITS_BASE, FOO_LongB, FooBits, LongField,
    PKCTRL_PACKUNPACK | PKCTRL_FLIPBIT, 19),
  PACK_BYTEBIT(FOOBITS_BASE, FOO_ShortC, FooBits, ShortField,
    PKCTRL_PACKUNPACK | PKCTRL_BIT | PSTF_EXISTS, 6),
  PACK_BYTEBIT(FOOBITS_BASE, FOO_ShortD, FooBits, ShortField,
    PKCTRL_PACKUNPACK | PKCTRL_FLIPBIT | PSTF_EXISTS, 2),
 PACK_ENDTABLE
};

Powyższy kod przypisuje tagi do bitów w następujący sposób (bit 0 to bit najmniej znaczący):

Nie wszystkie te możliwości są symetrycznie dostępne przy konwersji struktury na taglistę. Funkcja UnpackStructureTags() ignoruje flagi PSTF_EXISTS i PKCTRL_FLIPBIT i po prostu ustawia wartość taga na TRUE albo FALSE zgodnie z wartością bitu.