Boost build windows dll

Communication between 2 different language isn’t so easy.

It’s often possible to find libraries to help us achieve this behaviour.

That’s what we are going to see in this Boost.Python tutorial for Windows.

Let’s get started.

First of all

We need to install Python 3 and Boost on your computer.

So in order to have the exact same software and libraries installed in the exact same locations, I suggest to follow the 2 following tutorials:

  1. Python: https://www.badprog.com/python-3-installing-on-windows-10
  2. Boost: https://www.badprog.com/c-boost-setting-up-on-windows-10

For Visual Studio 2017 and MSVC 141, install Python 3.7 and Boost 1.71.0 for MSVC 141.

For Visual Studio 2019 and MSVC 142, install Python 3.9 and Boost 1.77.0 for MSVC 142.

Creating a DLL with Visual Studio

Let’s now create a new project:

From Visual Studio > File > New > Project… > Installed > Visual C++ > Windows Desktop > Dynamic-Link Library (DLL).

Then:

  • Name: badprog_lib
  • Location: C:\dev\c++\py\
  • Let unchecked the Create directory for solution option (don’t need for this tutorial).

Click OK.

So you have now the following directory:

  • C:\dev\c++\py\badprog_lib\

With classic files generated by Visual Studio like:

  • C:\dev\c++\py\badprog_lib\badprog_lib.cpp
  • C:\dev\c++\py\badprog_lib\badprog_lib.sln
  • And so on.

Setting the Visual Studio project platform

First thing is now to change the project platform in the Configuration manager:

From Visual Studio > Select (right click mouse) the badprog_lib project > Properties… > On upper right click the Configuration Mananger… push button > Change Active solution platform from x86 to x64 > Close.

Then still in the badprog_lib Property Pages, at top center, change the Platform from Win32 to x64 (if not automatically updated).

So you have now:

  • Configuration: Debug
  • Platform: x64

Then click OK.

You can stay in Debug mode or change it to Release (it won’t change anything for our tutorial).

Setting Visual Studio include paths for Boost and Python

First let’s set the includes.

From Visual Studio > Select the badprog_lib.cpp file.

Then right click mouse > Properties… > Configuration Properties > C/C++ > General > Additioal Include Directories > Edit > Add the 2 following lines:

For Visual Studio 2017 (and MSVC 141) you need to install Python 3.7:

  • C:\soft\boost_1_71_0
  • C:\soft\python37\include

For Visual Studio 2019 (and MSVC 142) you need to install Python 3.9:

  • C:\soft\boost_1_77_0
  • C:\soft\python39\include

Then OK > Apply > OK.

Setting Visual Studio library paths for Boost and Python

We’ve now to set the libraries.

Same thing but with the linker:

From Visual Studio > Right click the badprog_lib project > Properties > Configuration Properties > Linker > General Additioal Library Directories > Edit > Add the 5 following lines:

For MSVC 141 (2017)

  • C:\soft\boost_1_71_0\lib64-msvc-14.1

  • C:\soft\python37\DLLs

  • C:\soft\python37\Lib

  • C:\soft\python37\libs

  • C:\soft\python37\tcl

For MSVC 142 (2019)

  • C:\soft\boost_1_77_0\lib64-msvc-14.2
  • C:\soft\python39\DLLs
  • C:\soft\python39\Lib
  • C:\soft\python39\libs
  • C:\soft\python39\tcl
  •  

Then OK > Apply > OK.

Let’s code a bit

It’s now time to code our Dinamic-Link Library (DLL) from Visual Studio.

In our badprog_lib.cpp file let’s add the following code:

For MSVC 141 (2017) and Python 3.7

// badprog.com

#include «stdafx.h»

#include <boost/python.hpp>

// —————————————————————————-

// sayHello

// —————————————————————————-

char const* sayHello() {

//

return «Hello world from Badprog :D»;

}

// —————————————————————————-

// BOOST_PYTHON_MODULE(badprog_lib)

// It creates the badprog_lib module to import from Python

// —————————————————————————-

BOOST_PYTHON_MODULE(badprog_lib) {

//

boost::python::def(«welcome», sayHello);

}

For MSVC 142 (2019) and Python 3.9

// badprog.com

#include «pch.h»

#define BOOST_PYTHON_STATIC_LIB

#include <boost/python.hpp>

// —————————————————————————-

// sayHello

// —————————————————————————-

char const* sayHello() {

  //

return «Hello world from Badprog :D»;

}

// —————————————————————————-

// BOOST_PYTHON_MODULE(badprog_lib)

// It creates the badprog_lib module to import from Python

// —————————————————————————-

BOOST_PYTHON_MODULE(badprog_lib) {

  //

boost::python::def(«welcome», sayHello);

}

Then from Visual Studio > Build > Build badprog_lib.

After the build, you should have these lines in your Visual Studio Build Output:

1>------ Build started: Project: badprog_lib, Configuration: Debug x64 ------

