2009年4月29日星期三

VC2008链接MSVCRT.DLL不完全折腾

Ruby在Windows上有一个很讨厌的问题,其动态链接库文件名中包含了CRT库的文件名。例如用VC6编译的Ruby 1.8.6库名为msvcrt-ruby18.dll,而VC2008编译的则叫msvcr90-ruby18.dll。网上能找到的Ruby C库几乎都是用VC6编译的,如果你用的Ruby不是VC6编译的那就没法用。

造成这个问题的主要原因是不同版本VC的CRT库不兼容,ERRNO、alloc、free等在不同版本的CRT中混用会出问题。虽然这些问题似乎都可以绕过去,可Ruby社区的大牛小牛都不太喜欢搭理Windows的样子。总之现状就是,要么老实用VC6,要么所有东西都自己编译一遍。

我为啥会对这个问题有兴趣?因为新版本的VC编译出来的东西通常情况下会快一点,例如Ruby 1.9.1中的dl模块,VC2008编译出来的代码比VC6编译的至少要快50%。

几天前在网上翻到一个法子,说是链接Windows Driver Kit中包含的msvcrt.lib即可使用旧版CRT,于是忍不住试了试。结果,还是有问题。

尝试用VC2008和WDK编译一个Hello World,的确是依赖msvcrt.dll。可是在编译Ruby 1.9.1的时候,其他错误都可以通过修改代码解决,但最后却卡在environ上,“无法解析的外部符号 __imp___environ”。

写段测试代码:

#include <stdio.h>
#include "stdlib.h"

int main( int argc, char *argv[] ) {
  char **rb_origenviron = environ;
  printf( "Hello World!\n" );
  printf("%s\n", rb_origenviron[0]);
  
  return 0;
}

cl /MD t.c,问题依旧。

折腾失败……

2009年4月9日星期四

Unicode In Ruby 1.9

Ruby 1.9终于抛弃了丑陋的jcode,提供一定程度上的Unicode支持。只不过我总觉得有点儿不对劲,Ruby当前采用的处理方式,很可能会带来一些新的问题。

String有了encoding属性,部分方法的处理单位也由字节改为字符。源码文件的默认编码为US-ASCII,如果在代码中写了中文,就必须指定encoding。如果不想写,也可以用BOM。

# encoding: utf-8

s = '幺贰和叁'
puts s.encoding     # => UTF-8
puts s.length     # => 4
puts s.bytesize     # => 12

不同encoding字符串之间可以直接比较,也就是说从今以后比较字符串都要考虑编码问题,搞不好会有很多Bug因此而产生。

# encoding: utf-8

s = '位'.encode('gbk')
t = 'λ'
puts s.bytes.to_a == t.bytes.to_a # => true
puts s == t # => false
puts s == t.force_encoding('gbk') # => true

正则表达式方面更乱,encoding不同“=~”一定会报错,但match方法却不一定。

# encoding: utf-8

s = 'abc'.encode('gbk')
p s.match(/b/) # => #<MatchData "b">

s =~ /b/ # => incompatible encoding regexp match (UTF-8 regexp with GBK string) (Encoding::CompatibilityError)

s = '位'.encode('gbk')
p s.match('λ') # => incompatible encoding regexp match (UTF-8 regexp with GBK string) (Encoding::CompatibilityError)

IO方面也添加了encoding支持,不过还不支持BOM,所以读取带BOM的文件得多一个步骤。

File.open('utf8_with_bom.txt', 'r:utf-8') do |f|
  f.ungetc c unless (c = f.getc)=="\uFEFF"
  # ...
end

Python 3.0中一个很大的变化就是区分了String和Byte,所有String都是Unicode,说不定有一天Ruby也会走Python的老路,希望这只是我乌鸦嘴吧。

2009年4月7日星期二

Ruby/GD2

已更新,新内容参见:RGD - libgd binding for Ruby

Ruby/GD2是一个GD library的Ruby binding,支持GD2、Ruby 1.9,可运行于MSWin32平台。

本扩展很多代码都源自Ruby/GD v0.8.0,但由于API变化比较大,且不打算支持GD2之前的版本,故改名Ruby/GD2发布。由于我没有查到Ruby/GD采用的是何种许可证,如果将本扩展用于商业用途,请自行联系Ruby/GD当前维护者Alain Hoang,以确定没有法律问题。(真有人去问,也请告诉我一声。)

