从C#/ NET引用针对Cygwin的GCC内置的GNU C(POSIX)DLL

这就是我想要的:我为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  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),就像它无法加载它或找到导出的函数一样。

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

编辑:检查我的DLL与Dependency Walker(伟大的工具,谢谢),它似乎正确导出function。 问题:我应该引用它作为Dependency Walker似乎找到的函数名称(_Z5hellov)吗? Dependency Walker输出http://sofzh.miximages.com/c%23/1yagqv.png

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’:对内存位置的访问无效。(HRESULTexception: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(); } 

谢谢大家回答~~

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

 #include  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 /exportsvalidation结果。 (不要忘记从“Visual Studio命令提示符(2010)”启动命令promt以设置所有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 = 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文档中描述的标准方法,它可以工作(请参阅我的第一篇文章中的示例)。

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

您应该首先尝试让您的简单hello world样本运行。 尝试使用为POSIX编写的庞大的遗留C / C ++代码库没有太大意义,这取决于Cygwin,如果你甚至没有得到前者的权利。 请注意,如果您要连接Cygwin,则需要GPL许可您的库。

为此,请查看文档(例如,如果您正在使用Cdecl(您目前不做的事情),则需要在hello world示例中明确指定):使用非托管DLL函数

在本机方面,你必须初始化Cygwin(参见winsup / cygwin / how-cygtls-works.txt)

直接使用P / Invoke到您的库。 在PInvoking到LoadLibrary之类的Win32库中然后调用你的库是没有意义的。 这只是为错误添加了另一层,并且几乎没有任何东西。

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

如果可行则尝试让您的其他库工作。 (如果你希望使用混合两种完全不同的线程模型的函数,你将不得不运气好)

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

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

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

这可能会让您的诊断变得令人不快,您有几个候选DLL,其调试符号可能与您使用的调试器不匹配。 尝试通过在C中编写一个调用LoadLibrary的小测试程序来隔离它。 配置调试器以在第一次机会exception时停止。 在Visual Studio中,您可以使用Debug + Exceptions,Thrown复选框执行此操作。 祝你好运!

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

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