views:

159

answers:

3

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!

+1  A: 

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.

Oleg
We have to use cygwin. We want to compile a Hadoop ZooKeeper client on Windows. The ZooKeeper C interface depends on cygwin in Windows.
Fair Dinkum Thinkum
And why does it work sometimes? Isn't that strange?
Fair Dinkum Thinkum
@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
@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
@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.
Fair Dinkum Thinkum
@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
@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.
Fair Dinkum Thinkum
@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
@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
@Oleg, thanks for your recommendations. I'll defnitely consider them if I don't find a way to load POSIX DLLs in C#.NET
Fair Dinkum Thinkum
A: 

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
@Mike, 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?
Fair Dinkum Thinkum
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
A: 

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
@Les, 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.
Fair Dinkum Thinkum
@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
@Les, I've just checked your assumption. The cygwin init part succeeds everytime. I also removed the unsafe keyword. No effect.
Fair Dinkum Thinkum