1>dllmain.cpp

1>   Creating library C:\dev\c++\py\badprog_lib\x64\Debug\badprog_lib.lib and object C:\dev\c++\py\badprog_lib\x64\Debug\badprog_lib.exp

1>badprog_lib.vcxproj -> C:\dev\c++\py\badprog_lib\x64\Debug\badprog_lib.dll

========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

If yes you can check that the following DLL has been created:

  • C:\dev\c++\py\badprog_lib\x64\Debug\badprog_lib.dll

This DLL will serve as API for our Python script.

Now take this DLL and move it to the following directory:

  • C:\dev\c++\py\badprog_lib\

So we’ve this file like this:

  • C:\dev\c++\py\badprog_lib\badprog_lib.dll

Rename the extension from .dll to .pyd.

You have now the following file:

  • C:\dev\c++\py\badprog_lib\badprog_lib.pyd

This extension does really matter because without it the Python script won’t be able to import it.

We’ve now 2 options in order to get the code from the badprog_lib.pyd library.

  1. Directly by typing the code from the Python console, line by line
  2. Creating a Python file and call this file from any console (PowerShell for example)

Typing the code from the Python console

From the C:\dev\c++\py\badprog_lib\ directory, open a console.

Then type:

py

Your console will become a Python interpreter.

And you should see something like this appears:

PS C:\dev\c++\py\badprog_lib> py

