Here is what I want: I have a huge legacy C/C++ codebase written for POSIX, including some very POSIX specific stuff like pthreads. This can be compiled on Cygwin/GCC and run as an executable under Windows with the Cygwin DLL.
What I would like to do is build the codebase itself into a Windows DLL that I can then reference from C# and write a wrapper around it to access some parts of it programatically.
I have tried this approach with the very simple "hello world" example at http://www.cygwin.com/cygwin-ug-net/dll.html and it doesn't seem to work.
#include <stdio.h>
extern "C" __declspec(dllexport) int hello();
int hello()
{
printf ("Hello World!\n");
return 42;
}
I believe I should be able to reference a DLL built with the above code in C# using something like:
[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();
}
But this approach doesn't seem to work. LoadLibrary returns null. It can find the DLL (helloworld.dll), it is just like it can't load it or find the exported function.
I am sure that if I get this basic case working I can reference the rest of my codebase in this way. Any suggestions or pointers, or does anyone know if what I want is even possible? Thanks.
Edit: Examined my DLL with Dependency Walker (great tool, thanks) and it seems to export the function correctly. Question: should I be referencing it as the function name Dependency Walker seems to find (_Z5hellov)?
Edit2: Just to show you I have tried it, linking directly to the dll at relative or absolute path (i.e. not using LoadLibrary):
[DllImport(@"C:\.....\helloworld.dll")]
public static extern int hello();
static void Main(string[] args)
{
int theResult = hello();
Console.WriteLine(theResult.ToString());
Console.ReadKey();
}
This fails with:
"Unable to load DLL 'C:.....\helloworld.dll': Invalid access to memory location. (Exception from HRESULT: 0x800703E6)
*Edit 3: *
Oleg has suggested running dumpbin.exe on my dll, this is the output:
Dump of file helloworld.dll
File Type: DLL
Section contains the following
exports for 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
Summary
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
Edit 4 Thanks everyone for the help, I managed to get it working. Oleg's answer gave me the information I needed to find out what I was doing wrong.
There are 2 ways to do this. One is to build with the gcc -mno-cygwin compiler flag, which builds the dll without the cygwin dll, basically as if you had built it in MingW. Building it this way got my hello world example working! However, MingW doesn't have all the libraries that cygwin has in the installer, so if your POSIX code has dependencies on these libraries (mine had heaps) you can't do this way. And if your POSIX code didn't have those dependencies, why not just build for Win32 from the beginning. So that's not much help unless you want to spend time setting up MingW properly.
The other option is to build with the Cygwin DLL. The Cygwin DLL needs an initialization function init() to be called before it can be used. This is why my code wasn't working before. The code below loads and runs my hello world example.
//[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();
}
Thanks to everyone that answered~~