【问题标题】:Reference a GNU C (POSIX) DLL built in GCC against Cygwin, from C#/NET从 C#/NET 引用 GCC 中针对 Cygwin 构建的 GNU C (POSIX) DLL
【发布时间】:2011-02-12 05:16:19
【问题描述】:

这就是我想要的:我有一个为 POSIX 编写的庞大的遗留 C/C++ 代码库,包括一些非常 POSIX 特定的东西,比如 pthreads。这可以在 Cygwin/GCC 上编译,并在 Windows 下使用 Cygwin DLL 作为可执行文件运行。

我想做的是将代码库本身构建到一个 Windows DLL 中,然后我可以从 C# 中引用它并围绕它编写一个包装器以编程方式访问它的某些部分。

我已经在http://www.cygwin.com/cygwin-ug-net/dll.html 使用非常简单的“hello world”示例尝试了这种方法,但它似乎不起作用。

#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 返回 null。它可以找到DLL(helloworld.dll),就像它无法加载它或找不到导出的函数一样。

我确信,如果我得到这个基本案例,我可以通过这种方式引用我的代码库的其余部分。任何建议或指示,或者有人知道我想要的是否可能?谢谢。

编辑:用 Dependency Walker(很棒的工具,谢谢)检查了我的 DLL,它似乎正确地导出了函数。问题:我应该将它作为函数名 Dependency Walker 似乎找到 (_Z5hellov) 来引用吗?

Edit2:只是为了告诉你我已经尝试过了,直接链接到相对或绝对路径的 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 编译器标志进行构建,它在没有 cygwin dll 的情况下构建 dll,基本上就像在 MingW 中构建它一样.以这种方式构建它让我的 hello world 示例工作!但是,MingW 并没有 cygwin 在安装程序中拥有的所有库,因此如果您的 POSIX 代码依赖于这些库(我的有堆),您就不能这样做。如果您的 POSIX 代码没有这些依赖项,为什么不从一开始就为 Win32 构建。因此,除非您想花时间正确设置 MingW,否则这并没有多大帮助。

另一种选择是使用 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();
    }

感谢所有回答的人~~

【问题讨论】:

  • Dependency Walker (dependencywalker.com) 工具可用于确保 DLL 可加载并查看它们的依赖关系。
  • 您确定虽然 helloworld.dll 在路径中并且可以找到,但 cyg*.dll 文件也在您运行 c# 程序集的位置的路径中。如果您使用的是 Visual Studio,请进入项目属性并确保将所有必需的路径放入“环境变量”属性中。其他设置路径的方法见stackoverflow.com/questions/428085/…
  • 我没有低估你为什么要在所有实验中从 C# 调用你的 cygwin DLL。与使用非托管 C++ 相比,您在实用方面有哪些优势?您需要已经加载了 POSIX 子系统。为什么需要在同一个进程中加载​​ .NET 运行时?可以在 Win32 进程和用 C# 编写的托管进程之间进行通信,但它们必须不能共享相同的地址空间,就像您现在尝试做的那样。
  • 尝试查看系统或应用程序事件日志,看看在您看到该错误消息时是否有任何相关消息到达。

标签: c# dll gcc cygwin posix


【解决方案1】:

标准的 MS C 编译器支持大多数 POSIX 接口,包括 pthread。 有时作为单独的实现,但通常作为将 POSIX 语法转换为 Windows 库调用的宏。

如果您的 C 代码没有太多“gnuisms”,您应该能够使用标准的 Visual C 编译器对其进行编译。

