从 C# 导入 Ruby (Windows)

Importing Ruby from C# (Windows)

我正在尝试创建一个 C# 库,它 "embeds" 一个 Ruby 解释器,使用 DllImport 执行 C-Ruby 函数。

    public const string RUBY_DLL = @"msvcrt-ruby18";

    [DllImport(RUBY_DLL, CallingConvention = CallingConvention.Cdecl)]
    private static extern void ruby_init();

    // ... Everything in between...

    [DllImport(RUBY_DLL, CallingConvention = CallingConvention.Cdecl)]
    private static extern void ruby_finalize();

这工作得很好,我完全能够导入函数并与 Ruby 交互,但前提是使用 msvcrt-ruby18.dll,这显然已经过时了。我想使用 msvcrt-ruby240.dll,甚至是 msvcrt-ruby19*.dll,但我所做的每一次尝试都失败了。我创建了一个使用 LoadLibrary 和 GetProcAddress 加载函数的变体,这样我就可以使用 Ruby 的任何已安装版本,但一切都失败了。

当使用 DllImport 时,我得到 "DllNotFoundException",这似乎表明 Ruby dll 某处缺少依赖项。我确保我在 x86 下构建,并使用 x86 Ruby 库,所以这不是 BadImageFormat 问题。使用 LoadLibrary 时,我实际上可以在较新版本中调用 ruby_init 而不会出错,但是调用 rb_eval_string 会失败,除了 msvcrt-ruby18.dll。

我对使用 P/Invoke 非常熟悉,不会向他们询问 "how" 到 link。在实际编写 C 代码或准确理解 msvcrt-ruby***.dll、静态库等的构建差异时,我还很新手。

经过广泛的 Google 研究,我找不到一个 links C# 和 Ruby 使用任何比 msvcrt-ruby18.dll 更新的例子,我希望获得一些关于我能做什么以及我必须做什么的见解。如果需要的话,我不反对自己编译 Ruby,但如果需要的话,我会非常感谢任何提示,以及我必须编辑的内容等。

编辑: 这就是我正在做的。

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public class RubyHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [DllImport("kernel32", SetLastError = true)]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("kernel32")]
    public static extern bool FreeLibrary(IntPtr hModule);

    public RubyHandle(string rubyDllPath) : base(true)
    {
        SetHandle(LoadLibrary(rubyDllPath));
        if (handle == IntPtr.Zero)
            throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    public override bool IsInvalid
    {
        get => handle == IntPtr.Zero;
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    protected override bool ReleaseHandle()
    {
        return FreeLibrary(handle);
    }

    public static implicit operator IntPtr(RubyHandle rubyHandle)
    {
        return rubyHandle.DangerousGetHandle();
    }
}

并绑定函数...

    [SuppressUnmanagedCodeSecurity]
public static class Ruby
{
    public const string RUBY_DLL = @"C:\Ruby24\bin\msvcrt-ruby240.dll";

    [DllImport("kernel32", SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

    private static RubyHandle _rubyLib;

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void VoidArgs0();

    private static VoidArgs0 _ruby_init;
    private static VoidArgs0 _ruby_finalize;
    private static VoidArgs0 _ruby_show_version;
    private static VoidArgs0 _ruby_show_copyright;

    public static void Initialize(string rubyDllPath = null)
    {
        _rubyLib = new RubyHandle(rubyDllPath ?? RUBY_DLL);
        ImportFunctions();
        _ruby_init.Invoke();
    }

    private static void ImportFunctions()
    {
        _ruby_init = (VoidArgs0) ImportFunction<VoidArgs0>("ruby_init");
        _ruby_finalize = (VoidArgs0) ImportFunction<VoidArgs0>("ruby_finalize");
        _ruby_show_version = (VoidArgs0) ImportFunction<VoidArgs0>("ruby_show_version");
        _ruby_show_copyright = (VoidArgs0) ImportFunction<VoidArgs0>("ruby_show_copyright");
    }

    private static object ImportFunction<T>(string functionName) 
    {
        var ptr = GetProcAddress(_rubyLib, functionName);
        if (ptr == IntPtr.Zero)
            throw new Win32Exception(Marshal.GetLastWin32Error());
        return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
    }

    public static void Release()
    {
        _ruby_finalize.Invoke();
        _rubyLib.Dispose();
    }

    public static void ShowVersion()
    {
        _ruby_show_version.Invoke();
    }
}

错误一开始就发生了,甚至在它开始调用 "LoadLibrary" 之前,我就收到了 "Specified module was not found" 错误。我还确保 "C:\Ruby24\bin\ruby_builtin_dlls" 和 "C:\Ruby24\bin" 都包含在 PATH 中。

我在用头撞墙。我看不出为什么这不起作用...

好的,所以终于弄明白了,这里 post 可能会遇到类似问题的其他人的答案。

我最终通过 "AddDllDirectory" 为 Ruby 的依赖项手动添加了目录:

    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool AddDllDirectory(string lpPathName);

然后使用 "LoadLibraryEx" 反对 "LoadLibrary",并指定 "LOAD_LIBRARY_SEARCH_DEFAULT_DIRS" 标志。

        [DllImport("kernel32", SetLastError = true)]
    static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);

    [System.Flags]
    enum LoadLibraryFlags : uint
    {     
        DontResolveDllReferences = 0x00000001,
        LoadIgnoreCodeAuthzLevel = 0x00000010,
        LoadLibraryAsDatafile = 0x00000002,
        LoadLibraryAsDatafileExclusive = 0x00000040,
        LoadLibraryAsImageResource = 0x00000020,
        LoadLibrarySearchApplicationDir = 0x00000200,
        LoadLibrarySearchDefaultDirs = 0x00001000,
        LoadLibrarySearchDllLoadDir = 0x00000100,
        LoadLibrarySearchSystem32 = 0x00000800,
        LoadLibrarySearchUserDirs = 0x00000400,
        LoadWithAlteredSearchPath = 0x00000008
    }

然后...

    public static void Initialize(string rubyDllPath = null)
    {
        AddDllDirectory(@"C:\Ruby24\bin");
        AddDllDirectory(@"C:\Ruby24\bin\ruby_builtin_dlls");

        _rubyLib = new RubyHandle(rubyDllPath ?? RUBY_DLL);
        ImportFunctions();
        _ruby_init.Invoke();
    }

显然最终产品会动态执行此操作,但这种方式成功加载了库。