Pobieranie kodu HTML stron w C++ za pomocą WinSock2

Dzisiejsza notka będzie o tym jak pobrać kod HTML strony WWW w aplikacji C++ korzystając z WinSock2.

Pierwszą rzeczą, którą trzeba wiedzieć jest to w jaki sposób robi to przeglądarka internetowa. Zacznę od tego jak wygląda typowy adres strony WWW na przykładzie adresu do działu artykułów na stronie http://www.gamedev.pl/:

http://www.gamedev.pl/articles.php

Powyższy adres składa się z kilku części:

  • “http://” – protokół reprezentujący sposób transmisji danych, dzięki niemu przeglądarka wie w jaki sposób komunikować się z serwerem oraz na jaki port wysyłać żądania
  • “www.gamedev.pl” – domena na którą będzie wysłane zapytanie – podany adres jest tłumaczony na adres IP przez serwer DNS
  • “/articles.php” – adres żądanego plik lub żądanie dla serwera WWW, które aplikacja wykorzystuje do stworzenia nagłówka

Przeglądarka mając adres strony tworzy odpowiedni nagłówek, który zostaje wysłany do serwera WWW. Jak już wspomniałem adresem tego serwera jest domena zawarta w adresie WWW natomiast port jest określany na podstawie protokołu, czyli w przypadku “http://” jest to port 80. Nagłówek HTTP powinien zawierać następujące elementy:

  • rodzaj zapytania – w przypadku pobrania strony jest to “GET”
  • adres żądanego pliku lub żądanie – to co chcemy od serwera otrzymać, najczęściej jest to adres pliku na serwerze, czyli “/articles.php” w tym przykładzie
  • wersja protokołu – typowo HTTP/1.1
  • domena hosta

Dodatkowo, jeśli jest to konieczne, można wysłać informacje o tym, jakiej przeglądarki używamy (UserAgent), informacje o akceptowanych plikach, kodowaniu, ciasteczkach oraz czasie trwania połączenia, więcej można dowiedzieć się z tej strony wiki oraz samych nagłówków, które wysyła przeglądarka. Jako ciekawostkę mogę dodać, że istnieje fajna wtyczka dla Firefoxa, która pokazuje nagłówki wysyłane przez przeglądarkę – Live HTTP headers.

Oto nagłówek uzyskany z pomocą tej wtyczki podczas łączenia się do przykładowej strony:

1
2
3
4
5
6
7
8
9
GET /articles.php HTTP/1.1
Host: www.gamedev.pl
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pl,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

Jak widać jest on trochę długi (usunąłem informacje o ciasteczkach, nie są tutaj potrzebne). Aby pobrać kod strony wystarczą tak naprawdę dwie pierwsze linijki. Jednak ważną rzeczą jest, aby po każdej linijce występowała para znaków \r\n a koniec nagłówka reprezentowany był przez dwie pary tych znaków. W przypadku, gdy nagłówek nie będzie zawierał tak skonstruowanego nagłówka, serwer po prostu udrzuci zapytanie.

Teraz pora na kod C++ z WinSock2, oto on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Usuwa zbędne definicje z nagłówka
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
 
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>
 
#define BUFFER_SIZE 2048
#pragma comment(lib, "ws2_32.lib")	// Niezbędna biblioteka
using namespace std;
 