【讨论】:

  • POSIX 子系统不再真正得到维护,存在一些差异可能会使移植 POSIX 繁重的代码具有挑战性。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    您应该首先尝试让您的简单 hello world 示例运行。如果您甚至不正确地使用为 POSIX 编写的庞大的遗留 C/C++ 代码库,则尝试使用取决于 Cygwin 的大型遗留 C/C++ 代码库没有多大意义。请注意,如果您要在其中链接 Cygwin,则必须对您的库进行 GPL 许可。

    为此,请查看文档(例如,如果您使用 Cdecl(您目前不这样做),则需要在您的 hello world 示例中明确指定): Consuming Unmanaged DLL Functions

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

    直接使用 P/Invoke 到您的库。 PInvoking 进入像 LoadLibrary 这样的 Win32 库然后调用你的库是没有意义的。这只是为错误增加了另一层,并没有为您带来任何好处。

    确保架构正确(.Net 应用程序在 64 位机器上默认运行 64 位)。因此,请确保您的 dll 匹配/支持或将 .Net 限制为 32 位。

    如果可行,请尝试让您的其他库正常工作。 (如果您希望使用混合两种完全不同的线程模型的函数,那么您必须有一些运气)

    【讨论】:

    • 谢谢,这就是我想要做的。如果我能让这个非常基本的示例正常工作,那么困难的部分就结束了。我只是在直接 p/invoking 失败后才尝试 loadlibrary 方法,希望它会给我一个更具描述性的错误。
    • 那么接下来的两个步骤可能是将cygwin初始化添加到你的hello world c库中,然后将丢失的数据添加到hello world c#代码中的DllImport属性中。
    【解决方案4】:

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

    但是,你还没有走到那一步。 LoadLibrary() 调用失败,出现 Windows 错误 998、ERROR_NOACCESS、“对内存位置的无效访问”。这通常发生在您依赖具有 AccessViolation 硬件异常的炸弹的某个 DLL 中的 DllMain() 入口点时。这应该在调试器中可见(Visual Studio 中的输出窗口),您应该看到“第一次机会异常”,异常代码为 0xc0000005。

    这可能很难诊断,您有几个候选 DLL,其调试符号可能与您使用的调试器不匹配。尝试通过用 C 语言编写一个调用 LoadLibrary 的小型测试程序来隔离这一点。将调试器配置为在第一次机会异常时停止。在 Visual Studio 中,您可以使用 Debug + Exceptions, Throw 复选框来执行此操作。祝你好运!

    【讨论】:

      【解决方案5】:

      您遇到的主要问题是以下问题。在您可以使用 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 函数或在全局 PATH 环境变量中轻松包含 C:\cygwin\bin(在计算机上单击鼠标右键,选择属性,然后选择“高级系统设置” ", "环境变量...",然后选择系统变量 PATH 并附加 ";C:\cygwin\bin")。

      接下来如果你编译你的DLL,你最好在编译时使用DEF文件来定义DLL的BASE地址,并使你导出的所有函数名更清晰可读(见http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/win32.html

      如果您安装了 Visual Studio,您可以使用 dumpbin.exe mydll.dll /exports 验证结果。 (不要忘记从“Visual Studio 命令提示符(2010)”启动命令提示符以设置所有 Visual Studio)。

      更新:因为你没有写成功我认为存在一些问题。在 Win32/Win64 世界(非托管世界)中它可以工作。我发布的代码我已经测试过了。在 .NET 中加载 CygWin DLL 可能会出现一些问题。在http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw 中可以读到“确保堆栈底部有 4K 的暂存空间”。这个要求在 .NET 中可能是错误的。堆栈是线程的一部分,而不是进程。因此,您可以尝试在新的 .NET 线程中使用 CygWin DLL。由于 .NET 2.0 可以定义线程的最大堆栈大小。另一种方法是尝试理解http://cygwin.com/cgi-bin/cvsweb.cgi/~checkout~/src/winsup/cygwin/how-cygtls-works.txt?rev=1.1&content-type=text/plain&cvsroot=srchttp://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,以了解 MinGW DLL 可以像 Windows DLL 一样从 C# 调用。
      2. 在非托管进程或非托管线程中使用 CygWin DLL。这是 CygWin 文档中描述的一种标准方式,它可以工作(参见我第一篇文章中的示例)。

      附:如果您在其中一种或最后选择的另一种方式上取得了成功,请在您的问题文本中写下简短的内容。独立于声誉和赏金对我来说很有趣。

      【讨论】:

      • 您好,奥列格,感谢您的帮助。实际上我还没有时间尝试任何其他方法,但我会用我的结果更新这个线程。查看我编辑的dumpbin.exe输出答案,基地址似乎没问题,函数名也在那里。感谢您的建议,我将在接下来的几天内尝试。
      • 我遇到了同样的问题(仅 POSIX 库 + P/Invoke)。请注意,4K 的空闲堆栈空间应该在堆栈的底部,即朝向高地址。一旦一些堆栈帧被推入堆栈,这个要求就不可能满足。感谢您链接到的代码,我创建了一个应用程序,该应用程序使用适当的堆栈加载 cygwin1.dll,然后托管 CLR。但是,由于其他限制,即使这样,P/Invoked dll 也无法做任何有用的事情(多个 C 运行时在同一进程中并不健康)。给任何想尝试的人的注意事项:不要,这是没有意义的。
      猜你喜欢
      • 1970-01-01
      • 2018-12-24
      • 1970-01-01
      • 1970-01-01
      • 2011-10-06
      • 2020-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多