【发布时间】: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