int main()
{
	//************************************************
	// Inicjalizacja WinSock2
	//************************************************
	WSADATA wsaData;
	int error;
	ZeroMemory(&wsaData, sizeof(wsaData));
 
	if(FAILED(WSAStartup(MAKEWORD(2,2), &wsaData)));	// MAKEWORD(2,2) - Wersja WinSock
	{
		return 1;
	}
 
	char recvBuffer[BUFFER_SIZE];
	addrinfo hint;					// Struktura przechowująca dane o połączeniu
	addrinfo* wsResult;				// Wskaźnik na rezultat
	SOCKET pSocket;					// Właściwy pSocket
 
	ZeroMemory(recvBuffer, sizeof(recvBuffer));
	ZeroMemory(&hint, sizeof(hint));
	hint.ai_family = AF_UNSPEC;		// Rodzaj transmisji - nieokreślony
	hint.ai_socktype = SOCK_STREAM;	// Typ gniazda - strumień
	wsResult = NULL;
	pSocket = INVALID_SOCKET;
 
	//************************************************
	// Tworzenie zapytania	
	//************************************************
 
	// Wyciąganie informacji z adresu
	string httpAddress = "http://www.gamedev.pl/articles.php";
	string temp = httpAddress.substr(httpAddress.find("http://") + sizeof("http://") - 1);	// Tylko http:// więc można wyciąć
	string domain = temp.substr(0, temp.find_first_of('/'));								// Domena
	string addressTail = temp.substr(temp.find_first_of('/'));								// Żądanie pliku
 
	// Tworzenie nagłówka
	temp = string("GET ") + addressTail + " HTTP/1.1\r\n"
			   + "Host: " + domain + "\r\n"
			   //+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5\r\n"
			   //+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
			   //+ "Accept-Language: pl,en-us;q=0.7,en;q=0.3\r\n"
			   //+ "Accept-Encoding: gzip,deflate\r\n"
			   //+ "Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7\r\n"
			   //+ "Keep-Alive: 300\r\n"
			   //+ "Connection: keep-alive\r\n"
			   + "\r\n\r\n";
 
	// Pobieranie IP serwera z serwera DNS
	if(FAILED(getaddrinfo(domain.c_str(), TEXT("80"), &hint, &wsResult)))
	{
		return 1;
	}
 
	// Wskaźnik na zwrócone adresy
	addrinfo* ptr = wsResult;
 
	// Tworzenie socketu
	if(FAILED(socket(wsResult->ai_family, wsResult->ai_socktype, wsResult->ai_protocol))) 
	{
		freeaddrinfo(wsResult);
		return 1;
	}
 
	//************************************************
	// Wysyłanie zapytania	
	//************************************************
 
	// Łączenie się do serwera
	if(FAILED(connect(pSocket, ptr->ai_addr, (int)ptr->ai_addrlen))) 
	{
		goto Error;
	}
 
	// Wysyłanie nagłówka
	if(FAILED(send(pSocket, temp.c_str(), temp.size(), 0)))
	{
		goto Error;
	}
 
	// Kończenie wysyłania
	if(FAILED(shutdown(pSocket, SD_SEND)))
	{
		goto Error;
	}
 
	//************************************************
	// Odbieranie danych
	//************************************************
 
	string answer;
	// Odbieranie danych
	do
	{
		ZeroMemory(recvBuffer, sizeof(recvBuffer));
		error = recv(pSocket, recvBuffer, sizeof(recvBuffer), 0);
 
		if(error > 0)
		{
			answer += string(recvBuffer);
		}
	}
	while(error > 0);
 
	closesocket(pSocket);		// Zamknięcie socketu
	freeaddrinfo(wsResult);		// oraz zwolnienie struktury addrinfo
 
	printf("Kod HTML strony \"http://www.gamedev.pl/articles.php\":\n\n%s", answer.c_str());
	return 0;
 
Error:
	closesocket(pSocket);
	freeaddrinfo(wsResult);
	return 1;
}

Wynikiem tego kodu jest wyświetlony kod HTML przykładowej strony.

Environmental mapping + skybox

Tym razem będzie mały update dotyczący silnika. Niby nic, ale można się pochwalić.

etuymcrypPo pierwsze Environmental mapping, czyli proste przybliżenie zjawiska odbicia światła na powierzchni ciała oraz załamania światła na granicy dwóch ośrodków. Wymaga dostarczenia tylko dodatkowej tekstury sześciennej (cube-mapa) oraz skorzystania z dwóch dodatkowych funkcji HLSL: refract i reflect. Można go wzbogacić dodatkowo o efekt Fresnela i rozszczepienie chromatyczne. Całość wygląda jak na screenie obok. Efekt ten nie jest trudny do zaimplementowania, więc szczegółów implementacyjnych nie będzie, zresztą są one w książce CG Tutorial.