Python 3.7.5 (tags/v3.7.5:5c02a39a0b, Oct 15 2019, 00:11:34) [MSC v.1916 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

If yes, good news, you are in the Python console.

Let’s now import our library and type the following command:

import badprog_lib

If nothing has happened, then that’s great.

Let’s continue by calling our C++ code:

badprog_lib.welcome()

The welcoming message should appears:

'Hello world from Badprog :D'

So to sum up here what you should have typed and seen:

PS C:\dev\c++\py\badprog_lib> py

Python 3.7.5 (tags/v3.7.5:5c02a39a0b, Oct 15 2019, 00:11:34) [MSC v.1916 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import badprog_lib

>>> badprog_lib.welcome()

'Hello world from Badprog :D'

>>>

Creating a Python file and call it from a console

Let’s create a file named badprog_script.py in the same directory, so we’ve the following:

  • C:\dev\c++\py\badprog_lib\badprog_script.py

In this file we have to type the following code, that is exactly the same as the previous command in the Python interpreter:

// badprog.com

import badprog_lib

print(badprog_lib.welcome())

And now from this directory, open any console (Git bash, PowerShell, Cmd, etc.) and type:

py badprog_script.py

The output result will be:

Hello world from Badprog :D

Conclusion

A lot of things to check and set.

But you are now able to build a shared library directly from Visual Studio, call the Boost.Python library from a C++ code and use it with Python.

Good job, you did it. 

tag — The tag feature is used to customize the name of the generated files. The value should have the form:

@rulename

where rulename should be a name of a rule with the following signature:

rule tag ( name : type ? : property-set )

The rule will be called for each target with the default name computed by Boost.Build, the type of the target, and property set. The rule can either return a string that must be used as the name of the target, or an empty string, in which case the default name will be used.

Most typical use of the tag feature is to encode build properties or library version in library target names. You should take care to return non-empty string from the tag rule only for types you care about — otherwise, you might end up modifying names of object files, generated header file and other targets for which changing names does not make sense.

* debug-symbols=on/off.The debug-symbols feature specifies if produced object files, executables, and libraries should include debugging information. Typically, the value of this feature is implicitly set by the variant feature, but it can be explicitly specified by the user. The most common usage is to build release variant with debugging information.

runtime-debuggingon, off.The feature runtime-debugging the feature specifies if produced object files, executables, and libraries should include behavior useful only for debugging, such as asserts. Typically, the value of this feature is implicit the feature variant, but it can be explicitly specified by the user. The most common usage is to build release variant with debugging output.

target-osThe operating system for which the code is to be generated. The compiler you used should be the compiler for that operating system. This option causes Boost.Build to use naming conventions suitable for that operating system, and adjust build process accordingly. For example, with gcc, it controls if import libraries are produced for shared libraries or not.

The complete list of possible values for this feature is: aix, bsd, cygwin, darwin, freebsd, hpux, iphone, linux, netbsd, openbsd, osf, qnx, qnxnto, sgi, solaris, unix, unixware, windows.

See the section called “Cross-compilation” for details of cross-compilation

architecture The architecture features specify the general processor family to generate code for.

instruction-set  The instruction-set specifies for which specific instruction set the code should be generated. The code, in general, might not run on processors with older/different instruction sets.While Boost.Build allows a large set of possible values for this features, whether a given value works depends on which compiler you use. Please see the section called “C++ Compilers” for details.

address-model= 32/64.The address-model specifies if 32-bit or 64-bit code should be generated by the compiler. Whether this feature works depends on the used compiler, its version, how the compiler is configured, and the values of the architecture instruction-set features. Please see the section called “C++ Compilers” for details.

c++-template-depth=Any positive integer.This feature allows configuring a C++ compiler with the maximal template instantiation depth parameter. Specific toolsets may or may not provide support for this feature depending on whether their compilers provide a corresponding command-line option.

Note: Due to some internal details in the current Boost.Build implementation it is not possible to have features whose valid values are all positive integer. As a workaround a large set of allowed values has been defined for this feature and, if a different one is needed, user can easily add it by calling the feature.extend rule.

embed-manifestAllowed values: on, off.

This feature is specific to the msvc toolset (see the section called “Microsoft Visual C++”), and controls whether the manifest files should be embedded inside executables and shared libraries, or placed alongside them. This feature corresponds to the IDE option found in the project settings dialog, under → → → .

embed-manifest-fileThis feature is specific to the msvc toolset (see the section called “Microsoft Visual C++”), and controls which manifest files should be embedded inside executables and shared libraries. This feature corresponds to the IDE option found in the project settings dialog, under → → → .

Cover image for The definitive guide on compiling and linking Boost C++ libraries for Visual Studio projects

Recently, a requirement came up to migrate a legacy C++ codebase, containing quite a few of custom implemented functionalities, into a more robust architecture. After spending some time at searching, we decided to include the Boost libraries, due to the large set of utilities they offer. 

Most of the C++ Boost libraries are header-only; they consist entirely of header files, and require no separately-compiled library binaries. However, there are some libraries that need to be built separately. The «Getting Started» guide on Boost website is quite informative, but do not provide clear guidance on how to build for multiple architectures and targets (debug/release). We’re using Visual Studio as our IDE and finding the best way to build those binaries was quite tiresome. 

For that reason, we dedicated this guide on how to build and link the C++ Boost libraries in Visual Studio projects.

Downloading the Boost libraries

To get the latest version of Boost (or any other version for that matter), go to the official download page. We are targeting Windows platforms, so we need to choose the respective version. The «Getting Started» guide recommends downloading the 7-Zip archive.

We no longer recommend .zip files for Boost because they are twice as large as the equivalent .7z files. We don’t recommend using Windows’ built-in decompression as it can be painfully slow for large archives. - »Getting Started on Windows», www.boost.org

The current release, as of this day, is version 1.76.0, but the methodology we are going to follow works (and will continue to work in the future) for any release.

After we successfully downloaded the archive, we need to extract the containing folder into the desired location. 

The documentation assumes that the path for any library version is inside C:\Program Files\boost, so we’re going to respect that. Create a directory called boost inside C:\Program Files\ and extract the archive there.

Alt Text

Building the binaries

The Boost libraries includes a really nice build system, which we are definitely going to use it. The build system is triggered from the command line. First we have to open the cmd window and navigate into the root folder of the Boost library.

Alt Text

Then we have to initialize the build system by running the bootstrap.bat file.

Alt Text

After bootstraping is done, it’s time to build the actual binaries. Remember what we said earlier: We have to build

  • artifacts for x64 architecture
  • artifacts for x86 architecture
  • artifacts for static linking (i.e. .lib files)
  • artifacts for dynamic linking (i.e. .dll files)
  • release artifacts
  • debug artifacts

In general, we need to trigger 8 builds. Fortunately for us, the build command allow us to specify multiple targets in one build. In the end, we’ll only trigger two builds, one for each architecture type.

Building for x86 architecture

Run the following command in you terminal:

b2 --build-dir=build\x86 address-model=32 threading=multi --stagedir=.\bin\x86 --toolset=msvc -j 16 link=static,shared runtime-link=static,shared --variant=debug,release

Enter fullscreen mode

Exit fullscreen mode

Alt Text

This command will build and place the binaries into C:\Program Files\boost\boost_1_76_0\bin\x86\lib\. The output will contain all the required files we might need for most of our projects.

Building Debug for x64 architecture

Execute the following command on you terminal:

b2 --build-dir=build\x64 address-model=64 threading=multi --stagedir=.\bin\x64 --toolset=msvc -j 8 link=static,shared runtime-link=static,shared --variant=debug,release

Enter fullscreen mode

Exit fullscreen mode

Alt Text

This command will build and place the binaries into C:\Program Files\boost\boost_1_76_0\bin\x64\lib\. As before, the output will contain all the required files we might need for most of our projects.

Explaining the build command arguments

Let’s take a quick look on the arguments used in the previous two commands.

  • --build-dir: Specify the directory to place all the intermediate files while building.
  • address-model: Specify the targeting address model.
  • threading: Compile Boost to be thread aware. (see this question on stackoverflow for more info.)
  • --stage-dir: the directory where the binaries will be placed.
  • --toolset: The compiler and linker that will be used to build the artifacts. We chose msvc; this should be the default on Windows, but in case we have more compilers installed on the system, it’s better to explicitly define it.
  • -j: how many threads to use for building. This can drastically improve the build times on multi-threading environments, i.e. most modern machines.
  • link: declare a library target.
  • runtime-link: linking statically/dynamically to the C++ standard library and compiler runtime support libraries.
  • --variant: Build debug or release version of the binaries.

Including the Boost libraries on Visual Studio

To be able to work with the Boost libraries in Visual Studio, we have to define the root path, that is C:\Program Files\boost\boost_1_76_0, into each individual project properties. In this guide, we use Visual Studio 2019, but this process can be applied to older versions as well. To include the libraries, do the following:

On a Visual Studio solution → Select the project you want to add the library into → Right Click → «Properties»

The properties window should now be open. An example is attached bellow:

Alt Text

Before we proceed take a moment to ensure that both «Configuration» and «Platform» values are selected as shown in the following screenshot. This will apply the changes we’re going to make into all the platforms.

Alt Text

We’re going to modify the «Additional Include Directories» option of «C/C++» → «General» property.

Alt Text

Select the drop-down button and then click < Edit.. >.

Alt Text

Now we have to add the library path and hit OK.

Alt Text

The option value should be updated as shown in the following screenshot.

Alt Text

Note that %(AdditionalIncludeDirectories) macro was automatically included in the value. This is expected and includes some default values specified by the system.

We are ready to work with most of the Boost libraries. We can include them in our source code without any issues. However, if we use libraries that require linking (like boost/chrono, boost/thread, etc) we have to link the binaries we built earlier to the linker.

Linking the Boost libraries on Visual Studio

This is the final step to our journey. The only think we have to do, is to tell the linker to include our binaries. Like before, open the properties window:

On a Visual Studio solution → Select the project you want to add the library → Right Click → «Properties»

Alt Text

Before we proceed take a moment to ensure that the «Configuration» and «Platform» values are selected as shown in the following screenshot. This will apply the changes we’re going to make into all the platforms.

Alt Text

We’re going to modify the «Additional Library Directories» option of «Linker» → «General» property.

(Note: In case your project builds a static library, the «Linker» menu will be shown as «Librarian«.)

Alt Text

Select the drop-down button and then click < Edit.. >.

Alt Text

Add the following value: C:\Program Files\boost\boost_1_76_0\bin\$(PlatformTarget)\lib, and hit OK.

Alt Text

The property value should be updated as shown in the following screenshot.

Alt Text

Take a quick note on $(PlatformTarget) macro. Recall when we built our targets and created two directories, one for each architecture. This macro defines the correct folder for use depending on the selected build platform.

Note that %(AdditionalLibraryDirectories) macro was automatically included in the value. This is expected and includes some default values specified by the system.

Testing the boost libraries

We should now be able to build and run our projects in both x86 and x64 architectures, targeting both debug and release versions. In case you want a quick, test program, we provide the following example from boost/chrono library.

Bellow is a running example screenshot using Debug and x64:

Alt Text

Conclusion

In this guide we described how we can set up Boost C++ libraries in Windows and link them into Visual Studio projects. We tried to be as detailed as possible in our explanation. We wanted to provide a unified reference for future readers who might encounter the same problems we did.

As a final note, we inform the readers that we tested all the steps provided in this article, on multiple projects, without any issues. However, if you have a different set-up, that might or might not cause problems. If this is the case, feel free to comment your question or point any ambiguity you have encountered. 

I have plans to write more articles regarding project organization and software development in the future. Feel free to post suggestions and articles ideas you might want to read about.

 На странице очень кратко описан процесс сборки boost. Тем, кто знаком с процессом сбоки boost, статья может быть полезна в качестве шпаргалки или источника для копипасты. Начинающие могут рассматривать эту страницу как дополнительный материал к другим источникам. Не стоит рассматривать эту статью как основной источник информации по этой теме.

 Сборка boost под Windows и Linux

 Процесс сборки библиотеки boost для ОС Windows и Linux существенно не отличается. Далее кратко описаны необходимые шаги:

  • Скачать дистрибутив с официального сайта.

  • Распаковать архив

  • Зайти в распакованный архив

  • Выполнить bootstrap.bat (Windows) или bootstrap.sh (Linux).

  • Выполнить b2 с необходимыми параметрами

Например, процесс скачивания и сборки на Linux может выглядеть так:

# Ссылка на архив, актуальные ссылки искать на https://www.boost.org/users/download
url=https://archives.boost.io/release/1.86.0/source/boost_1_86_0.tar.gz
build_dir=boost-build

# Скачиваем архив
wget $url

# Распаковываем архив
tar -xzf boost_1_86_0.tar.gz
pushd boost_1_86_0

# Запускаем bootstrap
./bootstrap.sh
prefix=../$build_dir

# Создаём директорию для сборки
mkdir -p $prefix

# Запускаем сборку
./b2 -j 16 --prefix=$prefix install > ../build.log
popd

 Скачать скрипт для сборки boost для Linux / WSL : build-boost.sh

Аналогичный сценарий для Windows в PowerShell:

# Ссылка на архив, актуальные ссылки искать на https://www.boost.org/users/download
$url = "https://archives.boost.io/release/1.86.0/source/boost_1_86_0.zip"
$build_dir = "boost-build"

# Скачиваем архив
Invoke-WebRequest -Uri $url -OutFile boost_1_86_0.zip

# Распаковываем архив
Expand-Archive boost_1_86_0.zip -DestinationPath .
pushd boost_1_86_0

# Запускаем bootstrap
.\bootstrap.bat
$prefix = "..\$build_dir"

# Создаём директорию для сборки
mkdir -ErrorAction SilentlyContinue $prefix

# Запускаем сборку
.\b2 -j 16 --prefix=$prefix install > ..\build.log
popd

 Скачать скрипт для сборки boost для PowerShell : build-boost.ps1

Сборка может занимать ~ от 5 до 40 минут в зависимости от параметров сборки и конфигурации машины, не включая времени на скачивание и распаковку. Общий размер собранных библиотек может достигать 1.5 гигабайт при настройках по умолчанию.

Чтобы настроить сборку с нужными параметрами необходимо передать эти параметры при вызове  b2. Вот некоторые полезные параметры:

  1.  —prefix=directory — директория, куда будут скопированы заголовочные файлы и библиотеки после сборки. Настоятельно рекомендуется указывать этот параметр

  2.  -j N — выполнять сборку в N потоков — может сильно ускорить компиляцию

  3.  toolset=compiler — использовать для сборки компилятор compiler (должен быть установлен). Например toolset=vc142 означает сборку компилятором Microsoft Visual C++ 2019. Также могут быть указаны такие компиляторы как gcc, mingw, clang и другие. Полный список компиляторов можно найти на странице B2 User Manual.

  4. cxxflags=flags — дополнительные опции, передаваемые компилятору, например cxxflags=»-std=c++17″.

  5. link=type — указывает собирать статические или динамические библиотеки, возможные значения: link=static, link=shared.

  6. address-model=arch — явно указывает генерировать 32- или 64-разрядный код. Возможные значения: address-model=32address-model=64.

Полный набор флагов и свойств можно найти на странице B2 User Manual.

Примеры команд для сборки boost на mingw:

b2 -j 4 toolset=mingw link=shared --prefix=C:/boost install

b2 toolset=gcc threading=multi link=static runtime-link=shared --prefix=C:/boost install

Примеры команд для сборки boost на Visual C++:

b2 -j 8 toolset=msvc variant=debug link=shared threading=multi runtime-link=shared --prefix=C:/boost install

b2 toolset=vc141 link=static threading=multi runtime-link=shared address-model=32

Пример команды для сборки boost с помощью gcc на Linux:

./b2 -j 16 toolset=gcc link=shared runtime-link=shared address-model=64 --cxxflags="-fPIC -std=c++17" --prefix=/home/user/boost install
Сборка кросс-компилятором под ARM

  •  Скачать и распаковать.

  •  Запустить bootstrab.sh .

  •  Открыть project-config.jam, заменить «using gcc» на «using gcc : arm : arm-linux-gnueabihf-g++».

  •  Добавить путь к тулчейну в переменную PATH (если не сделано).

  •  В командной строке b2 указать необходимые параметры: toolset=gcc-arm cxxflags=»-std=c++11″.

  •  Сборка.

 Итоговая строка b2 может выглядеть так:

./b2 toolset=gcc-arm cxxflags="-std=c++11" --prefix=/home/user/boost-arm/ install

 Примечание: флаг cxxflags=»-std=c++11″ в некоторых случаях может помочь при компоновке boost::log.

Замечания о статической компоновке

На дистрибутивах с архитектурой x86_64 статические библиотеки, устанавливаемые через пакетный менеджер из репозиториев, могут быть собраны без флага -fPIC. Это не позволяет осуществлять статическую компоновку библиотеки к динамической библиотеке, что иногда бывает нужно. Для решения проблемы нужно собрать библиотеки с указанием флага -fPIC. Кроме того, желательно явно указать стандарт языка при сборке библиотек. Команда сборки библиотек Boost может выглядеть так:

./b2 link=static threading=multi cxxflags="-fPIC -std=c++11" install --prefix=/path-to-install

Полное описание всех возможностей и свойств b2 можно найти на странице B2 User Manual.

Время на прочтение9 мин

Количество просмотров14K

Всем доброго {daytime}!

Сегодня пришла пора рассказать вам о фундаментальной проблеме перекодировки при взаимодействии проекта собранного на MS Visual C++ на платформе Windows и наиболее приятной скриптовой обвязки для языка C++, благодаря библиотеке Boost.Python, собственно написанной для языка Python.

Вы ведь хотите использовать для вашего приложения на C++ под ОС Windows хорошую скриптовую обвязку на последней версии Python 3.x, либо вы хотите использовать для вашего приложения на Python максимально ускоренный участок кода вашего модуля, переписанный на C++. В обоих случаях, если вы знаете оба языка как минимум хорошо, вам стоит это прочитать.

Не буду вас утомлять многочасовыми выкладками о проблеме перекодировки текста в принципе. Все мы знаем что эта проблема не новая, везде и всюду решается по разному, в большинстве случаев никак, то есть перекладывается на плечи программиста.

В языке Python, начиная с версии 3.0 принято решение считать строками только сам текст. Не важно как сам текст закодирован, а закодирован он в Юникоде, само понятие строки навсегда оторвано от его кодировки. То есть нет никакой возможности понять какое число соответствует символу в строке иначе, как перекодировать его в массив байт, указав кодировку.

"Привет!".encode('cp1251')

Пример выше показывает, что сама строка «Привет!» останется таковой как вы её набрали, вне зависимости от того, смотрят на неё в России, США или в Китае, на Windows, Linux или MacOS, она останется строкой «Привет!». Декодировав её в массив байт методом строки str.encode( encoding ) мы всегда получим одно и то же значение элементов массива байт, вне зависимости от того в какой точке земного шара мы находимся и какой платформой мы пользуемся. И это замечательно!
Однако вернёмся на Землю. Есть такая ОС Windows…

Вся проблема заключается в замечательной среде разработки MS Visual Studio. А более всего она замечательна тем, что все строки в C++ в ней гарантированно в кодировке кодовой страницы Windows. То есть для России все строки всегда будут в ‘cp1251‘. И всё бы ничего, но для вывода на веб-страницу, сохранения в XML, вывода в интернациональную БД и прочего данная кодировка не подходит. Предлагаемый Microsoft вариант со строками вида L«Привет» приемлем чуть более, но мы ведь знаем как замечательно в C++ работается с такими строками. Кроме того мы будем исходить из того, что к нам попал проект уже с кучей строк в виде cp1251. Гигабайты кода, работающие с std::string и char* и работающие с ними прекрасно: быстро и качественно.

Если вы идёте со стороны Python в C++, просто помните, что строки Python отлично конвертируются в char* используя внутреннюю память Python, поскольку все строки в Python 3.x как минимум в UTF-8 уже хранятся и за ними зорко следят GC и счётчик ссылок. Поэтому опять же: не надо этого UCS-2 от Microsoft выдаваемого за Юникод, используйте обычные строки. Ну и кроме того, помните, что локальная для России БД вашей компании не скажет вам спасибо за удвоенный размер данных, при переходе с WIN-1251 на UTF-8, поскольку наверняка под завязку набита кириллицей.
В общем проблема обозначена.

Теперь решение.

У вас уже наверняка есть последняя версия Python 3.x (в настоящий момент это — Python 3.3), если ещё нет, ставьте последний отсюда: www.python.org/download/releases
Также у вас наверняка стоит MS Visual Studio (в настоящий момент последняя — это VS2012, но всё нижесказанное будет верно и для предыдущей версии VS2010).
Для связки ваших классов на C++ с Python потребуется библиотека Boost.Python. Поставляется в составе уже почти стандартной библиотеки Boost: www.boost.org (в настоящий момент последней версией является 1.52, но проверено и верно вплоть до 1.44).
К сожалению, в отличие от всего остального, Boost.Python нужно собрать. Если он у вас ещё не собран вместе с остальными библиотеками собрать только Boost.Python можно следующей командой Boost.Build (в более старых версиях через bjam):
b2 --with-python --build-type=complete
Если вы выкачали Python 3.x для x64, то нужно указать ещё и address-model=64.
Более подробно в документации Boost.Build.
В результате в {BoostDir}\stage\lib\ у вас должна появится куча библиотек вида boost-python*. Они нам уже вот-вот понадобятся!..

Итак собственно воспроизводим проблему. Пишем простой класс:

    class MY_EXPORT Search
    {
    public:
        static string That( const string& name );
    };

С вот такой реализацией его единственного метода:

    string Search::That( const string& name )
    {
        if( name == "Это Я!" )
            return "Я";
        else
            throw runtime_error( "Я ничего не нашёл!" );
    }

В реальности всё значительно сложнее: у вас скорее всего запись из БД с полями кириллицей, да и сами значения тоже кириллицей, и всё в кодировке Windows-1251. Но нам чтобы отладится хватит этого тестового примера. Здесь есть конвертация строк туда и обратно из С++ и даже передача исключений в Python.

Используя Boost.Python обернём нашу маленькую библиотеку:

BOOST_PYTHON_MODULE( my )
{
    class_<Search>( "Search" )
        .def( "That", &Search::That )
        .staticmethod( "That" )
    ;
}

Не забываем про зависимости от Boost и исходной библиотеки в настройках проекта!
Полученную библиотеку переименовываем в my.pyd (да-да, просто меняем расширение).

Пробуем поработать с ней из Python. Можно прямо из консоли, если нет под рукой IDE вроде Eclipse+PyDev, просто импортируем и используем в две строки:

import my
my.Search.That( "Это Я!" )

Не забываем, что это всё-таки .dll и ей наверняка требуется .dll исходной библиотеки с классом Search, кроме того новой библиотеке-обёртке потребуется .dll Boost.Python соответствующей сборки из {BoostDir}\stage\lib\, например для MS VS2012 и Boost 1.52 для сборки Debug (Multi-thread DLL) это boost_python-vc110-mt-gd-1_52.dll.
Если непонятно чего не хватает вашей .dll посмотрите её зависимости с помощью того же Dependency Walker: www.dependencywalker.com — просто откройте depends.exe вашу .dll с библиотекой-обёрткой.
Итак вам удалось импортировать библиотеку my и выполнить my.Search.That( "Это Я!" )

Если всё хорошо, вы увидите пришедшее исключение из С++ с пустым текстом. То есть мало того, что мы не попали в нужную ветку if, так ещё и текст исключения перекодировался не так, как мы его отправили!

Если вы присоединитесь к процессу Python через «Attach to process» из MS Visual Studio, то увидите что в Search::That( const string& name ) приходит name в UTF-8. Boost.Python не знает о том в какой кодировке отдавать строку, поэтому отдаёт по умолчанию в UTF-8.
Само собой наш код в Visual Studio полностью ориентирован на Windows-1251, поэтому понять что «Р­С‚Рѕ РЇ!» на самом деле «Это Я!» он также не может. Получаем разговор слепого с глухим. По той же причине не видно текста исключения пришедшего из C++ в Python.

Ну что же, будем исправлять.

Первое что приходит в голову: унаследовать/завернуть исходный класс в другой, который умеет перекодировать.
Ага, теперь посмотрим на остальные классы, сиротливо шаркающих ножкой в ожидании своей очереди. Вы готовы потратить полжизни? Даже если это не так, первые же замеры производительности покажут насколько вы не правы оборачивая потомков. Ну и в конце у вас будут адовые проблемы при попытке достать обёрнутые классы обратно в объекты C++. Они у вас будут, поверьте! Мы строим мост по которому будем ходить в обе стороны, и обёртки классов должны напрямую ссылаться на методы и свойства нужного класса. Смотри в сторону extract<T&>(obj) из boost::python на стороне C++.

Анализируем всё, что делается в Boost.Python когда строка путешествует между C++ и Python. Видим несколько замечательных мест в которых используются функции PyUnicode_AsString и PyUnicode_FromString. Немного зная родное для Python API для чистого Си (если не знаем, то читая документацию) понимаем, что это и есть корень всех зол. Boost.Python отлично различает Python 2 и 3 версий, но понять самостоятельно что строку юникода нужно преобразовать в строку закодированную кодовой страницей файловой системы он не может, однако предоставляет для этого альтернативные функции, которые предлагается использовать самостоятельно:

PyUnicode_DecodeFSDefault — перекодирует строку в кодировке файловой системы (в нашем случае это как раз Windows-1251) и возвращает уже готовый объект строки, отлично подходит вместо PyUnicode_FromString в {BoostDir}\libs\python\src\ в файлах str.cpp и converter\builtin_converters.cpp.

PyUnicode_DecodeFSDefaultAndSize – то же самое, но с указанием размера строки. Подходит в качестве замены аналогичной PyUnicode_FromStringAndSize в тех же файлах.

PyUnicode_EncodeFSDefault — наоборот принимает объект строки из питона и перекодирует, возвращает результат в виде массива байт (объекта PyBytes), из массива байт уже после этого можно вытянуть обычную сишную строку функцией PyBytes_AsString. Требуется для обратного преобразования вместо функции PyUnicode_AsUTF8String, а в паре
PyBytes_AsString( PyUnicode_EncodeFSDefault(obj) ) заменяют макрос _PyUnicode_AsString( obj ) делающий фактически то же, но без переконвертации.

Итак, мы вооружены до зубов, знаем врага в лицо, осталось только его найти и обезвредить!

Нам нужны файлы использующие PyUnicode_* в коде

{BoostDir}\libs\python\src\

и заголовочных файлах внутри

{BoostDir}\boost\python\

, кроме того, открою сразу тайну, нам потребуется ещё поправить исключения в файле error.cpp.

В общем список следующий:
builtin_converters.cpp — правим преобразования строк из Python в С++ и обратно
builtin_converters.hpp — надо поправить макрос преобразования в заголовочном файле
str.cpp — правим обёртку в C++ над классом Python str (обычная строка питона в C++).
errors.cpp – правим передачу текста исключения из C++ в Python

Изменений немного, они точечные, все перечислены ниже, в архиве приложенном к статье лежат патчи и отчёты об изменениях, как правило все изменения не превышают одной строки кода, чаще даже одной инструкции вызова, суммарно их ровно 13 на 4 файла. Вы ведь не суеверны, нет?..

После всех правок собираем только Boost.Python уже упомянутой выше командой:
b2 --with-python --build-type=complete
(Добавьте обязательно address-model=64 если сборка для x64, т. е. и ваш проект, и Python 3.x установленный на вашей машине собраны под 64-разрядную архитектуру адресации.)

После того как Boost.Python собран, соберите заново свой проект с обновлённой библиотекой, обновились не только .lib и .dll, но и один заголовочный файл.
Не забудьте подменить старый и унылые .dll на свежесобранные. Вы ведь наверняка не забудете их скопировать, так ведь?!

Момент истины!

import my
res = my.Search.That( 'Это Я!' )
print( res )

Всё тот же код теперь возвращает то, что и ожидалось: строку ‘Я’.
Вполне себе кириллица, очень даже Юникод, если Python 3 считает этот объект строкой!
Теперь проверим как там придёт наше исключение:

import my
res = my.Search.That( 'Это Я!' )
print( res )
try:
    my.Search.That( 'Это кто-то другой!' )
except RuntimeError as e:
    print( e )

Наше исключение приходит замечательно, с нужным текстом, в виде RuntimeError — стандартного исключения Python.
Бонусом мы получили то, что на стороне C++ создавая объекты boost::python::str мы их сразу конвертируем в Юникод, что очень поможет нам когда мы на стороне C++ захотим какой-нибудь аттрибут объекта Python названного кириллицей:

object my = import( "my" );
object func = my.attr( str("Функция") )
int res = extract<int>( func( x * x ) );

Теперь в MS Visual C++ не будет никаких проблем с таким кодом. Всё отлично выцепится, позовётся и вернёт всё что надо.
Ну и раз уж речь зашла о вызове из C++ кода на Python, стоит упомянуть о том, как ловить оттуда исключения.
Все исключения из Python на уровне C++ будут ловиться типом error_already_set& всё из того же boost::python. Выцепить текст, тип и стэк исключения не представляется сложным и подробно описано вот здесь: wiki.python.org/moin/boost.python/EmbeddingPython — раздел Extracting Python Exceptions. В подавляющем большинстве случаев ничего большего нежели забрать текст исключения и не понадобится, если конечно вы не придумали своей специфической логики исключений. Но в этом случае вам лучше написать свой транслятор исключений, а это уже совсем другая история…

Итого

Мы подружили родной код MS Visual C++ с обычным кодом Python с помощью небольшого патча Boost.Python, фактически не меняя код, просто подменив в нескольких местах вызов одних функций API питона на другие, выполняющие дополнительную перекодировку. Поскольку всё сделано через API самого Python, он сам позаботится о памяти выделяемой для объектов, никаких std::string и прочих ужасов обращения к Heap через замечательные мьютексы, которые Microsoft так старательно заложила в механизм new своей стандартной библиотеки. Нет! Ничего такого! Всё за нас сделает Python, нам лишь надо было ему немного помочь.
Простые смертные всё так же могут писать код в Visual Studio не задумываясь о кодировках. Возможно даже и не зная о них. В принципе узкому специалисту в области той же транспортной части (протоколы, пакеты данных и т. п.) знать об этом не так уж и обязательно.
Особо пытливые могут замерить потери на перекодировке, они разумеется есть. По моим замерам, они настолько незначительные, что однажды переписав код очень медленной генерации веб-страницы с C++ на один join+format в Python ускорил его почти на 10%. Это с учётом перекодировки с вышеприведёнными правками. Соответственно можете представить незначительность подобных потерь, если в коде на C++ просто собиралась достаточно большая строка (даже с предварительным reserve).
По стабильности, уже полгода минимум как оболочка построенная на данных изменениях благополучно крутится на рабочих сайтах (правда версии Boost намного старше текущей). На сегодняшний день всё перекодируется стабильно, нареканий не вызывает, и не вызывало.

Обещанный архив с изменениями

Здесь собраны отчёты и патчи по изменениям в файлах библиотеки Boost.Python:
www.2shared.com/file/NFvkxMzL/habr_py3_cxx.html

Также прилагается бонусом маленький архив с тестовым проектом (собран под x64):
www.2shared.com/file/FRboyHQv/pywrap.html

Ссылки на полезное

Ссылка на документацию Python 3. Раздел Си-API перекодировки из кодовой страницы файловой системы и обратно:
docs.python.org/3/c-api/unicode.html?highlight=pyunicode#file-system-encoding

Ссылка на документацию Boost.Python:
www.boost.org/doc/libs/1_52_0/libs/python/doc

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как попасть в корзину windows 10
  • Версия этой программы несовместима с используемой версией windows что делать
  • Asus x555l bluetooth driver windows 10
  • Sata drivers for windows 2000
  • Как в windows открыть linux диск в windows