如果跟我一样在Windows下用Ruby 1.9.1,压缩包中有编译好的文件可以直接用。将bgd.dll放到Ruby的bin下,GD2.so放到site_ruby\1.9.1\i386-msvcrt下即可。我不确定是否可以在Ruby 1.8下编译,应该没有问题。不过用在Linux或OSX下,可能需要修改一下函数指针的定义。

文档暂时没有,RDoc感觉有点不好用且不会用。不过除了Ruby/GD的原始文档外,也可以参考CPAN上的GD模块,尽可能与该模块保持API兼容是我的初衷之一。

我自己肯定用不到GD库的这么多功能,错漏在所难免,有任何意见、建议、补丁,欢迎留言。

最后是一句废话:拿这个扩展去打水印的话,麻烦请考虑一下你是否有修改他人图片的权利,以及水印本身是否美观,谢谢。

require 'GD2'

def copy_rect(tname, fname, offx, offy, width, height, oname)
  timg = GD2::Image.new(tname)
  fimg = GD2::Image.new(fname)
  
  w, h = timg.bounds
  
  timg.copy(fimg, w-offx, h-offy, w-offx, h-offy, width, height)
  
  timg.jpeg(oname, 80)
end

2009年4月2日星期四

Move Ruby/GD To Ruby 1.9.1

If you are looking for Ruby extension to use GD library, plese check here.

因为实在无法忍受JRuby调用JAVA API处理Jpeg的龟速,所以花了点时间改了一下Ruby/GD的代码,使其能运行在mswin32版Ruby 1.9.1下。成果是有一点,不过还是有暂时无法解决的问题。

Ruby/GD用到的API主要是IO部分,从Ruby的头文件定义来看,变动不小。以下这几个宏,是我从RMagick等其他扩展里抄来的:

// GetReadFile doesn't exist in Ruby 1.9.0
#if !defined(GetReadFile)
#define GetReadFile(fptr) rb_io_stdio_file(fptr)
#define GetWriteFile(fptr) rb_io_stdio_file(fptr)
#endif

// rb_io_t replaces OpenFile in Ruby 1.9.0
#if defined(HAVE_RB_IO_T)
#undef OpenFile
#define OpenFile rb_io_t
#endif

// These macros are required in 1.9.1 but aren't defined prior to 1.8.6.
#if !defined(RSTRING_LEN)
#define RSTRING_LEN(s) (RSTRING((s))->len)
#endif
#if !defined(RSTRING_PTR)
#define RSTRING_PTR(s) (RSTRING((s))->ptr)
#endif

// Backport these two macros to 1.8
#if !defined(RARRAY_LEN)
#define RARRAY_LEN(a) RARRAY((a))->len
#endif
// Matz says this macro is read-only! (see http://www.ruby-forum.com/topic/146072)
#if !defined(RARRAY_PTR)
#define RARRAY_PTR(a) RARRAY((a))->ptr
#endif

变化很大,而且还没解决的问题在rb_io_t结构体上。Ruby 1.8中,该结构体包括两个*File指针,虽然不清楚区别是什么,反正是可写的。而Ruby 1.9中rb_io_t增加了很多内容,*File指针变为一个,而且不可写。

static VALUE
img_from_jpeg(VALUE img, VALUE out, VALUE quality)
{
    gdImagePtr im;
    OpenFile *fptr;
    FILE *f;

    Data_Get_Struct(img, gdImage, im);
    Check_Type(out, T_FILE);
    rb_io_binmode(out);
    GetOpenFile(out, fptr);
    rb_io_check_writable(fptr);
    f = GetWriteFile(fptr);
    gdImageJpeg(im, f, FIX2INT(quality));
    return img;
}

以上函数就这么看似乎没什么不对,可就是会出错。

另外,我还发现Ruby/GD的API设计也怪怪的,有那么点不合理。比如载入Jpeg文件,Ruby/GD有两个方法:GD::Image.newFromJpeg和GD::Image.new_from_jpeg。这两个并非同名函数,前者接受的参数是File对象,后者接受文件路径。再比如copy方法,其定义为:srcimg.copy(destimg, dx, dy, sx, sy, w, h)。调用该方法后,有变化的是作为参数的destimg,而非调用者的srcimg,这似乎与平常的习惯相反。

改到最后,虽然只有newFromXXX系列函数不能用,可怎么着都觉得有点不舒服。最后还是以Ruby/GD为蓝本,改写了一个扩展出来,有兴趣的话参见这里。至于我改的Ruby/GD就不提供下载了,实在需要的话,留言吧。