Возможная ошибка F # Interactive PInvoke

При попытке доказать коллеге, что можно использовать классы С++ из F #, я придумал следующее доказательство концепции. Первый фрагмент кода - это код, который он предоставил для вызова, а фрагмент кода ниже - моя реализация в F #.


namespace testapp {
    struct trivial_foo {
        int bar;
        __declspec(dllexport) void set(int n) { bar = n; }
        __declspec(dllexport) int get() { return bar; }
    }
}

open System.Runtime.InteropServices

type TrivialFoo =
    struct
        val bar: int
        new(_bar: int) = { bar = _bar }
    end

[<DllImport("Win32Project2.dll", EntryPoint="[email protected][email protected]@@QAEHXZ", CallingConvention = CallingConvention.ThisCall)>]
extern int trivial_foo_get(TrivialFoo& trivial_foo)

[<DllImport("Win32Project2.dll", EntryPoint="[email protected][email protected]@@[email protected]", CallingConvention = CallingConvention.ThisCall)>]
extern void trivial_foo_set(TrivialFoo& trivial_foo, int bar)

type TrivialFoo with
    member this.Get() = trivial_foo_get(&this)
    member this.Set(bar) = trivial_foo_set(&this, bar)

При отладке в Visual Studio или запуске в качестве автономной программы это работает предсказуемо: TrivialFoo.Get возвращает значение bar и TrivialFoo.Set. Однако, если вы запускаете F # Interactive, TrivialFoo.Set не будет устанавливать это поле. Я подозреваю, что это может иметь какое-то отношение к доступу к управляемой памяти из неуправляемого кода, но это не объясняет, почему это происходит только при использовании F # Interactive. Кто-нибудь знает, что здесь происходит?

Ответ 1

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

Как PoC: F # создает MSIL, который вписывается в CLI, поэтому он может взаимодействовать с любым другим языком CLI там. Если этого недостаточно и вы хотите использовать межсетевой интерфейс, рассмотрите возможность использования COM или, как упоминалось выше, определений экспорта DLL на вашем С++. Я лично не стал бы взаимодействовать с определениями класса С++ так, как вы предлагаете здесь, есть способы упростить это.

В качестве альтернативы просто смените свой проект на С++ на проект .NET С++, и вы можете получить доступ к классам непосредственно из F #, но все еще имея силу С++.

Естественно, вы все еще можете задаться вопросом, почему пример не запускается в FSI. Вы можете увидеть подсказку ответа, выполнив следующее:

> System.IO.Directory.GetCurrentDirectory();;
val it : string = "R:\TMP"

Чтобы исправить это, у вас есть множество вариантов:

  • скопировать Win32Project2.dll в этот каталог
  • добавить любой путь, на который он находится, в PATH
  • используйте абсолютный путь
  • используйте константу времени компиляции
  • или используйте переменную среды (путь будет расширен)
  • динамически размещать dll и динамически связывать с ним (сложным)

Копирование, вероятно, является самым простым из этих решений.

Поскольку FSI означает REPL, он не может быть лучше всего подходит для таких задач, для которых требуется несколько проектов, библиотек или иначе сложные конфигурации. Вы можете проголосовать за этот запрос FSI для поддержки #package для импорта пакетов NuGet, которые могут быть использованы для облегчения таких задач.

Ответ 2

Совмещение структуры С++ в F # не обязательно является структурой. В С++ единственная разница между классами и структурами заключается в ограничениях доступа по умолчанию.

В F # структуры используются для типов значений, классы используются для ссылочных типов. Одна из проблем со значениями типов заключается в том, что они предназначены для использования в качестве неизменяемых значений, а временные копии часто создаются молча.

Проблема, которую вы наблюдаете, согласуется с этим сценарием. По какой-то причине F # interactive создает копию вашей структуры и передает ссылку на нее. Затем код С++ изменяет копию, оставляя оригинальную нетронутую.

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