LoginSignup
1
0

More than 1 year has passed since last update.

RDKitのC++ライブラリを使ってC#アプリを作成する[Windows編]

Last updated at Posted at 2022-09-24

前回

今回

前回はC++で直接コンソールアプリを作成しましたが、今回はラッパー関数のダイナミックライブラリを作成して別言語(C#)でコンソールアプリを作成します。

開発環境の構築

  • Visual Studio 2019
  • vcpkg
  • dotnet
    • Visual Studio Installerから「.NET デスクトップ開発」を追加してください

RDKitライブラリを使ってラッパーライブラリを作成

RDKitのスタティックライブラリを利用して、C#で利用するラッパー関数を持つダイナミックライブラリを作成します。
この際、RDKitのスタティックライブラリのビルドが終わっている必要があるので、前回の記事の「rdkitライブラリ(static)のビルド」まで進めてください。

フォルダ構成

RDKitWrapperとしてライブラリを作成します。
フォルダ構成は以下の通りです。
簡単のため、例のごとくCドライブ直下に作成します。

RDKitWrapper
├─ CMakeLists.txt
├─ rdkit_wrapper.cpp
└─ rdkit_wrapper.def

CMakeLists.txt

プロジェクト名以外前回のコンソールアプリとほぼ変わりませんが、add_executableがadd_libraryになっており、ライブラリを作成する設定になっています。

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(RDKitWrapper)
set(RDKit_DIR ${RDKit_ROOT_DIR})

add_library(RDKitWrapper SHARED rdkit_wrapper.cpp rdkit_wrapper.def)
find_package(Freetype REQUIRED)
find_package(RDKit REQUIRED)
target_include_directories(RDKitWrapper
    PRIVATE
    ${RDKit_INCLUDE_DIRS}
    C:/src/vcpkg/installed/x64-windows-static/include/cairo
)
target_link_directories(RDKitWrapper
    PRIVATE
    C:/rdkit/win64/lib/Release
)
target_link_libraries(RDKitWrapper
    ChemReactions
    DataStructs
    Depictor
    FileParsers
    GenericGroups
    GraphMol
    MolDraw2D
    MolTransforms
    RDGeneral
    RDGeometryLib
    RingDecomposerLib
    SmilesParse
    SubstructMatch
    coordgen
    ${FREETYPE_LIBRARIES}
)

rdkit_wrapper.cpp

SMILESからSVGを出力する関数です。
外部から実行する関係上、外部から渡すバッファーにsvgを書き込む形にしており、マーシャリングも行っています。
エラーも外部から読み込むことが難しいので、標準エラー出力にメッセージを出力する形にしています。
(もし略)

rdkit_wrapper.cpp
#include <iostream>
#include <string.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/MolOps.h>
#include <GraphMol/MolDraw2D/MolDraw2DSVG.h>
#include <GraphMol/Depictor/RDDepictor.h>
#include <GraphMol/FileParsers/MolFileStereochem.h>

extern "C"
{
    int DrawSVG(char *smiles, char *buf, size_t bufsize);
}

int DrawSVG(char *smiles, char *buf, size_t bufsize)
{
    try
    {
        auto mol = RDKit::SmilesToMol(smiles);
        if (mol == nullptr)
        {
            throw std::runtime_error("failed to parse smiles");
        }
        RDKit::MolOps::addHs(*mol);
        auto drawer = new RDKit::MolDraw2DSVG(300, 300, -1, -1, true);
        drawer->drawMolecule(*mol);
        std::string text = drawer->getDrawingText() + "</svg>\n";
        if (bufsize < text.length())
        {
            delete drawer;
            delete mol;
            std::string msg = "bufsize is not enough. ";
            msg += std::to_string(bufsize);
            msg += " < ";
            msg += std::to_string(text.length());
            throw std::runtime_error(msg);
        }
        for (size_t i = 0; i < text.length(); i++)
        {
            buf[i] = text[i];
        }
        buf[text.length()] = '\0';
        delete drawer;
        delete mol;
    }
    catch (std::runtime_error e)
    {
        std::cerr << "RDKitWrapper runtime_error: " << e.what() << std::endl;
        return -1;
    }
    catch (...)
    {
        std::cerr << "RDKitWrapper unknown exception" << std::endl;
        return -1;
    }
    return 0;
}


rdkit_wrapper.def

ライブラリで外部から読み込める関数を定義します。
EXPORTS下の関数が外部から読み込めます。

rdkit_wrapper.def
LIBRARY RDKitWrapper
EXPORTS
    DrawSVG

ラッパーライブラリのプロジェクトの生成

今回はダイナミックライブラリを作りたいのでtripletはx64-windows-staticではなくx64-windowsです。

cd C:/RDKitWrapper
mkdir win64 ||:
cmake -S. -Bwin64 \
	-DRDKit_ROOT_DIR=C:/rdkit/win64 \
	-DCMAKE_TOOLCHAIN_FILE=C:/src/vcpkg/scripts/buildsystems/vcpkg.cmake \
	-DVCPKG_TARGET_TRIPLET=x64-windows \
	-DVCPKG_APPLOCAL_DEPS=OFF

ラッパーライブラリのプロジェクトのビルド

開発者用のPowerShellを開いてビルドコマンドを実行します。
[Windowsメニュー]>[Visual Studio 2019]>[Developer PowerShell 2019]

cd win64
msbuild ALL_BUILD.vcxproj /p:Configuration=Release

ラッパーライブラリの構成の確認

依存性の確認

作成したライブラリに必要ない依存性が入っていないか確認します。
開発者用のPowerShellのまま以下のコマンドを実行します。

Developer Powershell for VS 2019
dumpbin.exe /dependents .\win64\Release\RDKitWrapper.dll

以下のような出力なら、Windowsでデフォルトで読み込むdllなので問題ないです。

Dump of file .\win64\Release\RDKitWrapper.dll

File Type: DLL

  Image has the following dependencies:

    KERNEL32.dll
    MSVCP140.dll
    VCRUNTIME140.dll
    VCRUNTIME140_1.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-math-l1-1-0.dll
    api-ms-win-crt-locale-l1-1-0.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-environment-l1-1-0.dll
    api-ms-win-crt-utility-l1-1-0.dll
    api-ms-win-crt-time-l1-1-0.dll
    api-ms-win-crt-convert-l1-1-0.dll

エクスポート関数の確認

作成したライブラリで必要な関数を読み込めるか確認します。
開発者用のPowerShellのまま以下のコマンドを実行します。

Developer Powershell for VS 2019
dumpbin.exe /exports .\win64\Release\RDKitWrapper.dll

以下のような出力ならDrawSVG関数が適切にエクスポートされているので問題ないです。

Dump of file .\win64\Release\RDKitWrapper.dll

File Type: DLL

  Section contains the following exports for RDKitWrapper.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00022640 DrawSVG

C#コンソールアプリの作成

前節で作成したDLLを用いてC#のコンソールアプリを作成します。
ライブラリでSMILESからSVGを作成し、C#側でPNG化して出力します。

(直接RDKitライブラリからPNGを作成しないのは、Cairoが必要になり依存関係が増えてしまうため(なぜかCairoの依存先のlibiconvがvcpkgでAndroidビルド出来ない))

例のごとくCドライブ直下にRDKitCSharpCliフォルダを作成してプロジェクトの作成を行います。
C#側のライブラリとしてSkiaSharpの追加も行います。
前節で作成したラッパーライブラリの配置もここに行います。
(後述のdotnet runで特に設定せず読み込んでくれるため)

cd c:
mkdir RDKitCSharpCli
cd RDKitCSharpCli
dotnet new console -lang C#
dotnet add package SkiaSharp.Svg
dotnet add package SkiaSharp
cp ../RDKitWrapper/win64/Release/RDKitWrapper.dll .

フォルダ構成

RDKitCSharpCli
├─ Program.cs
├─ RDKitCSharpCli.csproj
└─ RDKitWrapper.dll

Program.cs

テンプレートで出力されたProgram.csを以下のように変更します。
DLLImportを行うことでDLLライブラリ内の関数を読み出すことができます。

Program.cs
using System.Runtime.InteropServices;
using System.Text;
using SkiaSharp;

if (args.Length < 2)
{
    var exePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
    var exeName = Path.GetFileName(exePath);
    if (exeName == "")
    {
        exeName = "dotnet run";
    }
    var message = $"usage: {exeName} [SMIILES] [FILE]\n  SMILES: smiles. e.g. c1ccccc1\n  FILE: output png file name. e.g. benzen.png";
    Console.Write(message);
    return;
}

var smiles = args[0];
var filename = args[1];
Int32 bufsize = 1000000;

var stringBuilder = new StringBuilder(bufsize);
try
{
    DrawSVG(smiles, stringBuilder, bufsize);
    var tempPath = Path.Combine(Path.GetTempPath(), "temp.svg");
    File.WriteAllText(tempPath, stringBuilder.ToString());
    var svg = new SKSvg(new SKSize(300, 300));
    svg.Load(tempPath);

    var bitmap = new SKBitmap((int)svg.CanvasSize.Width, (int)svg.CanvasSize.Height);
    var canvas = new SKCanvas(bitmap);
    canvas.DrawPicture(svg.Picture);
    canvas.Flush();
    canvas.Save();

    using (var image = SKImage.FromBitmap(bitmap))
    using (var data = image.Encode(SKEncodedImageFormat.Png, 80))
    using (var stream = File.OpenWrite(filename))
    {
        data.SaveTo(stream);
    }
}
catch (Exception e)
{
    Console.WriteLine(e);
}

[DllImport("RDKitWrapper")]
extern static int DrawSVG(string smiles, [Out] StringBuilder buf, Int32 bufsize);

プロジェクト実行

dotnet runを使うとそのまま実行できます。

dotnet run c1ccccc1 benzen.png

image.png

ビルドと実行

ビルドは以下のコマンドで実行できます。
ただ、SkiaSharp.dllなどと違ってRDKitWrapper.dllの配置が行われないので、実行ファイルがある位置に手動でコピーする必要があります。

dotnet build -c Release
cp RDKitWrapper.dll bin/Release/net6.0/
bin/Release/net6.0/RDKitCSharpCli.exe c1ccccc1 benzen.png

image.png

メモ

実はRDKit側で、SWIGでJAVAのクラスライブラリと一緒にC#のクラスライブラリを作成するオプションもあったりします。
ただ、何をやっているか分かりづらいこと、細々とした修正が必要なこと、サイズが大きくなること、必要ない関数まで出力されてしまうなどの理由から、今回は必要な関数だけを持つラッパーライブラリを作成する方向性にしました。

次回

C#で実行できるようになったので次回はUnityを使ってゲーム内で実行できるかを記事にしたいと思います。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0