Kolejną rzeczą jest skybox. Jak nazwa wskazuje, jest to pudełko z tłem nieba, które powoduje, że scena z obiektami nie wygląda po prostu pusto. Poniżej przedstawiam trzy kroki rysowania skyboxa:

  1. Potrzebne jest pudełko wraz z teksturą, którą jest już wspomniana wcześniej cube-mapa. Biblioteka D3DX posiada do tego odpowiednią funkcję: D3DXCreateBox(), która jako parametry przyjmuje wymiary pudełka. Najlepiej jest stworzyć sześcian 1:1:1.
  2. Pudełko powinno obracać się razem z kamerą, ale nie może zmieniać swojej pozycji. Aby to osiągnąć należy użyć tylko macierzy widoku i projekcji. przy czym ostatni wiersz tej pierwszej powinien mieć takie wartości: (0, 0, 0, 1).
  3. Skybox powinien być pierwszym elementem rysowanym na scenie, a dodatkowym warunkiem koniecznym by był on tłem całej sceny jest wyłączenie zapisu do ZBuffera. Dzięki temu każdy kolejny obiekt sceny będzie rysowany zawsze przed skyboxem. Aby odczytać kolor tekstury zamiast funkcji tex2D należy wykorzystać funkcję texCUBE, do której jako współrzędne tekstury przekazuje się pozycję danego pixela.

Jak widać nie jest to skomplikowane, a dzięki temu scena wygląda o wiele lepiej.

Swing czyli taniec z Javą

2009-10-26(3)

Tym razem postawiłem napisać o czymś, czym się aktualnie zajmuję na uczelni czyli programowaniem w Javie, a konkretniej programowanie interfejsu graficznego w Swingu. Java Swing jest w pełni obiektową (jak sam język) biblioteką graficzną służącą do majstrowania okienkowego GUI (podobnie jak QT, czy też Windows Forms). Korzystanie z tej biblioteki jest bardzo proste, niestety ma ona jedną podstawową wadę (oprócz bycia w Javie) – domyślnie okienko posiada własny style, więc nie wykorzystuje systemowego, dlatego może się wyróżniać.

Tworzenie okienek w Swingu jest bardzo proste, przykładowo wyświetlenie pustego okienka wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
 
public class Okienko
{
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            JFrame f = new JFrame();
            f.setTitle("Hello World!");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setSize(290, 250);
            f.setVisible(true);
         }
      });
   }
}

2009-10-26(4)

Ten krótki kod tworzy prostą formatkę JFrame, a następnie ustawia jej tytuł, rozmiar, akcje zamnięcia i na końcu pokazuje okienko. Wszystko to jest wykonywane w funkcji run() anonimowej klasy wewnętrznej po interfejsie Runnable (swoją drogą najpiękniejszy mechanizm w Javie :)), która działa w osobnym wątku biblioteki Swing. Jak widać jest to bardzo proste.

Co jednak gdy chcemy stworzyć coś bardziej wyszukanego? Wtedy najlepiej rozszerzyć (odziedziczyć) klasę JFrame. Mając własną klasę o wiele łatwiej wszystkim zarządzać wszystkimi komponentami, które znajdują się na formatce. Poniższy kod tworzy formatkę i wyświetla na niej przycisk i pole tekstowe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.awt.FlowLayout;
import javax.swing.*;
 
public class Okienko extends JFrame
{
   private JButton bPisz = new JButton("Pisz");
   private JTextArea textArea = new JTextArea(10, 20);
 
   public Okienko()
   {
      setTitle("Hello World!");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setSize(290, 250);
 
      setLayout(new FlowLayout());
      add(new JScrollPane(textArea));
      add(bPisz);
   }
 
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            new Okienko().setVisible(true);
         }
      });
   }
}

Powyższy kod jest już troszkę dłuższy, a jego zadanie polega na wyświetleniu na formatce przycisku oraz pola tekstowego. Pojawiły się tutaj dodatkowe komponenty takie jak: JButton, JTextArea, JScrollPane oraz funkcja setLayout(). Dwa pierwsze są oczywiste, bo jest to wspomniany przycisk i pole tekstowe, natomiast JScrollPane jest dodatkowym komponentem dodającym do JTextArea paski przesuwania (to wszystko przez proste opakowanie go). Funkcja setLayout() ustawia sposób rozmieszczenia kontrolek. Domyślnym układem jest BorderLayout, który kontrolki układa jedna na drugiej, natomiast FlowLayout ustawia je w taki sposób aby wszystkie były widoczne (po szczegóły zapraszam do dokumentacji :)).

2009-10-26(5)

