【问题标题】:Importing Ruby from C# (Windows)从 C# (Windows) 导入 Ruby
【发布时间】:2018-08-04 19:30:51
【问题描述】:

我正在尝试创建一个“嵌入”Ruby 解释器的 C# 库,使用 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 非常熟悉,我并没有问“如何”链接到它们。在实际编写 C 代码或准确理解 msvcrt-ruby***.dll、静态库等构建中的差异时,我非常绿色。

经过广泛的 Google 研究,我找不到一个链接 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”之前,我得到了“找不到指定的模块”错误。我还确保“C:\Ruby24\bin\ruby_builtin_dlls”和“C:\Ruby24\bin”都包含在 PATH 中。

我正在用头撞墙。我看不出这不起作用的原因......

【问题讨论】:

  • msvcrt-ruby240.dll 确实与 libgmp-10.dll 静态链接(您可以使用 de Depends 或 Dumpbin 工具找到它)。我在全新安装时重现了您的问题,但是如果我将 C:\Ruby24\bin\ruby_builtin_dlls 添加到 PATH 现在它可以工作,您确定您重新启动了您的环境以便考虑新的 PATH 吗? PS:我使用 sysinternals 的 Procmon 工具来查找丢失的 dll。
  • PS:另外,您应该使用 Unicode 版本的 LoadLibrary API,以防 ruby​​ 已安装在需要 Unicode 的路径中。
  • 我确实在最终剪辑中添加了 Unicode 版本,但没有很好地重新启动可能是我的问题,因为我没有这样做。最后,我发现通过 AddDllDirectory 添加路径是更清洁的解决方案,但它肯定对调试我的问题没有任何帮助,因为在更改添加到 PATH 后不重新启动。 :facepalm:

标签: c# ruby windows embed pinvoke


【解决方案1】:

好的,所以终于想通了,将在这里为可能遇到类似问题的其他人发布答案。

我最终通过“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();
    }

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 1970-01-01
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 2011-05-23
    • 2011-11-29
    相关资源
    最近更新 更多