27 sty 2010

Deklaracje zapowiadające w celu przyspieszenia kompilacji projektu

W projektach pisanych w C++ lubię korzystać z biblioteki boost.
Niestety ma ona jedną uciążliwą wadę: kod wykorzystujący boosta strasznie wolno się kompiluje. Przyczyną tego jest fakt, iż boost w dużym stopniu wykorzystuje template-y i to w bardzo zaawansowany sposób. Metaprogramy to dla kompilatora c++ nie lada wyzwanie!

W projektach składających się z kilkudziesięciu plikow źródłowych czas kompilacji całości przekracza kilka minut! Nawet przy kompilacji przyrostowej z użyciem make, po zmianie jednego z headerów w projekcie kompilacja może trwać powyżej minuty!

Jedną z technik przyspieszających kompilację jest unikanie dodawania #include do własnych headerów. Jeżeli tylko można należy wstępnie zadeklarować klasę w headerze, a właściwy #include dodać dopiero do pliku cpp.

Poniżej przykład:
Piszemy klasę Database, w której chcemy skorzystać z klasy XmlDocument. Ściślej: obiekty klasy XmlDocument będą przekazywane jako parametr w metodach klasy Database.

Jak zatem powinniśmy napisać header klasy Database.hpp:
class XmlDocument;

class Database
{
    void init( XmlDocument &document );
}

A co jeśli do obiektu XmlDocument chcemy mieć dostęp nie przez referencję, ale przez smart pointer (boost::shared_ptr)?

Żeby zdefiniować smart pointer wcale nie jest potrzebny header klasy. Także tutaj wystarczy jej deklaracja zapowiadająca:
class XmlDocument;
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

class Database
{
    void init( XmlDocumentPtr document );
}


Dodatkowa wskazówka: aby uniknąć duplikowania kodu, dla klas z których chcę korzystać poprzez smart pointer, tworzę osobny - dodatowy header KlasaXXXPtr.hpp

Dla przykładu header taki dla klasy XmlDocument będzie wyglądał tak:
#ifndef XMLDOCUMENTPTR_HPP_
#define XMLDOCUMENTPTR_HPP_

#ifndef XMLDOCUMENT_H_
class XmlDocument;
#endif // XMLDOCUMENT_H_

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

#endif /* XMLDOCUMENTPTR_HPP_ */
Wszędzie, gdzie chcę zadeklarować smart pointera na XmlDocument po prostu wpisuję #include "XmlDocumentPtr.hpp", natomiast w pliku cpp (np. Database.cpp) muszę jeszcze dodać #include "XmlDocument.hpp".

22 sty 2010

Deklaracje zapowiadające obiektów z biblioteki xerces-c++

Staram się ograniczać liczbę #include-ów w moich headerach.

Jeśli to możliwe wstępnie deklaruję wykorzystywaną klasę, a właściwy #include tej klasy ląduje w pliku cpp. Dzięki temu projekt znacznie szybciej się kompiluje.

Przykład: w klasie SslSession chcę korzystać z obiektów Database.
SslSession.hpp wygląda tak:
class Database;
class SslSession
{
    Database &m_db;
};
SslSession.cpp wygląda tak:
#include "Database.hpp"
Niestety wstępna deklaracja obiektów z biblioteki xerces-c++ w powyższy sposób daje następujący błąd kompilacji:
namespace xercesc
{
    class xercesc::DOMNode;
}

/usr/include/xercesc/util/XercesDefs.hpp:110: error: declaration of namespace ‘xercesc’ conflicts with
../src/modules/../lib/XmlDocumentPtr.hpp:18: error: previous declaration of namespace ‘xercesc’ here
Okazuje się, że xerces-c ma mechanizm parametrycznego włączania/wyłączania namespace (w zależności od wersji xerces-c i użytego kompilatora), który nie współgra dobrze z klasycznymi deklaracjami zapowiadającymi.

Na szczęście chłopaki z apacha zapewnili inny mechanizm pozwalający na deklararowanie ich obiektów:
#include <xercesc/util/XercesDefs.hpp>

XERCES_CPP_NAMESPACE_BEGIN
class DOMNode;
XERCES_CPP_NAMESPACE_END
Teraz wszystko ładnie się kompiluje!

18 gru 2009

Zwracanie rekordu z procedury składowanej w PostgreSQL

Jeśli z procedury/funkcji składowanej w bazie danych PostgreSQL chcemy zwrócić kilka wartości, to możemy zadeklarowąć ją jako zwracającą typ RECORD, ad-hoc utworzyć rekord z wartościami i zwrócić go.
Nie jest to trudne. Niestety "dokumentaliści" PostgreSQL zapomnieli pokazać jak taką funkcję wywołać.
Poniżej przykład znaleziony na jednym z forów Posrgresa:

