代码之家  ›  专栏  ›  技术社区  ›  Dale

从c/net引用gcc中针对cygwin构建的gnuc(posix)dll

  •  12
  • Dale  · 技术社区  · 14 年前

    这就是我想要的:我为POSIX编写了一个巨大的C/C++代码库,包括一些非常特殊的POSIX,比如pPults。这可以在cygwin/gcc上编译,并使用cygwin dll在windows下作为可执行文件运行。

    我想做的是将代码库本身构建成一个windows dll,然后我可以从c中引用它,并在它周围编写一个包装器,以编程方式访问它的某些部分。

    我用了一个非常简单的“hello world”示例在 http://www.cygwin.com/cygwin-ug-net/dll.html 似乎也没用。

    #include <stdio.h>
    extern "C" __declspec(dllexport) int hello();
    
    int hello()
    {
      printf ("Hello World!\n");
     return 42;
    }
    

    我相信我应该能够引用一个用上述代码在c中构建的dll,比如:

    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);
    
    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    
    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);
    
    
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int hello();
    
    static void Main(string[] args)
    {
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "helloworld.dll");
        IntPtr pDll = LoadLibrary(path);
        IntPtr pAddressOfFunctionToCall = GetProcAddress(pDll, "hello");
    
        hello hello = (hello)Marshal.GetDelegateForFunctionPointer(
                                                                    pAddressOfFunctionToCall,
                                                                    typeof(hello));
    
        int theResult = hello();
        Console.WriteLine(theResult.ToString());
        bool result = FreeLibrary(pDll);
        Console.ReadKey();
    }
    

    但这种方法似乎行不通。 loadlibrary返回空值 . 它可以找到dll(helloworld.dll),就像它无法加载它或找到导出的函数一样。

    我确信,如果我让这个基本的情况下,我可以参考我的代码库的其他方式。有什么建议或建议,或者有人知道我想要的是不是可能的?谢谢。

    编辑: 用依赖性walker(非常好的工具,谢谢)检查了我的dll,它似乎正确地导出了函数。问:我是否应该引用它作为函数名依赖性walker似乎找到的函数名(z5hellov)? Dependency Walker Output http://i44.tinypic.com/1yagqv.png

    编辑2: 为了向您展示我已经尝试过了,直接以相对或绝对路径链接到dll(即不使用loadlibrary):

        [DllImport(@"C:\.....\helloworld.dll")]
        public static extern int hello();
    
    
        static void Main(string[] args)
        {
            int theResult = hello();
            Console.WriteLine(theResult.ToString());
            Console.ReadKey();
        }
    

    失败的原因是: “无法加载dll'c:……\helloworld.dll':对内存位置的访问无效。(hresult的异常:0x800703e6)

    *****编辑3:***** Oleg建议在我的dll上运行dumpbin.exe,这是输出:

    文件helloworld.dll的转储

    文件类型:dll

    部分包含以下内容 HelloWorld.dll的导出

    00000000 characteristics
    4BD5037F time date stamp Mon Apr 26 15:07:43 2010
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names
    
    ordinal hint RVA      name
    
          1    0 000010F0 hello
    

    总结

        1000 .bss
        1000 .data
        1000 .debug_abbrev
        1000 .debug_info
        1000 .debug_line
        1000 .debug_pubnames
        1000 .edata
        1000 .eh_frame
        1000 .idata
        1000 .reloc
        1000 .text
    





    编辑4 谢谢大家的帮助,我设法让它工作了。奥列格的回答给了我所需要的信息来找出我做错了什么。

    有两种方法可以做到这一点。 一种是使用gcc-mno cygwin编译器标志构建,该标志构建的dll没有cygwin dll,基本上就像是在mingw中构建的一样。以这种方式构建它让我的hello world示例发挥了作用!但是,mingw没有cygwin在安装程序中拥有的所有库,因此如果posix代码依赖于这些库(我的库有很多),就不能这样做。如果您的posix代码没有这些依赖项,为什么不从头为win32构建呢?所以如果你不想花时间把明威安排好,那就没什么用了。

    另一个选项是使用cygwin dll构建。cygwin dll需要调用初始化函数init()才能使用。这就是为什么我的代码以前不起作用的原因。下面的代码加载并运行我的hello world示例。

        //[DllImport(@"hello.dll", EntryPoint = "#1",SetLastError = true)]
        //static extern int helloworld(); //don't do this! cygwin needs to be init first
    
        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    
        [DllImport("kernel32", SetLastError = true)]
        static extern IntPtr LoadLibrary(string lpFileName);
    
    
        public delegate int MyFunction();
    
        static void Main(string[] args)
        {
            //load cygwin dll
            IntPtr pcygwin = LoadLibrary("cygwin1.dll");
            IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
            Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
            init(); 
    
            IntPtr phello = LoadLibrary("hello.dll");
            IntPtr pfn = GetProcAddress(phello, "helloworld");
            MyFunction helloworld = (MyFunction)Marshal.GetDelegateForFunctionPointer(pfn, typeof(MyFunction));
    
            Console.WriteLine(helloworld());
            Console.ReadKey();
        }
    

    感谢所有回答的人~~

    5 回复  |  直到 14 年前
        1
  •  9
  •   Oleg    14 年前

    你的主要问题是。在你可以使用你的 helloworld.dll 必须初始化cygwin环境(请参见 http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw )因此,在本机C++中的以下代码将工作:

    #include <windows.h>
    
    typedef int (*PFN_HELLO)();
    typedef void (*PFN_CYGWIN_DLL_INIT)();
    
    int main()
    {
        PFN_HELLO fnHello;
        HMODULE hLib, h = LoadLibrary(TEXT("cygwin1.dll")); 
        PFN_CYGWIN_DLL_INIT init = (PFN_CYGWIN_DLL_INIT) GetProcAddress(h,"cygwin_dll_init");
        init(); 
    
        hLib = LoadLibrary (TEXT("C:\\cygwin\\home\\Oleg\\mydll.dll"));
        fnHello = (PFN_HELLO) GetProcAddress (hLib, "hello");
        return fnHello();
    }
    

    当然是通往 CygWiN1.DLL 必须找到。您可以将c:\cygwin\bin设置为当前目录,使用 SetDllDirectory 函数或Easy在全局路径环境变量中包含C:\cygwin\bin(在计算机上单击鼠标右键,选择“属性”,然后选择“高级系统设置”、“环境变量…”,然后选择“系统变量路径”,并将其附加到“;C:\cygwin\bin”)。

    接下来,如果编译dll,最好在编译期间使用def file定义dll的基址,并使导出的所有函数名更清晰可读(请参见 http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/win32.html )

    你可以用 dumpbin.exe mydll.dll /exports ,如果已安装Visual Studio。(不要忘记从“visual studio命令提示符(2010)”启动command promt以设置所有visualstudio)。

    更新的 :因为你没有写成功,我认为存在一些问题。在win32/win64 world(非托管世界)中,它起作用。我发布的代码我已经测试过了。在.net中加载cygwin dll可能会有一些问题。在 http://cygwin.com/faq/faq.programming.html\faq.programming.msvs-mingw 一个人可以阅读” 确保堆栈底部有4K的划痕空间 “。在.NET中,此要求可能是错误的。堆栈是线程的一部分,而不是进程。因此您可以尝试在新的.NET线程中使用cygwin dll。自.NET2.0以来,可以定义线程的最大堆栈大小。另一种方法是试图理解 http://cygwin.com/cgi-bin/cvsweb.cgi/~checkout~/src/winsup/cygwin/how-cygtls-works.txt?rev=1.1&content-type=text/plain&cvsroot=src 以及中描述的代码 http://old.nabble.com/Cygwin-dll-from-C--Application-td18616035.html#a18616996 . 但真正有趣的是,我找到了两种不需要任何技巧的方法:

    1. 使用mingw工具而不是cygwin工具编译dll。mingw生成的代码与windows兼容得多。我自己没有使用cygwin或mingw,所以我不确定您是否能够编译mingw中使用posix函数的所有现有代码。如果有可能的话,这种方法可以取得更大的成功。你可以看看 http://www.adp-gmbh.ch/csharp/call_dll.html 例如,要知道,可以从c调用mingw dll,这与windows dll完全一样。
    2. 在非托管进程或非托管线程内部使用cygwin dll。这是cygwin文档中描述的一种标准方法,它可以正常工作(参见我第一篇文章中的示例)。

    如果你在这方面或在最后选择的另一方面取得了成功,请在你的问题文本中简短地写下。对我来说,独立于名誉和赏金是很有趣的。

        2
  •  2
  •   Foxfire    14 年前

    您应该首先尝试运行简单的hello world示例。在CygWin上,如果你甚至没有得到前一个权限,那么尝试为POSIX编写的一个巨大的遗留C/C++代码库没有多大意义。请注意,如果你在链接cygwin,你必须获得gpl许可你的库。

    为此,请查看文档(例如,如果您使用的是cdecl(当前不做的事情),则需要在hello world示例中显式指定): Consuming Unmanaged DLL Functions

    在本机端,必须初始化cygwin(请参阅winsup/cygwin/how cygtls works.txt)

    直接对库使用p/invoke。用pinvoking进入像loadlibrary这样的win32库来调用库是没有意义的。这只是为错误添加了另一层,却一无所获。

    确保您的架构正确无误(网络应用程序在64位计算机上默认运行64位)。因此,请确保您的dll匹配/支持它,或者将.net限制为32位。

    如果可以的话,试着让你的另一个图书馆开始工作。(如果您希望使用混合两种完全不同的线程模型的函数,那么您将不得不有一些运气)

        3
  •  1
  •   Michael Aaron Safyan    14 年前

    您应该能够引用针对cygwin构建的dll,而无需创建新的dll。唯一的要求是,您需要确保“cygwin.dll”和试图加载的dll都位于适当的路径中。你可能需要使用 SetDllDirectory 在调用“loadlibrary”之前。

        4
  •  1
  •   Hans Passant    14 年前

    编写的代码不起作用,导出的函数名由C++编译器修饰,不再类似于“hello”。您通常会声明函数extern“c”来抑制装饰。

    但是,你还没走那么远。loadlibrary()调用失败,出现Windows错误998,error\u noaccess,“对内存位置的访问无效”。当其中一个dll中的dllmain()entrypoint依赖于具有accessviolation硬件异常的bombs时,通常会发生这种情况。这应该在调试器(visual studio的输出窗口)中可见,您应该会看到一个异常代码为0xc000005的“首次机会异常”。

    这可能会使诊断变得不愉快,您有几个候选dll,它们的调试符号可能与您使用的调试器不匹配。尝试用C编写一个调用loadlibrary的小测试程序来隔离这个问题。将调试器配置为在第一次出现异常时停止。在visual studio中,您可以使用debug+exceptions,thrown复选框来执行此操作。祝你好运!

        5
  •  1
  •   abatishchev Karl Johan    14 年前

    标准的ms c编译器支持大多数posix接口,包括pthreads。 有时作为单独的实现,但通常作为宏将posix语法转换为windows库调用。

    如果你的c代码没有太多的“gnuism”,你应该能够使用标准的visualc编译器编译它。