No cóż mamy przycisk, ale teraz co zrobić, aby po jego naciśnięciu coś się stało. Obsługa akcji na kontrolkach opiera się na zdarzeniach, czyli w przypadku naciśnięcia przycisku wywoływana jest odpowiednia funkcja, która to zdarzenie potrafi obsłużyć. W takim razie jak obsłużyć akcję wciśnięcia przycisku? W tym celu należy dodać do przycisku odbiorcę zdarzenia za pomocą funkcji addActionListener(). Funkcja ta przyjmuje referencję do interfejsu ActionListener, z którego należy zaimplementować funkcję actionPerformed(). Właśnie ta funkcja zostanie wywołana w momencie wystąpienia zdarzenia.

Wynikowy kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.awt.FlowLayout;
import javax.swing.*;
import java.awt.event.*;
 
public class Okienko extends JFrame
{
   private JButton bPisz = new JButton("Pisz");
   private JTextArea textArea = new JTextArea(10, 20);
 
   public Okienko()
   {
      setTitle("Hello World!");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setSize(290, 250); 
 
      bPisz.addActionListener(new ActionListener()
      {
         public void actionPerformed(ActionEvent e)
         {
            textArea.append("Hello World ");
         }
      });
 
      setLayout(new FlowLayout());
      add(new JScrollPane(textArea));
      add(bPisz);
   }
 
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            new Okienko().setVisible(true);
         }
      });
   }
}

SDKMeshToXConverter

Już trochę czasu temu postanowiłem napisać konwerter wspomnianego formatu .sdkmesh do .x i w końcu dopiąłem swego. Mimo tego, że jak już wcześniej wspomniałem, format ten nie nadaje się do modeli animowanych (posiada informacje o animacji, ale nie ma informacji o kościach), można ostatecznie wyciągnąć z niego model statyczny.

To właśnie robi poniższa aplikacja. Wystarczy tylko przeciągnąć plik .sdkmesh na plik aplikacji i w katalogu mesha źródłowego pojawia się wygenerowany plik w formacie .x.

Pobierz

Kilka zmian

rpotStwierdziłem, że wypadałoby się wreszcie odezwać po kolejnej długiej nieobecności. W końcu udało mi się zaimplementować Skinning w silniku, oczywiście bazując na przykładzie SkinnedMesh z DX SDK. W praktyce zrobiłem  to co było w ów sample’u, a jedyną różnicą jest miejsce położenia kości, które u mnie znajdują się w hierarchii zaraz za modelem. Samą hierarchię tworzą modele (zwykłe drzewo, które ustanawia hierarchię na podstawie której tworzone są wynikowe macierze świata dla tych modeli). Póki co mam tylko dwa rodzaje modeli: StaticMesh i SkinnedMesh, ale bez problemu można do niej wstawić innego rodzaju obiekty, które występują na scenie.

Kolejną sprawą są stosowane przeze mnie formaty. Otóż w poprzedniej notce napisałem jakoby format .x był bardzo prostym formatem. Nic bardziej mylnego, jest on bardziej złożony niż myślałem (chodzi mi tylko o format modeli, nie o system zapisu danych). W standardowym pliku .x może znajdować się więcej modeli, które są ułożone w hierarchię, a do ich załadowania służy funkcja:

D3DXLoadMeshHierarchyFromX();

Funkcja ta ładuje nie tyko wspomnianą hierarchię meshy, ale również informacje o kościach i  animacji. Niestety format materiałów dalej nie zawiera nic więcej poza strukturą D3DMATERIAL9 i nazwą tekstury diffuse.

Natomiast format SDKMesh jest jeszcze gorszy (mimo jednej zalety, że posiada nazwy dla tekstur normal i specular), ponieważ nie posiada on żadnych informacji o kościach mimo, że są informacje o animacji. Po prostu wczytywany mesh jest już przekonwertowany i gotowy do wyświetlenia (co blokuje możliwość prostego użycia ID3DXSkinInfo, którym mógłbym software’owo przekształcić model np. do kolizji per vertex).

SDKMeshInfo

Pisząc różne efekty graficzne, w pewnym momencie możemy stwierdzić, że potrzebujemy jakiegoś innego modelu. Wtedy nasuwa się również pytanie “skąd wziąć taki model?”. Tutaj przychodzi z pomocą DirectX SDK, które posiada dużą ilość różnych modeli w formacie .x oraz .sdkmesh. Z formatem .x można sobie poradzić funkcją

D3DXLoadMeshFormX

