TransWikia.com

Подключение фортрановской LAPACK x64 в x64 проект на C#

Stack Overflow на русском Asked by serg.tortilliani on August 30, 2021

Имеется фортрановская библиотека LAPACK – пакет работы с линейной алгеброй. Конкретно в данный момент интересует её функция DGTSV (метод прогонки для трёхдиагональных матриц). Её сигнатура в фортране такая:

subroutine dgtsv (
integer N,
integer NRHS,
double precision, dimension( * ) DL,
double precision, dimension( * ) D,
double precision, dimension( * ) DU,
double precision, dimension( ldb, * ) B,
integer LDB,
integer INFO 
)

В компонентном паскале (он только 32-bit, поэтому версия LAPACK – тоже 32-bit) я подключал LAPACK таким образом (звездочка имеет смысл метки для видимости извне, аналог public в C#):

MODULE LinalgLAPACK ["liblapack.dll"];
PROCEDURE DGTSV* ["dgtsv_"] (VAR n : LAPACK_Integer; VAR nrhs : LAPACK_Integer;
      VAR dl : Double; VAR d : Double; VAR du : Double; VAR b : Double;
      VAR ldb : LAPACK_Integer; VAR info : LAPACK_Integer);

Тут есть нюанс – liblapack.dll требует libblas.dll, а еще libgfortran-3.dll, libgcc_s_dw2-1.dll и libquadmath-0.dll. Все они – 32-битные. Зависимости исследовались Dependency Walker’ом.

Поскольку в фортране ВСЕ аргументы передаются ТОЛЬКО по ссылке, поэтому аргументы-указатели на массивы заменены на ссылки на начальные элементы массивов. В основном модуле вызов выглядел так:

MODULE LinalgTest;
IMPORT LAPACK := LinalgLAPACK;
...
PROCEDURE Test*;
  VAR
    size, nrhs, info : INTEGER;
    c0, c1, c2, v : POINTER TO ARRAY OF REAL;
  BEGIN
    size := 3;
    nrhs := 1;
    NEW (c0, size);
    NEW (c1, size);
    NEW (c2, size);
    NEW (v, size);
    ...
    LAPACK.DGTSV (size, nrhs, c0[1], c1[0], c2[0], v[0], size, info);
  END Test;

Главная диагональ имеет длину size, над- и под- диагонали – соответственно size-1. Во избежание смещения индексов используется маленькая хитрость: по ссылке (а фортрановская DGTSV интерпретирует сcылку как указатель) передается элемент с индексом 1 из массива длиной size, а не элемент с индексом 0 из массива длиной size – 1. И все прекрасно работает…

Теперь пытаюсь проделать то же в C#. Используя скомпилированные в x64 .dll библиотеки – это x64 версия liblapack.dll, которая явно требует x64 версии libblas.dll, а неявно еще и libgfortran_64-3.dll, libgcc_s_seh_64-1.dll, libquadmath-0.dll, решил разобраться с простым консольным x64 приложением на C#.

using System;
using System.Runtime.InteropServices;

namespace Linalg
{
    class Program
    {
        [DllImport("liblapack.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern void dgtsv_(in int size1, in int nrhs, in double c0, in double c1, in double c2, in double v, in int size2, out int info);

        static void Main(string[] args)
        {
            int size = 3;
            int nrhs = 1;
            double[] c0 = new double[] { 0.0, 1.0, 0.0 };
            double[] c1 = new double[] { 1.0, -2.0, 1.0 };
            double[] c2 = new double[] { 0.0, 1.0, 0.0 };
            double[] v = new double[] { 1.0, 2.0, 3.0 };
            int info = 0;
            dgtsv_(in size, in nrhs, in c0[1], in c1[0], in c2[0], in v[0], in size, out info);
            Console.WriteLine("info = {0}", info);
        }
    }
}

При запуске – System.BadImageFormatException: "Была сделана попытка загрузить программу, имеющую неверный формат. (0x8007000B)"

Диспетчер конфигураций:
введите сюда описание изображения

Весь проект:
введите сюда описание изображения

Люди, добрые – что не так? )))

3 Answers

Кажется, я таки допинал C# и OpenBLAS. Малой кровью получилось с Visual Studio 2010 (32-битная версия). Пример реально работающей программы с 32-битными OpenBLAS и с референсными BLAS/LAPACK (пример честно стащил отсюда и совсем немного подправил название библиотеки):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace VAK_lapack_test
{
    class MainClass
    {
        //[DllImport("liblapack.dll", EntryPoint = "dgesv_")]
        [DllImport("libopenblas.dll", EntryPoint = "dgesv_")]
        static extern void lapack_dgesv(ref int n, ref int nrhs, double[] a, ref int lda, int[] ipvt, double[] b, ref int ldb, ref int infos);

        public static void Main(string[] args)
        {

            // 5X - 2Y = 7 
            // -X +  Y = 1
            double[] a = { 5, -1, -2, 1 };
            double[] b = { 7, 1 };

            int n = 2;
            int nrhs = 1;
            int lda = 2;
            int ldb = 2;
            int infos0 = 0;

            var a0 = a.ToArray();
            var b0 = b.ToArray();
            int[] ipvt0 = new int[n];

            lapack_dgesv(ref n, ref nrhs, a0, ref lda, ipvt0, b0, ref ldb, ref infos0);

            Console.WriteLine("Equations");
            Console.WriteLine("5X - 2Y = 7");
            Console.WriteLine("-X +  Y = 1");
            Console.WriteLine();

            Console.WriteLine("Solutions netlib lapack");
            //Console.WriteLine($"X = {b0[0]} and Y = {b0[1]}");
            Console.WriteLine("X = {0} and Y = {1}", b0[0], b0[1]);
            Console.WriteLine();

        }
    }
}