CREATE FUNCTION xyz() RETURNS record AS
$$
declare
 abc RECORD;
begin
 abc := (1, 2);
 return abc;
end;
$$
language plpgsql;

select a, b from xyz() as (a int, b int);


Czyli wołamy SELECTA na tym, co zwróci nasza funkcja, ale jeszcze musimy zamapować wartości z rekordu na kolumny klauzulą AS.

29 paź 2009

Nie używać wielkich liter w nazwach funkcji/procedur składowanych PostgresQL!

Postanowiłem napisać swoją pierwszą procedurę składowaną w PostgreSQL.

Utworzyłem ją za pomocą kreatora w pgAdmin, ale za nic w świecie nie mogłem jej uruchomić.

Każda próba wywołania, kończyła się komunikatem:
No function matches the given name and argument types. You might need to add explicit type casts.

Sprawdziłem, że wywołuję ją w tym samym schemacie (public), w którym jest ona zdefiniowana.

Uprościłem treść procedury do minimum (usunąłem wszystkie parametry) i oto co mi zostało:

CREATE OR REPLACE FUNCTION "InsertEvent"()
  RETURNS integer AS
$BODY$
BEGIN

    RETURN 1;

END;$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST 100;

A wołam ją tak:
select InsertEvent() AS ret

Nadal ten sam komunikat: nie może znaleźć wołanej procedury!

Minęła godzinka prób... ale zaraz, zaraz... nieco dziwnie to wygląda w definicji:
FUNCTION "InsertEvent"()

czasem pgAdmin wstawia mi cudzysłowia w identyfikatorach - nigdy nie zagłębiałem się dlaczego to robi. Zmieniłem wywołanie na:
select "InsertEvent"() AS ret

Hurrra! Działa!

Gdybym od początku nazwał procedurę nie używając konwencji CamelCase, np. insert_event, to cudzysłowia nie byłyby potrzebne i wszystko by się wykonało za pierwszym razem.

Szczegółowe wyjaśnienie co się stało:
Gdy użyłem nazwy procedury: InsertEvent, pgAdmin utworzył tzw. Quoted identifier, który jest case sensitive.
Dla nazwy: insert_event pgAdmin utworzył zwykły identyfikator, który jest case insensitive.

Więcej o identufikatorach w PostgreSQL tutaj


Wniosek:
Na wszelki wypadek lepiej używać standardowej konwencji nazywania identyfikatorów postgres: wszystkie litery małe rozdzielone znakiem "_". Np. insert_event

8 wrz 2009

Serwer ssl wykorzystujący boost::asio zawsze korzysta z jednego wątka

Napisałem serwer wykorzystujący boost::asio::ssl, który zawierał pulę 10 wątków obsługujących żądania klientów.
Szybko jednak okazało się, że żądania od klientów zawsze są obsługiwane w jednym wątku roboczym:
jeśli wątek ten wykonywał długą operację, to request od innego klienta czekał aż operacja w pierwszym wątku zakończy się.

Dokładną przyczynę tego błędu opisałem na grupie boost-users w poście:
"[asio] SSL enabled http server3 example is blocking"

Rozwiązanie problemu jest dość proste:
nie wykonywać długich operacji bezpośrednio w handlerze wywoływanym przez async_read(), async_write(). Zamiast tego handler powinien wysłać tę operację do io_serwis, aby wykonała się asynchoriniczie.

Przykład handlera wywyoływanego po asynchronicznym odczycie danych (requestu od klienta):

void Request::readHeaderHandler( const boost::system::error_code
&error, std::size_t bytesTransferred )
{
m_ioService.post( boost::bind
( &Request::executeCommandHandler::executeCommandHandler,
shared_from_this() ) );

}

void Request::executeCommandHandler()
{
// long running operation
char c;

}

1 wrz 2009

Jak serwer ssl z obiektu boost::asio::stream może uzyskać adres IP klienta?

// definicja stream-u ssl opartego na sockecie tcp
typedef boost::asio::ssl::stream ssl_socket;

// kod inicjujący nasłuch serwera na sockecie zakończony wywołaniem: async_accept() / accept()

// jak już klient się połączył, to mamy obiekt ssl_socket:
ssl_socket m_socket;

// z, którego można pobrać IP klienta:
const boost::asio::ip::tcp::endpoint &endpoint = m_socket.lowest_layer().remote_endpoint();
const boost::asio::ip::address &addr = endpoint.address();

std::cout << "client: " << addr.to_string() << std::endl;

Jak odczytywać certyfikaty X509 za pomocą openssl?

Ano tak:
http://kahdev.wordpress.com