1

I try to load a simple DLL compiled with GCC in cygwin into a C#.NET application. The DLL looks like this

#ifndef __FOO_H
#define __FOO_H

#if _WIN32
  #define EXPORT extern "C" __declspec(dllexport)
#else //__GNUC__ >= 4
  #define EXPORT extern "C" __attribute__((visibility("default")))
#endif

EXPORT int bar();

#endif  // __FOO_H

The function bar() just returns 42.

I compiled and linked the DLL with

g++ -shared -o foo.dll foo.cpp

Now I want to load this super simple DLL into a C# WinForms application.

public partial class Form1 : Form
{
  [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 IntPtr Action2();

  unsafe public Form1()
  {
    InitializeComponent();

    IntPtr pcygwin = LoadLibrary("cygwin1.dll");
    IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
    Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
    init();
  }

  unsafe private void button1_Click(object sender, EventArgs e)
  {
    IntPtr foo = LoadLibrary("foo.dll");         // CRASH ... sometimes
    IntPtr barProc = GetProcAddress(foo, "bar");
    Action2 barAction = (Action2)Marshal.GetDelegateForFunctionPointer(barProc, typeof(Action2));
    IntPtr inst = barAction();
  }
}

Now the strange thing is: sometimes it works and sometimes it doesn't. When it doesn't work it crashes when it loads foo.dll. I run it in debug mode but I don't even get an exception. The debugger just stops as if I stopped it myself!

I also tried to load foo.dll in the same stack frame where I load cygwin1.dll. Same thing!

Any hints why this happens and what I can do to make it work?

Update 1: We use the latest cygwin and Visual Studio 2010.

Update 2: An assumption is that it has to with timing and garbage collection. It seems to me that the time between loading cygwin1.dll and loading foo.dll matters. The shorter the time between the two LoadLibrary calls the more likely it seems to work.

Update 3: If loading foo.dll succeeds the first time it succeeds always during a session. I can click button1 as often as I want.

Note: LoadLibrary("foo.dll") does not simply fail to load foo.dll. That'd be nice. I crashes and the debugger stops working. Not even an exception is thrown. AND it does not crash always. Sometimes it works!

Jan Deinhard
  • 19,645
  • 24
  • 81
  • 137

3 Answers3

1

Look at the "UPDATED" part of my old answer about the close problem. I recommend you to to compile your DLL with respect of MinGW tools instead of CygWin tools. If nothing is changed in the since the time the requirement "Make sure you have 4K of scratch space at the bottom of your stack" makes CygWin DLLs incompatible to .NET. I don't know how to realized the requirement in a .NET application.

Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • We have to use cygwin. We want to compile a Hadoop ZooKeeper client on Windows. The ZooKeeper C interface depends on cygwin in Windows. – Jan Deinhard Sep 28 '10 at 10:56
  • @Fair Dinkum Thinkum: Then in your case it will be easier to write an EXE instead of DLL and communicate with the EXE with respect of any known methods (to recommend one method I need to have more information from you). It seem to me be much easer as to try solve the problem with "4K of scratch space at the bottom of your stack" requirement. – Oleg Sep 28 '10 at 11:09
  • @Fair Dinkum Thinkum: I don't know how you can control the stack of .NET application. The stack is not a part of your application. It belong to the thread. The managed threads will controlled a little other as unmanaged threads. CygWin requirement to the unmanaged code running in the current thread seems me too hard for managed application. Sometimes stack can be free and your DLL call could work, but I don't know an .NET Interop attribute (option) which allow to control the stack requirement of CygWin. – Oleg Sep 28 '10 at 11:15
  • @Oleg, What information do you need? We have to connect our C# module to a ZooKeeper cluster. It needs read and write access to ZooKeeper. Actually our solution right now IS an executable. The exe writes information to files which the C# module reads. But that solution seems to be "wrong" and prone to errors to us. Plus it does not consider writting data from the C# module to ZooKeeper. We would not like to use a file for that direction too. – Jan Deinhard Sep 28 '10 at 11:21
  • @Fair Dinkum Thinkum: I am far from ZooKeeper. So it say me not much. Which IPC ways you can use inside of CygWin? Do you can use COM/DCOM? Shared Memory, Named Pipes, sockets, Window messaging? Is it easier for you to call a Web Service? You can use any IPC (inter process communications) method to send information to a .NET application and receive the response back. You should choose the most reliable and simple in the implementation communication way. – Oleg Sep 28 '10 at 11:28
  • @Oleg, I'm quite new to cygwin but since it is a POSIX environment I don't think we can use any Windows specific IPC mechanism. That'd leave files and TCP/IP which I don't like to use if I can avoid them. – Jan Deinhard Sep 28 '10 at 12:00
  • @Fair Dinkum Thinkum: The solution with sockets can be implemented in both CygWin/POSIX and .NET and the implementation could be not so complex. I recommend you to look in the direction. Another variants I wrote mostly to show that the bridge (communications ways) between POSIX and Windows/.NET are very restricted. Most of worlds speak his own language... But probably somebody will suggest you a better way... – Oleg Sep 28 '10 at 12:04
  • @Fair Dinkum Thinkum: One more small remark. If you will try the way with sockets I'll recommend you to bind socket only to the **localhost** to reduce any security problems. – Oleg Sep 28 '10 at 12:23
  • @Oleg, thanks for your recommendations. I'll defnitely consider them if I don't find a way to load POSIX DLLs in C#.NET – Jan Deinhard Sep 28 '10 at 13:32
0

You should try process monitor from msft. This is most likely being caused by a failure to load a dependent dll. Process monitor will show you what dll and why it is not loading.

Mike
  • 3,462
  • 22
  • 25
  • A missing dependency was the first thing I thought about. I already checked if there is a DLL missing. And why does it work sometimes? Shouldn't it fail always if there is a DLL missing? – Jan Deinhard Sep 28 '10 at 19:09
  • Dlls are not supposed to do any real work in their DLLMain, so a common practice is be to spawn a thread to do something. If the next dll depends on that something being done, then the load will fail. In this case, the first dll is probably doing something that is updating or modifying the path to include the cygwin dlls. If this is the case then you should be able to fix the crashes by making the path modification yourself. You would be able to tell if this is the case by using process monitor on a working and failing case and comparing the search paths used. – Mike Oct 01 '10 at 16:29
0

Try the following...

  [DllImport("kernel32", CharSet=CharSet.Unicode)]
  static extern IntPtr LoadLibrary(string lpLibFileName);

maybe even use

  [DllImport("kernel32", CharSet=CharSet.Unicode, SetLastError=true)]

and check for return values from both your calls to LoadLibrary and GetProcAddress before trying to use them.

Les
  • 10,335
  • 4
  • 40
  • 60
  • if loading foo.dll crashes there is no return value. It is the call to LoadLibrary("foo.dll") that causes the crash sometimes. Sometimes it works. – Jan Deinhard Sep 29 '10 at 04:49
  • @Fair, the idea behind my suggestion is that the first LoadLibrary could be the culprit if the PInvoke signature and data are out of whack. Furthermore, is "safe" required? Avoid safe if you can. – Les Sep 29 '10 at 12:16
  • I've just checked your assumption. The cygwin init part succeeds everytime. I also removed the unsafe keyword. No effect. – Jan Deinhard Sep 30 '10 at 08:39