И самое главное. Это работает только с моими специально обученными библиотеками. Специальная обученность их заключается в том, что они собраны с помощью MinGW GCC 4.9 (Об особенностях этой версии можно немного почитать в официальном документе How to use OpenBLAS in Microsoft Visual Studio; но там не говорится, что это единственная возможность. Как я писал, это просто самый простой вариант в моих условиях).

Для 32-битных версий, теоретически, можно использовать динамические библиотеки от MinGW с верисями, новее чем gcc 4.7, но у меня почему-то это не вышло с более новыми версиями (имеются ввиду, версии MinGW GCC, которым я собирал библиотеку OpenBLAS). А вот для 64-битной версии, как я понял, других вариантов нет - библиотеки нужно собирать в самом Visual Studio.

Теперь, когда есть работающий пример, можно начинать искать набор библиотек, совместимых с VS (можно попробовать вот отсюда - я не пробовал, это Яндекс подсказывает; но это оригинальные библиотеки с Netlib.org, а не OpenBLAS).

Correct answer by Vladimir on August 30, 2021

Выяснился еще один нюанс - в Компонентом Паскале (среда BlackBoox Component Builder) я использовал следующие конструкции.

При описании:

PROCEDURE DGTSV* ["dgtsv_"] (VAR n : LAPACK_Integer; VAR nrhs : LAPACK_Integer;
  VAR dl : Double; VAR d : Double; VAR du : Double; VAR b : Double;
  VAR ldb : LAPACK_Integer; VAR info : LAPACK_Integer);

При использовании:

LAPACK.DGTSV (size, nrhs, c0[1], c1[0], c2[0], v[0], size, info);