Funkcja ta wczytuje i parsuje model, zwracając wskaźnik, z którego można już korzystać.

SDKMeshInfoNiestety format .x jest bardzo prosty, ponieważ zawiera on tylko jeden zestaw materiałów i teksturę diffuse na subset. Problem ten rozwiązano tworząc nowy format z rozszerzeniem .sdkmesh. Powstał on i jest wykorzystywany na potrzeby przykładów Direct3D 10. Pliki tego formatu mogą zawierać kilka modeli, podzielonych na kilka subsetów. Materiały zostały rozszerzone o tekstury normal i specular. Dodane zostały również informacje o klatkach animacji, natomiast szczegóły danej klatki zawiera osobny plik z rozszerzeniem .sdkmesh_anim. Więcej szczegółów dotyczących tego formatu znajduje się w dokumentacji pod hasłem “Overview of the SDK Mesh File Format”.

Postanowiłem się zainteresować tym formatem nie tylko z powodu tego, że część modeli jest w nim zapisana, ale potrzebowałem modeli, które mógłbym wykorzystać do mojej zabawy z animacją opartą na klatkach kluczowych. Właśnie z tego powodu napisałem aplikację, którą tu zamieszczam. SDKMeshInfo jest programem do podglądu bebechów plików .sdkmesh. Nie posiada on opcji ich wyświetlania, ale pokazuje pełne dane z nagłówków zawartych we wskazanym pliku.

Download

Shaderki

AplikacjaPostanowiłem wreszcie zająć się wyświetlaniem grafiki opartej na shaderach. Stworzyłem sobie prosty framework oparty na moim “silniku”, który jest aktualnie tylko szkieletem aplikacji. Framework składa się z kilku klas, których zadaniem jest uprościć wykonywanie różnych rzeczy. Aktualnie w zestawie jest kamerka FPP, prosty system cząsteczek oraz klasy do wczytywania modeli i zarządzania efektami.

Na dole tej notki znajduje się link do aplikacji, która reprezentuje aktualny stan kodu. Aplikacja wyświetla 4 modele wczytane z plików w formacie .x, 1 model z formatu .sdkmesh oraz cząsteczki, których pozycja obliczana jest w shaderze. Światło użyte w scenie jest punktowe, a obliczenia są wykonywane w pixel shaderze. Materiały modeli pochodzą z ich plików, dlatego nie wszystko wygląda super ;).

Kamerkę obsługuje się za pomocą myszy i klawiszy WSAD, a światło za pomocą strzałek oraz klawiszy PG_UP i PG_DN.

Pobierz

NViDo v0.3 Release

Tym razem chciałbym przedstawić mój nowy program. NViDo jest to aplikacja do pobierania filmów z serwisów takich jak Youtube. Zaletą programu jest prostota obsługi, która może sprowadzić się do skopiowania linku do schowka i wciśnięciu skrótu klawiszowego “SHIFT + ALT + D”. Innymi funkcjami aplikacji są:

  • NViDoodtworzenie filmu zaraz po ściągnięciu
  • tworzenie historii pobranych plików wideo (z której można bezpośrednio odpalić dany plik)
  • pobieranie również wersji HD jeżeli istnieje w serwisie

Aktualnie ilość serwisów, które NViDo obsługuje nie jest szałowa, ponieważ są to tylko (już wspomniany) Youtube i Vimeo (w przyszłości pewnie dodam więcej). Aplikacja jest w miarę odbugowana, jeśli wystąpią jakieś błędy to postaram się naprawić (choć nie obiecuję).

Pobierz

W tym miejscu chciałbym zareklamować również inną aplikację tego typu, a mianowicie jDownloader. Ten program, oprócz tego, że automatycznie pobiera pliki z takich serwisów jak rapidshare, potrafi ściągać filmy z większości znanych serwisów podobnych do Youtube.

jDownloader

Defekt optymalizacji kompilatora x64

Podczas dalszego kodowania mojego silnika natrafiłem na bardzo dziwny błąd kompilatora Visual Studio. Zaczynając od początku – mam oto taki kod:

