在MinGW和VS中使用cygwin1.dll

原创
2016/08/29 16:30
阅读数 668

在将代码从Linux平台移植到Windows平台的时候, 首先想到的是Cygwin这个项目, 其核心是利用cygwin1.dll来模拟linux接口, 但是这样也带来了一个问题, 就是只能用Cygwin里面自带的GCC编译环境来编译程序, 不能在MinGW或VS中编译。所以便想到了在MinGW以及VS中使用cygwin1.dll。

对于这个问题, Cygwin官方的FAQ有相关的解释说明:How do I use cygwin1.dll with Visual Studio or Mingw-w64。从中可以看到, 对于在MinGW和VS中动态调用cygwin1.dll, 官方有具体的说明, 只要按照上面所写的去做就可以了。但是对于静态调用cygwin1.dll, 官方并没有尝试过, 但是有人在mailinglist中分享了自己的操作步骤。自己分别在MinGW和VS中尝试并改进了一下, 也成功了, 于是在这里分享一下我的具体步骤。

    1 . 在MinGW中调用cygwin1.dll

  • 准备

在MinGW中调用cygwin1.dll与在Cygwin中直接编译程序有点类似。首先, 去找一个Cygwin的源, 如http://mirrors.ustc.edu.cn/cygwin/x86/release/cygwin/, 下载最新版本的源代码, 如cygwin-2.5.2-1-src.tar.xz, 然后将其解压, 提取其中的winsup\cygwin\crt0.c文件, 其部分源代码如下:

/* crt0.c

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

/* In the following ifdef'd i386 code, the FPU precision is set to 80 bits
   and all FPU exceptions are masked.  The former is needed to make long
   doubles work correctly.  The latter causes the FPU to generate NaNs and
   Infinities instead of signals for certain operations.  */

#include "winlean.h"
#include <sys/cygwin.h>

注意到crt0.c中引用了winlean.h这个头文件, 所以在提取时要将crt0.c与winlean.h一起提取出来。这个是在编译时要加入到工程中的源文件。

接着下载最新版本的编译好的二进制文件, 如cygwin-2.5.2-1.tar.xz, 解压, 找到并提取usr\bin\cygwin1.dll文件, 这个是最后程序运行所需要的动态库文件。

最后, 到源(如http://mirrors.ustc.edu.cn/cygwin/x86/release/cygwin/cygwin-devel/)里面去下载dev库, 如cygwin-devel-2.5.2-1.tar.xz, 解压, 找到并提取其中的usr\lib\libcygwin.a文件, 这个是链接时用到的导入库。

以上步骤仅仅是准备工作, 接下来开始编译及链接源程序。

  • 编译

把crt0.c和winlean.h文件添加进项目源代码中, 然后正常编译, 生成目标文件。

  • 链接

链接libcygwin.a库, 可以用-lcygwin, 也可以直接把libcygwin.a作为参数传给编译器, 然后再额外添加如下选项:

-nostartfiles -e mainCRTStartup

如果不添加-nostartfiles选项, 就会报符号重定义的错误。选项-e mainCRTStartup指明程序入口点为mainCRTStartup, 但实际使用时, MinGW的GCC总会报一个warning说"cannot find entry symbol mainCRTStartup; defaulting to 00401000"。但是最终的程序是可以正常运行的。

    2 . 在VS中调用cygwin1.dll

  • 准备

首先, 新建一个源文件my_crt0.c, 内容如下:

#include <sys/cygwin.h>
#include <stdlib.h>

typedef int (*MainFunc) (int argc, char *argv[], char **env);

void my_crt0 (MainFunc f)
{
    cygwin_crt0(f);
}

其中, my_crt0函数名可以改为任意符合C语言的命名规范的函数名, 只是要记住后续的操作也应跟着同步修改。同时, 在代码中main函数的定义应尽量与my_crt0.c中的声明保持一致, 防止出现一些比较奇怪的问题。

然后, 在Cygwin的命令行环境下用gcc编译并链接这个文件:

gcc -shared my_crt0.c -o my_crt0.dll -Wl,--output-def,my_crt0.def

在VS的命令行环境下用上一步骤生成的def文件生成my_crt0.dll的导入库, 用于后续的链接步骤:

lib /def:my_crt0.def /out:my_crt0.lib

接着, 去newlib-cygwin的镜像上面去下载Cygwin 1.7.5版本的源代码并解压, 找到并提取winsup\cygwin\crt0.c文件, 这个文件是在编译时要加入工程中的源文件。注意, 这里选用的版本是1.7.5的。至于为什么选这个版本, 将代码比对一下就可以看出来。这是cygwin1.7.5版本的crt0.c的部分代码:

void mainCRTStartup ()
{
    #ifdef __i386__
        //...
    #endif

    cygwin_crt0 (main);
}

这是cygwin1.7.5版本后的crt0.c的部分代码:

void mainCRTStartup ()
{
    #ifdef __i386__
        //...
    #endif

    cygwin_crt0 (main);

    /* These are never actually called.  They are just here to force the inclusion
     of things like -lbinmode.  */

    cygwin_premain0 (0, NULL, NULL);
    cygwin_premain1 (0, NULL, NULL);
    cygwin_premain2 (0, NULL, NULL);
    cygwin_premain3 (0, NULL, NULL);
}

代码中主要多了cygwin_premainx()函数, 虽然从注释看出这些调用并没有什么实际用处, 但是为了避免出现一些奇怪的问题, 还是用老版本为好。

在提取crt0.c之后, 对其进行修改, 将其中的cygwin_crt0调用修改为my_crt0, 即crt0.c中调用的函数应与my_crt0.c中的函数保持一致。

然后, 去MYSY2的里面下载工具mingw-w64-i686-tools-git-x.x.x.xxxx.xxxxxxx-x-any.pkg.tar.xz, 然后解压, 找到里面的gendef.exe, 用如下命令生成cygwin1.dll的def文件:

gendef cygwin1.dll

按照刚才生成my_crt0.lib的方法去从cygwin1.def生成cygwin1.lib, 用于后续的链接步骤:

lib /def:cygwin1.def /out:cygwin1.lib

至此, 准备工作完成, 然后开始编译和链接源程序。

  • 编译

将crt0.c加入工程完成编译生成目标文件即可。

  • 链接

链接时, 把my_crt0.lib和cygwin1.lib文件加入链接器, 同时给传递如下选项以设置程序入口点:

/entry:mainCRTStartup

还有, 运行时不要忘了加上cygwin1.dll动态库。如果在编译时报错, 可以先在MinGW中编译成目标文件, 然后再在VS中完成链接。另外, 这个方法同样适用于MinGW。

从上面的整个流程来看, 比较繁琐的还是在VS中调用cygwin1.dll。而且, 源代码在VS中还不一定能够完成编译, 需要借助MinGW编译成目标文件, 然后才能继续在VS中使用。因此, 一般情况下可以优先考虑在MinGW中使用cygwin1.dll。

展开阅读全文
打赏
1
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
1
分享
返回顶部
顶部