Uma biblioteca de mapeamento de memória C++ 11 de plataforma cruzada, fácil de usar, apenas de cabeçalho, com licença MIT.
mio foi criado com o objetivo de ser facilmente incluído (ou seja, sem dependências) em qualquer projeto C++ que precise de IO de arquivo mapeado na memória sem a necessidade de obter Boost.
Fique à vontade para abrir um problema. Tentarei resolver quaisquer preocupações da melhor maneira possível.
Porque o mapeamento de memória é a melhor coisa desde o pão fatiado!
Mais seriamente, a principal motivação para escrever esta biblioteca em vez de usar Boost.Iostreams, foi a falta de suporte para estabelecer um mapeamento de memória com um identificador/descritor de arquivo já aberto. Isso é possível com mio.
Além disso, a solução da Boost.Iostreams exige que o usuário escolha os deslocamentos exatamente nos limites da página, o que é complicado e sujeito a erros. O mio, por outro lado, gerencia isso internamente, aceitando qualquer deslocamento e encontrando o limite da página mais próximo.
Embora seja um pequeno problema, Boost.Iostreams implementa IO de arquivo mapeado na memória com um std::shared_ptr
para fornecer semântica compartilhada, mesmo que não seja necessária, e a sobrecarga da alocação de heap pode ser desnecessária e/ou indesejada. No mio, existem duas classes para cobrir os dois casos de uso: uma que é somente movimentação (basicamente uma abstração de custo zero sobre as funções de mapeamento específicas do sistema) e outra que atua exatamente como sua contraparte Boost.Iostreams, com semântica compartilhada.
NOTA: o arquivo deve existir antes de criar um mapeamento.
Existem três maneiras de mapear um arquivo na memória:
std::system_error
em caso de falha: mio::mmap_source mmap (path, offset, size_to_map);
ou você pode omitir os argumentos offset
e size_to_map
, caso em que todo o arquivo será mapeado:
mio::mmap_source mmap (path);
std::error_code error;
mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error);
ou:
mio::mmap_source mmap = mio::make_mmap_source(path, error);
map
: std::error_code error;
mio::mmap_source mmap;
mmap.map(path, offset, size_to_map, error);
ou:
mmap.map(path, error);
NOTA: Os construtores exigem que exceções sejam habilitadas. Se preferir construir seus projetos com -fno-exceptions
, você ainda pode usar as outras formas.
Além disso, em cada caso, você pode fornecer algum tipo de string para o caminho do arquivo ou pode usar um identificador de arquivo válido e existente.
# include < sys/types.h >
# include < sys/stat.h >
# include < fcntl.h >
# include < mio/mmap.hpp >
// #include <mio/mio.hpp> if using single header
# include < algorithm >
int main ()
{
// NOTE: error handling omitted for brevity.
const int fd = open ( " file.txt " , O_RDONLY);
mio::mmap_source mmap (fd, 0 , mio::map_entire_file);
// ...
}
No entanto, o mio não verifica se o descritor de arquivo fornecido tem as mesmas permissões de acesso que o mapeamento desejado, portanto o mapeamento pode falhar. Esses erros são relatados por meio do parâmetro std::error_code
out que é passado para a função de mapeamento.
USUÁRIOS DO WINDOWS : Esta biblioteca suporta o uso de tipos de caracteres amplos para funções onde cadeias de caracteres são esperadas (por exemplo, parâmetros de caminho).
# include < mio/mmap.hpp >
// #include <mio/mio.hpp> if using single header
# include < system_error > // for std::error_code
# include < cstdio > // for std::printf
# include < cassert >
# include < algorithm >
# include < fstream >
int handle_error ( const std::error_code& error);
void allocate_file ( const std::string& path, const int size);
int main ()
{
const auto path = " file.txt " ;
// NOTE: mio does *not* create the file for you if it doesn't exist! You
// must ensure that the file exists before establishing a mapping. It
// must also be non-empty. So for illustrative purposes the file is
// created now.
allocate_file (path, 155 );
// Read-write memory map the whole file by using `map_entire_file` where the
// length of the mapping is otherwise expected, with the factory method.
std::error_code error;
mio::mmap_sink rw_mmap = mio::make_mmap_sink (
path, 0 , mio::map_entire_file, error);
if (error) { return handle_error (error); }
// You can use any iterator based function.
std::fill (rw_mmap. begin (), rw_mmap. end (), ' a ' );
// Or manually iterate through the mapped region just as if it were any other
// container, and change each byte's value (since this is a read-write mapping).
for ( auto & b : rw_mmap) {
b += 10 ;
}
// Or just change one value with the subscript operator.
const int answer_index = rw_mmap. size () / 2 ;
rw_mmap[answer_index] = 42 ;
// Don't forget to flush changes to disk before unmapping. However, if
// `rw_mmap` were to go out of scope at this point, the destructor would also
// automatically invoke `sync` before `unmap`.
rw_mmap. sync (error);
if (error) { return handle_error (error); }
// We can then remove the mapping, after which rw_mmap will be in a default
// constructed state, i.e. this and the above call to `sync` have the same
// effect as if the destructor had been invoked.
rw_mmap. unmap ();
// Now create the same mapping, but in read-only mode. Note that calling the
// overload without the offset and file length parameters maps the entire
// file.
mio::mmap_source ro_mmap;
ro_mmap. map (path, error);
if (error) { return handle_error (error); }
const int the_answer_to_everything = ro_mmap[answer_index];
assert (the_answer_to_everything == 42 );
}
int handle_error ( const std::error_code& error)
{
const auto & errmsg = error. message ();
std::printf ( " error mapping file: %s, exiting... n " , errmsg. c_str ());
return error. value ();
}
void allocate_file ( const std::string& path, const int size)
{
std::ofstream file (path);
std::string s (size, ' 0 ' );
file << s;
}
mio::basic_mmap
é somente movimentação, mas se forem necessárias múltiplas cópias para o mesmo mapeamento, use mio::basic_shared_mmap
que tem semântica std::shared_ptr
e tem a mesma interface que mio::basic_mmap
.
# include < mio/shared_mmap.hpp >
mio::shared_mmap_source shared_mmap1 ( " path " , offset, size_to_map);
mio::shared_mmap_source shared_mmap2 (std::move(mmap1)); // or use operator=
mio::shared_mmap_source shared_mmap3 (std::make_shared<mio::mmap_source>(mmap1)); // or use operator=
mio::shared_mmap_source shared_mmap4;
shared_mmap4.map( " path " , offset, size_to_map, error);
É possível definir o tipo de um byte (que deve ter a mesma largura de char
), embora os aliases para os mais comuns sejam fornecidos por padrão:
using mmap_source = basic_mmap_source< char >;
using ummap_source = basic_mmap_source< unsigned char >;
using mmap_sink = basic_mmap_sink< char >;
using ummap_sink = basic_mmap_sink< unsigned char >;
Mas pode ser útil definir seus próprios tipos, digamos, ao usar o novo std::byte
em C++17:
using mmap_source = mio::basic_mmap_source<std::byte>;
using mmap_sink = mio::basic_mmap_sink<std::byte>;
Embora geralmente não seja necessário, uma vez que os usuários do mio mapeiam compensações solicitadas para os limites da página, você pode consultar a granularidade de alocação de página do sistema subjacente invocando mio::page_size()
, que está localizado em mio/page.hpp
.
O Mio pode ser adicionado ao seu projeto como um único arquivo de cabeçalho simplesmente incluindo single_includemiomio.hpp
. Arquivos de cabeçalho único podem ser regenerados a qualquer momento executando o script amalgamate.py
em third_party
.
python amalgamate.py -c config.json -s ../include
Por ser uma biblioteca somente de cabeçalho, mio não possui componentes compilados. No entanto, um sistema de construção CMake é fornecido para permitir testes, instalação e composição de subprojetos fáceis em muitas plataformas e sistemas operacionais.
Mio é distribuído com um pequeno conjunto de testes e exemplos. Quando mio é configurado como o projeto CMake de nível mais alto, esse conjunto de executáveis é compilado por padrão. Os executáveis de teste do Mio são integrados ao programa driver de teste CMake, CTest.
CMake oferece suporte a vários back-ends para compilação e vinculação.
Para usar uma ferramenta de construção de configuração estática, como GNU Make ou Ninja:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake -D CMAKE_BUILD_TYPE= < Debug | Release >
-G < " Unix Makefiles " | " Ninja " > ..
# build the tests
< make | ninja | cmake --build . >
# run the tests
< make test | ninja test | cmake --build . --target test | ctest >
Para usar uma ferramenta de criação de configuração dinâmica, como Visual Studio ou Xcode:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake -G < " Visual Studio 14 2015 Win64 " | " Xcode " > ..
# build the tests
cmake --build . --config < Debug | Release >
# run the tests via ctest...
ctest --build-config < Debug | Release >
# ... or via CMake build tool mode...
cmake --build . --config < Debug | Release > --target test
É claro que as etapas de construção e teste também podem ser executadas através dos destinos all e test , respectivamente, de dentro do IDE após abrir o arquivo de projeto gerado durante a etapa de configuração.
Os testes do Mio também estão configurados para operar como cliente do aplicativo de painel de qualidade do software CDash. Consulte a documentação do Kitware para obter mais informações sobre este modo de operação.
O sistema de compilação do Mio fornece um alvo de instalação e suporte para consumo downstream por meio da função intrínseca find_package
do CMake. CMake permite a instalação em um local arbitrário, que pode ser especificado definindo CMAKE_INSTALL_PREFIX
no momento da configuração. Na ausência de uma especificação do usuário, o CMake instalará o mio em um local convencional com base no sistema operacional da plataforma.
Para usar uma ferramenta de construção de configuração estática, como GNU Make ou Ninja:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake [-D CMAKE_INSTALL_PREFIX = " path/to/installation " ]
[-D BUILD_TESTING = False]
-D CMAKE_BUILD_TYPE=Release
-G < " Unix Makefiles " | " Ninja " > ..
# install mio
< make install | ninja install | cmake --build . --target install >
Para usar uma ferramenta de criação de configuração dinâmica, como Visual Studio ou Xcode:
cd < mio source directory >
mkdir build
cd build
# Configure the project
cmake [-D CMAKE_INSTALL_PREFIX = " path/to/installation " ]
[-D BUILD_TESTING = False]
-G < " Visual Studio 14 2015 Win64 " | " Xcode " > ..
# install mio
cmake --build . --config Release --target install
Observe que o último comando da sequência de instalação pode exigir privilégios de administrador (por exemplo, sudo
) se o diretório raiz da instalação estiver fora do seu diretório inicial.
Esta instalação
include/mio
da raiz da instalaçãoshare/cmake/mio
da raiz da instalação Esta última etapa permite que projetos CMake downstream consumam mio via find_package
, por exemplo
find_package ( mio REQUIRED )
target_link_libraries ( MyTarget PUBLIC mio::mio )
USUÁRIOS DO WINDOWS : O destino mio::mio
#define
s WIN32_LEAN_AND_MEAN
e NOMINMAX
. O primeiro garante que a área de superfície importada da API Win seja mínima e o último desativa as macros min
e max
do Windows para que não interfiram com std::min
e std::max
. Como mio é uma biblioteca apenas de cabeçalho, essas definições vazarão nas compilações downstream do CMake. Se a presença deles estiver causando problemas em sua construção, você poderá usar o destino alternativo mio::mio_full_winapi
, que não adiciona nenhuma dessas definições.
Se o mio foi instalado em um local não convencional, pode ser necessário que os projetos downstream especifiquem o diretório raiz da instalação do mio por meio de
CMAKE_PREFIX_PATH
,CMAKE_PREFIX_PATH
oumio_DIR
.Consulte a documentação do Kitware para obter mais informações.
Além disso, o mio suporta instalações relocáveis empacotadas via CPack. Após a configuração, no diretório build, invoque cpack da seguinte maneira para gerar uma instalação empacotada:
cpack -G < generator name > -C Release
A lista de geradores suportados varia de plataforma para plataforma. Veja a saída de cpack --help
para obter uma lista completa de geradores suportados em sua plataforma.
Para usar o mio como um subprojeto, copie o repositório mio para a pasta dependencies/externals do seu projeto. Se o seu projeto tiver versão controlada usando git, um submódulo git ou subárvore git pode ser usado para sincronizar com o repositório updstream. O uso e as vantagens relativas desses recursos git estão além do escopo deste documento, mas resumidamente, cada um pode ser estabelecido da seguinte forma:
# via git submodule
cd < my project ' s dependencies directory>
git submodule add -b master https://github.com/mandreyel/mio.git
# via git subtree
cd <my project ' s root directory >
git subtree add --prefix < path/to/dependencies > /mio
https://github.com/mandreyel/mio.git master --squash
Dado um subdiretório mio em um projeto, basta adicionar as seguintes linhas ao seu projeto para adicionar diretórios mio include ao caminho de inclusão do seu destino.
add_subdirectory ( path /to/mio/ )
target_link_libraries ( MyTarget PUBLIC <mio::mio | mio> )
Observe que, como um subprojeto, os testes e exemplos do mio não serão construídos e a integração do CPack será adiada para o projeto host.