enum
{
	// Rozmiary pól
	MAX_BITS_INDEX = sizeof(Size_t) * 4,
	MAX_BITS_MAGIC = sizeof(Size_t) * 4,
	// Maksymalne wartości pól (Przesunięcie 1 w dwóch operacjach, ponieważ inaczej kompilator nie daje rady)
	MAX_INDEX = ((1 << MAX_BITS_INDEX / 2) << MAX_BITS_INDEX / 2) - 1,
	MAX_MAGIC = ((1 << MAX_BITS_MAGIC / 2) << MAX_BITS_MAGIC / 2) - 1
};
/* ... */
 
// handle = 0x0000000400000006;   - przykladowo
// Size_t ma rozmiar wskaźnika (jest zależny od platformy)
Size_t value = handle & MAX_MAGIC;

Otóż problem z tym kodem jest taki, że gdy zostanie skompilowany na platformę x64, zostaje wygenerowany następujący kod w assemblerze:

    79:                 {
    80:                         Size_t value = handle & MAX_MAGIC;
 
000000013FC96F94  mov         rax,qword ptr [handle]
000000013FC96F9C  mov         qword ptr [value],rax
 
    81:                         if(value == *it) return true; // Dalszy kod

Jak widać problem polega na braku kluczowej instrukcji and, której zadaniem jest odcięcie najstarszych 32-bitów. Dla porównania kod assemblera wygenerowanego dla platformy x86 jest następujący:

    79:                 {
    80:                         Size_t value = handle & MAX_MAGIC;
00DC9DDC  mov         eax,dword ptr [handle]
00DC9DDF  and          eax,0FFFFh
00DC9DE4  mov         dword ptr [value],eax
    81:                         if(value == *it) return true; // Dalszy kod

Tutaj instrukcja ta występuje.

Błąd ten jednak nie występuje gdy samemu sprecyzuję stałą, tj.:

Size_t value = handle & 0xffffffff;

W tym przypadku zostanie wygenerowany poprawny kod.

Wniosek jest taki, że kompilator widząc operację and zmiennej ze stałą typu enum (który przechowuje wartości 32-bitowe), traktuje tą zmienną jako typ 32-bitowy. W tym przypadku stwierdza, że instrukcja and nie ma sensu, bo wartość MAX_MAGIC zawiera maksymalną wartość (0xffffffff). Według mnie jest to ewidentny błąd i nie powinno takie coś wystąpić.

// Edit :)
Problemem oczywiście jest to, że enum jest tak naprawdę typem int, więc się dokładnie tak zachowuje.

Problemów z x64 ciąg dalszy

Postanowiłem przekompilować aktualnie pisany framework pod platformę x64. Jest to już trochę napisanego kodu, więc objawiło się to trzema problemami, z których najważniejszym jest ten komunikat:

LNK1112: module machine type 'X86' conflicts with target machine type x64''

Zostaje on wyrzucony wtedy, gdy podczas składania programu x64 Linker napotka bibliotekę skompilowaną pod target x86.Poprawne ustawienie bibliotek

Przyczyną tego konfliktu są błędne wpisy ścieżek do bibliotek statycznych, które znajdują się w opcjach Visual Studio. Niestety wpisy dla platformy x64 są po prostu kopiowane z platformy Win32 (nie wszystkie, bo jest ich mniej). Należy je poprawić tak aby wskazywały na foldery z bibliotekami x64. Obok jest obrazek z poprawnymi ustawieniami mojego Visual Studio.

Drugim, ale już mniej znaczącym problemem jest ostrzeżenie

conversion from 'size_type' to 'int', possible loss of data

powodowane rozrzeszeniem rozmiaru typu ’size_type’ z 4 do 8. Typ ten jest zwracany przez funkcje size() w kontenerach biblioteki standardowe. W tym przypadku rozwiązaniem jest po prostu zamiana powszechnie używanego (unsigned) int na size_type bądź własny odpowiednik zdefiniowany w klauzulach #ifdef/#endif :).

Ostatnim problemem jest brak inline’owych wstawek assemblerowych. W moim przypadku za ten błąd odpowiadało makro NAssert, w środku którego jest wykorzystana konstrukcja:

__asm 
{
   int 3 
}

Powoduje ona wywołanie Breakpointa w miejscu asercji. Okazuje się, że rozwiązanie tego jest bardzo proste, ponieważ Visual Studio posiada następującą funkcję typu intrinsic:

__debugbreak()

Powoduje ona dokładnie tę samą akcję co podana wyżej wstawka w assemblerze.

A poniżej wynik całego procesu przenoszenia :).
End