Обращаю внимание на VAR dl, d, du, b : Double; (VAR - указание на использование передачи по ссылке, а не по значению, аналог в c# - ref) - это не dl, d, du, b : POINTER TO ARRAY OF Double;. И при вызове стоят не указатели на массивы c0, c1, c2, v, а указатели на элементы массивов c0[1], c1[0], c2[0], v[0]. Причём, c0[1] - это указатель на элемент массива с индексом 1 (индексация с 0). К моему удивлению, если в исходнике C# Linalg подправить описание с

static extern void lapack_dgtsv(ref int n, ref int nrhs, double[] dl, double[] d, double[] du, double[] v, ref int ldb, ref int info);

на

static extern void lapack_dgtsv(ref int n, ref int nrhs, ref double dl, ref double d, ref double du, ref double v, ref int ldb, ref int info);

то и вызов можно сделать не

lapack_dgtsv (ref size, ref nrhs, c0, c1, c2, v, ref size, ref info);

а

lapack_dgtsv(ref size, ref nrhs, ref c0[1], ref c1[0], ref c2[0], ref v[0], ref size, ref info);

т.е. тот же трюк, что работал в Компонентном Паскале можно провернуть и тут (c0[1])!!!

Итак, решаем одномерную стационарную задачу теплопроводности d2u/dx2 = 0 на трёх точках - да, это смешно, но это самый простой тест. Вторая производная аппроксимируется так: u[2] - 2*u[1] + u[0] = 0. Левое граничное условие - u[0] = 0, правое - u[1] = 1. Матрица коэффициентов и прочее:

0 [1  0  0]     [u0]   [0]
  [1 -2  1]   * [u1] = [0]
  [0  0  1] 0   [u2]   [1]

Стационарное решение - прямая x = i/(N-1), где i = 0..2, N = 3, т.е. u = (0, 0.5, 1). Нулевые элементы матрицы за пределами []-скобок - как бы продолжения над- и под-диагоналей.

Основная диагональ - (1, -2, 1). Нижняя диагональ - (1, 0), а с учетом дополнительного элемента (0, 1, 0). Аналогично - верхняя: (0, 1, 0)

Дополнительные элементы как-бы не используются, но есть. Они позволяют избежать смещения индексов: с0[i-1] вместо желаемого с0[i].

с0[i-1] := a[i,i-1];
с1[i] := a[i,i];
с2[i] := a[i,i+1];

Исходный код:

using System;
using System.Runtime.InteropServices;

namespace Linalg
{
    class Program
    {
        [DllImport("openblas.dll", EntryPoint = "dgtsv_")]
        static extern void lapack_dgtsv(ref int n, ref int nrhs, ref double dl, ref double d, ref double du, ref double v, ref int ldb, ref int info);
        static void Main(string[] args)
        {
            int size = 3;
            int nrhs = 1;
            double[] c0 = new double[] { 0.0, 1.0, 0.0 };
            double[] c1 = new double[] { 1.0, -2.0, 1.0 };
            double[] c2 = new double[] { 0.0, 1.0, 0.0 };
            double[] v = new double[] { 0.0, 0.0, 1.0 };
            int info = 0;

            Console.WriteLine("v = ({0}, {1}, {2})", v[0], v[1], v[2]);

            lapack_dgtsv(ref size, ref nrhs, ref c0[1], ref c1[0], ref c2[0], ref v[0], ref size, ref info);

            Console.WriteLine("info = {0}", info);
            Console.WriteLine("v = ({0}, {1}, {2})", v[0], v[1], v[2]);
        }
    }
}

И да - это работает!

введите сюда описание изображения

Answered by serg.tortilliani on August 30, 2021

Огроменное спасибище Владимиру https://ru.stackoverflow.com/users/248924/vladimir

Решение нашлось.

Сначала в VS2019 добавляем поддержку C++. Он в процессе потребуется и без файла vcvars64.bat не обойтись.

введите сюда описание изображения

Делаем всё по инструкции: https://github.com/xianyi/OpenBLAS/wiki/How-to-use-OpenBLAS-in-Microsoft-Visual-Studio

Качаем OpenBLAS c https://sourceforge.net/projects/openblas/files/v0.3.10/ (https://sourceforge.net/projects/openblas/files/v0.3.10/OpenBLAS%200.3.10%20version.tar.gz/download)

Качаем и устанавливаем 64-битную миниконду со страницы https://docs.conda.io/en/latest/miniconda.html (https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe)

Все операции я проводил в корне C: - у меня это SSD. Создал папку C:opt - она потребуется в дальнейшем. Распаковал архив с OpenBLAS и переименовал его в C:OpenBLAS. Запускаем консоль анаконды "Anaconda Prompt (Miniconda3)" - я её запустил с правами администратора, на всякий - во избежание возможных проблем с правами на запись. Переходим в консоли в папку с OpenBLAS'ом:

cd
cd openblas

В консоли отображается

(base) C:OpenBLAS>

Все команды вводим в консоли. Поехали!

conda update -n base conda
conda config --add channels conda-forge
conda install -y cmake flang clangdev perl libflang
conda install -y -c isuruf kitware-ninja

Затем

"C:Program Files (x86)Microsoft Visual Studio2019CommunityVCAuxiliaryBuildvcvars64.bat"

Создаем в C:OpenBLAS .bat-файл с таким содержимым (опция -DBUILD_SHARED_LIBS=ON обязательна). Я назвал его zx.bat:

set "LIB=%CONDA_PREFIX%Librarylib;%LIB%"
set "CPATH=%CONDA_PREFIX%Libraryinclude;%CPATH%"
mkdir build
cd build
cmake .. -G "Ninja" -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_Fortran_COMPILER=flang -DBUILD_WITHOUT_LAPACK=no -DNOFORTRAN=0 -DDYNAMIC_ARCH=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release

В консоли анаконды его запускаем:

zx

Переходим к самому главному и самому долгому:

cmake --build . --config Release

После окончания компиляции завершаем всё командой:

cmake --install . --prefix c:opt -v

Забегая вперед - нужно ввести еще одну, самую последнюю команду :)

conda install libflang

Заходим в C:optbin и видим свежеиспечённую openblas.dll. В 64-битном Dependency walker'е (он проживает по адресу https://www.dependencywalker.com/) можно увидеть зависимости этой dll. Нужны оказываются ещё и некие flang.dll и flangrti.dll, а сама flang.dll потребует libomp.dll. После команды conda install libflang они как раз установятся и найти их можно в папке "C:Documents and SettingsAll UsersMiniconda3Librarybin".

Теперь запускаем Visual Studio 2019 (ну у кого что), создаем консольное приложение. В диспетчере конфигурации сразу выставляем платформу x64.

Исходный код:

using System;
using System.Runtime.InteropServices;

namespace Linalg
{
    class Program
    {
        [DllImport("openblas.dll", EntryPoint = "dgtsv_")]
        static extern void lapack_dgtsv(ref int n, ref int nrhs, double[] c0, double[] c1, double[] c2, double[] v, ref int ldb, ref int info);
        static void Main(string[] args)
        {
            int size = 3;
            int nrhs = 1;
            double[] c0 = new double[] { 1.0, 0.0, 0.0 };
            double[] c1 = new double[] { 1.0, -2.0, 1.0 };
            double[] c2 = new double[] { 0.0, 0.0, 1.0 };
            double[] v = new double[] { 1.0, 2.0, 3.0 };
            int info = 0;
            lapack_dgtsv (ref size, ref nrhs, c0, c1, c2, v, ref size, ref info);

            Console.WriteLine("info = {0}", info);
        }
    }
}

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

Всем большое спасибо за участие.

Answered by serg.tortilliani on August 30, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP