Una biblioteca de mapeo de memoria C++ 11 multiplataforma de encabezado fácil de usar con una licencia MIT.
mio se ha creado con el objetivo de poder incluirse fácilmente (es decir, sin dependencias) en cualquier proyecto de C++ que necesite E/S de archivos asignados en memoria sin la necesidad de utilizar Boost.
No dude en abrir un problema. Intentaré abordar cualquier inquietud lo mejor que pueda.
¡Porque el mapeo de memoria es lo mejor desde el pan de molde!
Más en serio, la motivación principal para escribir esta biblioteca en lugar de usar Boost.Iostreams fue la falta de soporte para establecer un mapeo de memoria con un identificador/descriptor de archivo ya abierto. Esto es posible con mio.
Además, la solución de Boost.Iostreams requiere que el usuario seleccione los desplazamientos exactamente en los límites de la página, lo cual es engorroso y propenso a errores. mio, por otro lado, gestiona esto internamente, aceptando cualquier desplazamiento y encontrando el límite de página más cercano.
Aunque sea un pequeño detalle, Boost.Iostreams implementa IO de archivos mapeados en memoria con std::shared_ptr
para proporcionar semántica compartida, incluso si no es necesaria, y la sobrecarga de la asignación del montón puede ser innecesaria y/o no deseada. En mio, hay dos clases para cubrir los dos casos de uso: una que es de solo movimiento (básicamente una abstracción de costo cero sobre las funciones de mapeo específicas del sistema) y la otra que actúa igual que su contraparte Boost.Iostreams, con semántica compartida.
NOTA: el archivo debe existir antes de crear una asignación.
Hay tres formas de asignar un archivo a la memoria:
std::system_error
en caso de falla: mio::mmap_source mmap (path, offset, size_to_map);
o puede omitir los argumentos offset
y size_to_map
, en cuyo caso se asigna todo el archivo:
mio::mmap_source mmap (path);
std::error_code error;
mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error);
o:
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);
o:
mmap.map(path, error);
NOTA: Los constructores requieren que se habiliten excepciones. Si prefiere construir sus proyectos con -fno-exceptions
, aún puede utilizar las otras formas.
Además, en cada caso, puede proporcionar algún tipo de cadena para la ruta del archivo o puede utilizar un identificador de archivo válido 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);
// ...
}
Sin embargo, mio no comprueba si el descriptor de archivo proporcionado tiene los mismos permisos de acceso que la asignación deseada, por lo que la asignación puede fallar. Dichos errores se informan a través del parámetro std::error_code
out que se pasa a la función de mapeo.
USUARIOS DE WINDOWS : Esta biblioteca admite el uso de tipos de caracteres anchos para funciones donde se esperan cadenas de caracteres (por ejemplo, parámetros de ruta).
# 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
es de solo movimiento, pero si se necesitan varias copias en el mismo mapeo, use mio::basic_shared_mmap
que tiene la semántica std::shared_ptr
y tiene la misma interfaz 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);
Es posible definir el tipo de byte (que debe tener el mismo ancho que char
), aunque de forma predeterminada se proporcionan alias para los más comunes:
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 >;
Pero puede resultar útil definir sus propios tipos, por ejemplo, cuando utilice el nuevo std::byte
en C++17:
using mmap_source = mio::basic_mmap_source<std::byte>;
using mmap_sink = mio::basic_mmap_sink<std::byte>;
Aunque generalmente no es necesario, dado que los usuarios de mapas de mio solicitaron desplazamientos a los límites de las páginas, puede consultar la granularidad de asignación de páginas del sistema subyacente invocando mio::page_size()
, que se encuentra en mio/page.hpp
.
Mio se puede agregar a su proyecto como un archivo de encabezado único simplemente incluyendo single_includemiomio.hpp
. Los archivos de encabezado único se pueden regenerar en cualquier momento ejecutando el script amalgamate.py
dentro de third_party
.
python amalgamate.py -c config.json -s ../include
Como biblioteca de solo encabezado, mio no tiene componentes compilados. Sin embargo, se proporciona un sistema de compilación CMake para permitir pruebas, instalación y composición de subproyectos fácilmente en muchas plataformas y sistemas operativos.
Mio se distribuye con un pequeño conjunto de pruebas y ejemplos. Cuando mio se configura como el proyecto CMake de más alto nivel, este conjunto de ejecutables se crea de forma predeterminada. Los ejecutables de prueba de Mio están integrados con el programa controlador de prueba de CMake, CTest.
CMake admite varios backends para compilación y vinculación.
Para utilizar una herramienta de creación de configuración estática, como GNU Make o 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 utilizar una herramienta de creación de configuración dinámica, como Visual Studio o 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
Por supuesto, los pasos de compilación y prueba también se pueden ejecutar a través de todos los objetivos y de prueba , respectivamente, desde el IDE después de abrir el archivo de proyecto generado durante el paso de configuración.
Las pruebas de Mio también están configuradas para funcionar como cliente de la aplicación de panel de calidad del software CDash. Consulte la documentación del kitware para obtener más información sobre este modo de funcionamiento.
El sistema de compilación de Mio proporciona un objetivo de instalación y soporte para el consumo posterior a través de la función intrínseca find_package
de CMake. CMake permite la instalación en una ubicación arbitraria, que puede especificarse definiendo CMAKE_INSTALL_PREFIX
en el momento de la configuración. En ausencia de una especificación del usuario, CMake instalará mio en una ubicación convencional basada en el sistema operativo de la plataforma.
Para utilizar una herramienta de creación de configuración estática, como GNU Make o 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 utilizar una herramienta de creación de configuración dinámica, como Visual Studio o 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
Tenga en cuenta que el último comando de la secuencia de instalación puede requerir privilegios de administrador (por ejemplo, sudo
) si el directorio raíz de instalación se encuentra fuera de su directorio personal.
Esta instalación
include/mio
de la raíz de instalaciónshare/cmake/mio
de la raíz de instalación Este último paso permite que los proyectos posteriores de CMake consuman millones a través de find_package
, por ejemplo
find_package ( mio REQUIRED )
target_link_libraries ( MyTarget PUBLIC mio::mio )
USUARIOS DE WINDOWS : El objetivo mio::mio
#define
s WIN32_LEAN_AND_MEAN
y NOMINMAX
. El primero garantiza que el área de superficie importada de Win API sea mínima, y el segundo deshabilita las macros min
y max
de Windows para que no interfieran con std::min
y std::max
. Debido a que mio es una biblioteca de solo encabezado, estas definiciones se filtrarán en las compilaciones posteriores de CMake. Si su presencia está causando problemas con su compilación, puede usar el objetivo alternativo mio::mio_full_winapi
, que no agrega ninguna de estas definiciones.
Si mio se instaló en una ubicación no convencional, puede ser necesario que los proyectos posteriores especifiquen el directorio raíz de instalación de mio a través de
CMAKE_PREFIX_PATH
,CMAKE_PREFIX_PATH
, omio_DIR
.Consulte la documentación del kitware para obtener más información.
Además, mio admite instalaciones reubicables empaquetadas a través de CPack. Después de la configuración, desde el directorio de compilación, invoque cpack de la siguiente manera para generar una instalación empaquetada:
cpack -G < generator name > -C Release
La lista de generadores compatibles varía de una plataforma a otra. Consulte el resultado de cpack --help
para obtener una lista completa de los generadores compatibles en su plataforma.
Para usar mio como subproyecto, copie el repositorio de mio a la carpeta dependencias/externos de su proyecto. Si la versión de su proyecto está controlada mediante git, se puede utilizar un submódulo git o un subárbol git para sincronizarlo con el repositorio ascendente. El uso y las ventajas relativas de estas funciones de git están más allá del alcance de este documento, pero en resumen, cada una puede establecerse de la siguiente manera:
# 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 un subdirectorio mio en un proyecto, simplemente agregue las siguientes líneas a su proyecto para agregar directorios de inclusión mio a la ruta de inclusión de su destino.
add_subdirectory ( path /to/mio/ )
target_link_libraries ( MyTarget PUBLIC <mio::mio | mio> )
Tenga en cuenta que, como subproyecto, las pruebas y ejemplos de mio no se crearán y la integración de CPack se difiere al proyecto anfitrión.