(Ruby + C/C++扩展) × Windows + 调料 = LoadError

事情来源于和@bobcao3一起研究用OpenGL(GLFW)和Ruby做跨平台多媒体程序的时候。bobcao3不喜欢Ruby的OpenGL绑定而准备自己做一套。他日常使用Linux系统,所以在造扩展的时候没遇到啥问题;因为我正好在Windows上,就做了Windows相关部分。

在*nix里,ruby extconf.rb && make以后能得到一个.so文件,于是为了方便就直接在Ruby代码里用require ‘hello.so’,不过就是用法稍微奇怪一些,总归是能用的。然后Windows上理所当然、喜闻乐见地不能用了,把SO那套全部换成DLL都不行。

这段经历真是太爽了,所以还是记录下来为好。

2016-07-11

开始尝试了各种方法。下面是我关机之后的记录:

[20:16:35] bobcao3: 噗
[21:00:10] bobcao3: 去看了眼Windows被吓傻了回来
[21:00:17] bobcao3: Windows支持还是你弄吧
[21:00:25] bobcao3: 我搞不定这破Windows

2016-07-12

[11:38:13] satgo1546: 你那边so的导出有啥啊
[11:38:43] satgo1546: 我有这么几个,然而还是报错

序列        地址         名字
00000001    6FE416B0     Init_gnd
00000002    6FE481F4     _nm__rb_cObject
00000003    6FE48268     _nm__rb_eArgError
00000004    6FE48278     _nm__rb_eNoMemError

[11:39:11] satgo1546: (库是我随便写的一个,不是draw engine)
[11:49:59] bobcao3: 我是只导出Init的
[11:50:12] satgo1546: 反正下面几个也没有用
[11:50:18] satgo1546: 但是还不行
[11:50:22] bobcao3: 那个库的dll名要对应Init后面那个
[11:50:29] satgo1546: 对啊
[11:50:33] satgo1546: 我随便搞了个gnd
[11:50:48] bobcao3: 是gnd.dll咯
[11:50:39] satgo1546: 名字叫gnd.dll
[11:50:52] satgo1546: 然后require_relative ‘gnd.dll’报错
[11:51:00] bobcao3: 错误信息是啥
[11:51:13] satgo1546:

LoadError: cannot load such file -- Y:/project76/Ground/ext/gnd/gnd.dll
        from (irb):1:in `require_relative'
        from (irb):1
        from D:/Prgm/Ruby/bin/irb.cmd:19:in `<main>'

