07 December 2015

Skomplikowane struktury danych i biblioteki DLL

Podczas pisania biblioteki DLL napotkałem problem. Potrzebowałem połączyć kilka wątków jednego procesu z biblioteką DLL. Chodziło o sytuację, w której jeden proces platformy MetaTrader 4, podczas uruchamiania jednego skryptu na wielu wykresach tworzył wątki, które wywoływały bibliotekę DLL. Wątki, które miały niezależnie wykorzystywać pamięć biblioteki w ramach jednego procesu nadpisywały sobie nawzajem zmienną globalną biblioteki. Dla architektury NT normalna sprawa, która tworzy "błędy" w czasie wykonywania kodu. Błędy, które powodują nadpisywanie pamięci wprowadzając mylne informacje do systemu handlowego decydującego o kupnie lub sprzedaży waloru. Na skutki nie było trzeba długo czekać - ceny jednego waloru wpływały na decyzję związaną z innym. Diagram z boku ilustruje sytuację, która nie zdarza się często - bo kiedy ostatnio napisałeś program którego wątki odwołują się do jednej funkcji z tej samej biblioteki DLL, w której z konieczności wykorzystujesz zmienną globalną ? Aby rozwiązać problem można posłużyć się klasą i ją wyeksportować, ale w moim przypadku musiałbym przebudować logikę skryptu MQL oraz całą bibliotekę DLL. Chciałem rozwiązać problem zmieniając tylko kod wspólnej biblioteki. Jakby tego było mało, MT4 w dość specyficzny sposób wywołuje DLL - nie udało mi się zrozumieć jak wywoływana jest funkcja DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) z parametrem DLL_THREAD_ATTACH. Zabezpieczenia chroniące kod MT4 przed śledzeniem są skuteczne i pozostała mi analiza behawioralna kodu, która coś wniosła do rozwiązania ale pewności jak to działa nie mam. Rozwiązaniem było stworzenie wektora, który był jedyną globalną zmienną w bibliotece, a jego zadaniem było przechowywanie obiektów tworzonych w momencie kiedy wątek z procesu MT4 wywoływał funkcje biblioteki. Obiekty w wektorze były rozpoznawane po losowych zmiennych identyfikujących kolejne wywołania skryptów w MT4. Musiałem przetransportować dużo danych z biblioteki do terminala i jednocześnie mieć gwarancję, że nie wystąpią błędy związane z nadpisywaniem informacji - powstało coś takiego:

wektor w pamięci globalnej zawierający obiekty klasy->klasa zawierająca wektory "protected" i metody "public"->wektory "protected" zawierające struktury oraz metody dostępu do nich

W sumie powstało coś dość skomplikowanego co działa. W całej układance brakuje jednak elementu synchronizacji, bo co bym nie zrobił to wykorzystywanie zmiennych globalnych przez wiele wątków wymaga synchronizacji dostępu. Udało mi się uniknąć większych zmian w kodzie MQL i spowodować, że zmiany w pamięci globalnej biblioteki występują na tylko rzadko aby nie pojawił się problem z synchronizacją podczas dodawania i usuwania elementów z wektora znajdującego się we wspólnej pamięci DLL.