[11:51:26] bobcao3: (直接require呢
[11:51:27] satgo1546: 没啥错误信息
irb(main):002:0> require ‘./gnd.dll’

LoadError: cannot load such file -- ./gnd.dll
        from D:/Prgm/Ruby/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
        from D:/Prgm/Ruby/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
        from (irb):2
        from D:/Prgm/Ruby/bin/irb.cmd:19:in `<main>'

[11:51:47] satgo1546: 依旧如此
[11:52:40] bobcao3: 不造
[11:52:47] satgo1546: ……
[11:52:47] bobcao3: 你去查下百度呢
[11:52:56] satgo1546: 你这用法用的人少
[11:53:02] satgo1546: 并没有
[11:53:06] bobcao3: 话说你的GL教程在哪里
[11:53:13] satgo1546: 往上翻……
[11:53:17] satgo1546: https://learnopengl-cn.github.io/
[11:53:14] bobcao3: 别人怎么用的?
[11:53:27] satgo1546: 别人都是建gem,用gem extension
[11:53:33] satgo1546: 然而我这里又不能make
[11:53:43] satgo1546: 会报错
[11:53:42] bobcao3: 可是本质上是一样的啊
[11:53:46] satgo1546: 对啊
[11:53:50] satgo1546: 但反正就报错
[11:54:22] satgo1546: 我怀疑我RDK没装
[11:54:26] satgo1546: = =|||
[11:56:51] satgo1546: 我的gnd虽然是手动编译出来的
[11:57:05] satgo1546: 但是命令用的是Makefile里的
[11:57:14] satgo1546: 只是因为make不好用
[11:57:24] satgo1546: 额……
[11:57:24] bobcao3: 呃
[11:57:27] bobcao3: 垃圾Windows
[11:57:32] satgo1546: 对了
[11:57:36] bobcao3: Cygwin make都不行?
[11:57:38] satgo1546: 你手动编译一个看看
[11:57:45] satgo1546: Ruby是不用Cygwin的
[11:57:56] satgo1546: 会产生乱七八糟的事情
[11:58:10] bobcao3: 我不知道怎么手编
[11:58:09] satgo1546: 我这里是

gcc -c gnd.c -o gnd.o -I. -ID:\Prgm\Ruby\include\ruby-2.3.0\i386-mingw32 -ID:\Prgm\Ruby\include\ruby-2.3.0\ruby\backward -ID:\Prgm\Ruby\include\ruby-2.3.0 -DFD_SETSIZE=2048 -D_WIN32_WINNT=0x0501 -D__MINGW_USE_VC2005_COMPAT -D_FILE_OFFSET_BITS=64 -O3 -fno-omit-frame-pointer -fno-fast-math -Wall -Wextra
gcc -s -shared gnd.o -o gnd.dll -L. -LD:\Prgm\Ruby\lib -Wl,--enable-auto-image-base,--enable-auto-import  -lmsvcrt-ruby230 -lshell32 -lws2_32 -liphlpapi -limagehlp -lshlwapi

[11:58:14] bobcao3: <表情>
[11:58:18] satgo1546: 233333
[11:58:25] satgo1546: make有记录啊
[11:58:37] satgo1546: 你那里make可用的
[11:58:51] satgo1546: 把Makefile第五行V = 0改为1
[11:58:57] bobcao3: 等下我在火车上吃饭
[11:59:02] satgo1546: 哦
[12:35:11] bobcao3: 什么鬼
[12:35:24] bobcao3: extconf生成的就是那样?
[12:35:26] satgo1546:

相比-g选项,-rdynamic却是一个连接选项,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里,以便那些通过dlopen()或backtrace()(这一系列函数使用.dynsym表内符号)这样的函数使用。

[12:35:38] satgo1546: Google来的
[12:35:51] bobcao3: 我怀疑就是这几个dynamic
[12:36:02] bobcao3: 后面那个export dynamic你试试
[12:36:15] satgo1546: ld.exe: warning: –export-dynamic is not supported for PE targets, did you mean –export-all-symbols?
[12:36:23] satgo1546: 233
[12:36:29] bobcao3: WTF
[12:36:35] bobcao3: 那就all symbols试试
[12:36:38] satgo1546: 但是我导出都有啊
[12:36:46] bobcao3: (我记得Windows上是叫symbol)
[12:36:58] satgo1546: 没警告了
[12:37:13] bobcao3: 跑个试试
[12:37:19] satgo1546: 我觉得有希望

序列        地址         名字
00000001    6FE416B0     Init_gnd
00000002    6FE481F4     _nm__rb_cObjecy
00000003    6FE48268     _nm__rb_eArgError
00000004    6FE48278     _nm__rb_eNoMemError
00000005    6FE415A0     gnd_alloc
00000006    6FE41580     gnd_free
00000007    6FE41620     gnd_init
00000008    6FE415E0     gnd_release

[12:37:41] bobcao3: 这么做就有点暴力了
[12:37:44] satgo1546: LoadError: cannot load such file — Y:/project76/Ground/ext/gnd/gnd.dll
[12:37:49] satgo1546: 23333
[12:37:50] bobcao3: <表情>
[12:37:58] bobcao3: 这load error真隐晦
[12:38:08] bobcao3: 我这里会写是什么error的
[12:38:13] satgo1546: 没变过……
[12:38:13] bobcao3: 你那里就不行
[12:38:37] satgo1546: 真诡异
[12:38:48] satgo1546: 问题在于我找不到DLL
[12:39:05] satgo1546: 没有nokogiri.dll啥的
[12:39:09] satgo1546: 也不能参考
[12:39:03] bobcao3: 你看看有没有别的
[12:39:16] bobcao3: 叫nokogiri的东西
[12:39:48] satgo1546: 我都这么搜了

D:\Prgm\Ruby>tree/f>a.txt

[12:40:07] satgo1546: 就是没DLL
[12:40:01] bobcao3: 顺便
[12:40:05] bobcao3: 貌似
[12:40:22] bobcao3: 是gcc和MSVC的符号表不兼容导致的
[12:40:32] satgo1546: 哦?
[12:40:30] bobcao3: 如果Ruby不需要Cygwin
[12:40:36] bobcao3: 你就需要改用MSVC
[12:40:47] satgo1546: Ruby本身是用MinGW编译的
[12:41:05] bobcao3: 可是Ruby网站上说要nmake
[12:41:11] satgo1546: WTF
[12:41:11] bobcao3: 就是native make
[12:41:15] satgo1546: 哪里写了
[12:41:17] bobcao3: 你试试nmake V=1
[12:41:31] bobcao3: 百度到的也是nmake
[12:41:40] satgo1546: 那它为啥要用MinGW……
[12:41:47] satgo1546: 你有nmake就肯定有cl
[12:42:15] satgo1546: makefile(92) : fatal error U1000: 语法错误: 宏调用中缺少“)”
[12:42:23] satgo1546: 有这种事情就肯定不能用nmake
[12:42:52] satgo1546: 话说你从何处看到的nmake?
[12:43:13] bobcao3:

之后运行ruby extconf.rb,以便生成Makefile文件。
Linux下,一般直接运行make即可生成so,然后make install即可。
Windows下,必须装有Visual Studio,然后使用nmake即可。
Visual Studio要注意版本问题,直接去掉Ruby的头文件判断吧:
ruby安装目录下:D:\Ruby\ruby186\lib\ruby\1.8\i386-mswin32
config.h第二行注释掉即可:

#if _MSC_VER != 1200
//#error MSC version unmatch
#endif

然后使用nmake编译。

关于VC++ 7.0以上版本有一些特殊情况,就是manifest文件,这是发布一个.exe的可执行文件或者一个.dll的动态库所需要的。默认情况下,.manifest文件需要内嵌到你的类库中去。可使用下述批处理脚本:

ruby extconf.rb
nmake clean
nmake
mt -manifest %1.so.manifest -outputresource:%1.so;2
nmake install

[12:43:35] bobcao3: 就是说Windows一定要用MS工具链
[12:43:52] satgo1546: makefile(92) : fatal error U1000: 语法错误: 宏调用中缺少“)”
[12:43:52] satgo1546: 没卵用
[12:44:09] bobcao3: 你看看第92行
[12:44:11] bobcao3: 什么鬼
[12:44:24] satgo1546: LDSHARED = $(CC) -shared $(if $(filter-out -g -g0,$(debugflags)),,-s)
[12:44:51] satgo1546: 十分正常……
[12:45:34] bobcao3: 我这里LDSHARED没那么鬼畜
[12:45:37] satgo1546: 我这里有mingw和MSVC
[12:45:48] satgo1546: 它怎么判断用哪个啊
[12:46:25] bobcao3: 我这里LDSHARED = $(CC) -shared
[12:46:36] satgo1546: 我试试
[12:46:54] satgo1546: NMAKE : fatal error U1073: 这个SB不知道如何生成“/D/Prgm/Ruby/include/ruby-2.3.0/ruby.h”
[12:47:12] bobcao3: <表情>
[12:47:18] bobcao3: 呃
[12:49:01] bobcao3: 呃我看了下OpenGL的extconf
[12:49:11] bobcao3: OpenGL是产生一个.so的
[12:49:36] satgo1546: Windows也用so啊……?
[12:49:46] bobcao3: 不是
[12:49:50] bobcao3: 我说
[12:49:58] bobcao3: 是那个库也是跟我一个载入方法
[12:50:04] satgo1546: 我想知道怎么让extconf强制用MSVC
[12:50:16] satgo1546: 它现在用的是MinGW
[12:51:07] bobcao3: 你加一句

have_header 'Windows.h'

[12:51:34] satgo1546: 额?
[12:52:49] satgo1546: 还是不行
[12:52:54] satgo1546: 它依旧在用gcc
[12:53:04] bobcao3: 好像必须做特殊操作
[12:53:09] bobcao3: 我研究下
[12:55:15] bobcao3: 你在主Init前
[12:55:17] bobcao3: 加一个DLLEXPORT
[12:55:52] satgo1546: 这不会有用的吧
[12:56:00] satgo1546: extconf很傻的
[12:56:22] satgo1546: 另外,Google:

I ended up moving everything to the mingw toolchain, but I don’t
understand why you’re saying it’s impossible to use Visual Studio.

[12:57:53] bobcao3: 噗
[12:57:57] bobcao3: 等下
[12:58:16] bobcao3: 我在头文件里做Windows手脚
[12:58:24] satgo1546: ?
[12:59:58] bobcao3: 你在Init前面
[13:00:16] bobcao3: 加__declspec(dllexport)
[13:00:28] bobcao3: 然后正常编译(手工)
[13:00:38] satgo1546: 手工是怎么弄
[13:00:50] satgo1546: 用MSVC还是MinGW
[13:00:54] bobcao3: MinGW吧
[13:01:04] satgo1546: 不过cl怎么用我也不知道= =
[13:01:04] bobcao3: 我没看到任何关于mSVC的内容
[13:01:28] satgo1546: 编译正常
[13:01:42] bobcao3: 然后加载?
[13:01:51] satgo1546: LoadError
[13:01:55] satgo1546: 一模一样
[13:02:27] bobcao3: 靠
[13:02:32] satgo1546: 我有种这个事情已经被钦定的感觉
[13:02:35] satgo1546: <表情>
[13:02:41] bobcao3: 理论上nmake就行啊
[13:03:10] satgo1546: 诶
[13:03:11] satgo1546: 等等
[13:03:21] bobcao3: ?
[13:03:25] satgo1546: 我发毛病把dll的后缀改成了so
[13:03:29] satgo1546: 然后错误就不一样了
[13:03:32] satgo1546: 2333333
[13:03:36] bobcao3: (⊙o⊙)哦
[13:03:38] bobcao3: 是啥
[13:03:39] satgo1546: NameError: uninitialized constant Gnd
[13:03:44] bobcao3: 噗
[13:03:46] bobcao3: <表情>
[13:03:53] bobcao3: 有没有load error
[13:03:59] satgo1546: 没有!
[13:04:03] satgo1546: 好神奇了
[13:04:08] satgo1546: 我试试
[13:04:10] bobcao3: 这他娘的是哪门子黑科技
[13:04:14] bobcao3: <表情>
[13:04:15] bobcao3: <表情>
[13:04:32] satgo1546: 我定义了一个Gnd

irb(main):003:0> module Gnd
irb(main):004:1> end
=> nil

[13:04:37] satgo1546: 然后
[13:04:38] satgo1546:

irb(main):005:0> require './gnd.so'
TypeError: wrong argument type Module (expected Class)

[13:05:13] bobcao3: (⊙o⊙)哦
[13:05:19] bobcao3: 你定义的是module?
[13:05:27] satgo1546: 我觉得接下来就是我的extension写的问题了
[13:05:40] bobcao3: 所以就好了?
[13:05:43] satgo1546: 我随便搞的

__declspec(dllexport) void Init_gnd(void) {
	VALUE cGnd;

	cGnd = rb_const_get(rb_cObject, rb_intern("Gnd"));

	rb_define_alloc_func(cGnd, gnd_alloc);
	rb_define_method(cGnd, "initialize", gnd_init, 1);
	rb_define_method(cGnd, "free", gnd_release, 0);
}

[13:05:47] bobcao3: 你试试直接require ‘dll’
[13:05:51] bobcao3: 呸
[13:05:54] bobcao3: reqire ‘gnd’
[13:06:00] satgo1546: 什么鬼
[13:06:00] bobcao3: 不要那个.dll
[13:06:08] bobcao3: (Windows的确有这个毛病)
[13:06:18] satgo1546: 跟有so是一样的
[13:06:24] satgo1546: 效果一样
[13:06:34] bobcao3: 那就不改so了
[13:06:41] bobcao3: 也就是说加了.dll
[13:06:58] bobcao3: Ruby以为你要加载sys32啥的之类的
[13:07:05] bobcao3: 于是被吓坏了?
[13:07:08] satgo1546: 你知道最小的Init怎么写吗
[13:07:33] satgo1546: 我这个可能是写的问题
[13:07:24] bobcao3: 呃
[13:08:07] bobcao3:

VALUE A = rb_define_module("WTFWindoge");

[13:08:19] bobcao3: 嗯这就是最短的了大概
[13:08:09] satgo1546: 卧槽
[13:08:20] satgo1546: true了
[13:08:25] satgo1546:

Y:\project76\Ground\ext\gnd>irb
irb(main):001:0> class Gnd
irb(main):002:1> end
=> nil
irb(main):003:0> require '.gnd'
=> true
irb(main):004:0>

[13:08:32] satgo1546: TM能用了!!!
[13:08:39] bobcao3: 说明
[13:08:43] bobcao3: 就是不能加dll
[13:08:50] satgo1546: 不,要加so
[13:08:52] satgo1546: <表情>
[13:09:00] satgo1546: 在Windows上加so
[13:09:02] satgo1546: <表情>
[13:08:58] bobcao3: <表情>
[13:09:00] bobcao3: 可以
[13:09:04] bobcao3: 很好的讽刺
[13:09:13] satgo1546: WindogeAPI
[13:09:51] satgo1546: 绝对正常了

irb(main):005:0> Gnd.new(5).free
=> #<Gnd:0x335b488>

[13:10:49] bobcao3: Windows = WTFdoge
[13:10:52] bobcao3: <表情>
[13:11:17] satgo1546: 我笑死了
[13:11:24] bobcao3: 等下我上传一个Git
[13:11:35] bobcao3: 一会儿你再跑一下试试
[13:11:37] satgo1546: 我觉得应该把这段聊天记录公开……
[13:11:47] bobcao3: +1

最终的解决方案成为了windoge_make.rb

发表评论

电子邮件地址不会被公开。 必填项已用*标注