Introduction
What should I do when I want to import a third-party DLL into my C# project. Maybe I want to write a DLL for myself. This tip is about to discover how to make it work even with a generic void *
returning value function.
Background
Unfortunately, I had already installed cygwin g++ compiler with the version.
g++ (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)
This happened to lead to some headscratch. I want to save this tip for next time, if there is a need for me to make a C++ DLL for some C# project.
Using the Code
The main tricks are about to use the:
extern "C" {
__declspec(dllexport)
LocalAlloc
LocalFree
long is like int
In the C++ code and...
[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);
unsafe public delegate
LoadLibrary
GetProcAddress
Marshal.GetDelegateForFunctionPointer
int is like long
...in the C# code.
That Escalated Quickly...
Start from the beginning...
I created a C# console project that will use the C++ function. I know that there will be some void *
variable passing, so I set it to Platform target x86, and checked the Allow unsafe code in the build settings of the project.
Next I have created a C++ project with the settings:
- Target extension: .dll
- Configuration Type: Dynamic Library (.dll)
- Use of MFC: Use MFC in a Shared DLL
- Character Set: Use Unicode Character Set
- Common Language Runtime Support: Common Language Runtime Support (/clr)
Name it Project1
. The code of the C++ is very simple.
extern void *GetSomething();
#include "Header.h"
extern void *GetSomething() {
long *var = new long();
*var = 2147483647l;
return var;
}
Compile ok. Now I want to reference it into my C# project. References right click -> add reference -> Solution -> Projects -> select Project1
. I set the Copy Local property to true
.
Let's start Google how to import DLL:
[DllImport("Project1.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe extern static void* GetSomething();
Done. Usage:
static void Main(string[] args)
{
unsafe
{
long* variable = (long*)GetSomething();
Console.WriteLine(*variable);
Console.ReadKey();
}
}
Done. Result:
An unhandled exception of type 'System.EntryPointNotFoundException' occurred in xy.exe
Additional information: Unable to find an entry point named 'GetSomething' in DLL 'Project1.dll'.
Google my friend. This link suggests to check the dumpbin. How to open dumpbin? I had to start a Developer Command Prompt for VSxxxx. Here, I have the command.
$dumpbin /exports Project1.dll
Microsoft (R) COFF/PE Dumper Version 12.00.40629.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file Project1.dll
File Type: DLL
Summary
1000 .data
9000 .rdata
1000 .reloc
1000 .rsrc
3000 .text
$
There is nothing in it. After research name mangling come to my mind, because how would the compiler possibly find out which classes/namespaces function I want to call? If my function is in a class, maybe it's easier to reference it, but let's do it the hard way. Well. Not such a hard way. There is a trick for unmangle the name.
#include <windows.h>
extern "C" {
__declspec(dllexport) void *GetSomething();
}
#include "Header.h"
extern "C" {
__declspec(dllexport) void *GetSomething() {
long *var = new long();
*var = 2147483647l;
return var;
}
}
Remember to first build the Project1
, then run the main project, which will copy the DLL to the local folder and use it. Now check the dumpbin.
Dump of file Project1.dll:
$dumpbin /exports Project1.dll
File Type: DLL
Section contains the following exports for Project1.dll
00000000 characteristics
55BA0076 time date stamp Thu Jul 30 12:46:14 2015
0.00 version
1 ordinal base
1 number of functions
1 number of names
ordinal hint RVA name
1 0 000010E9 GetSomething = _GetSomething
Summary
1000 .data
A000 .rdata
1000 .reloc
1000 .rsrc
3000 .text
$
Good. The result shown in the console however is not satisfactory> -144680347789950977
. It must be some kind of overflow. Because of the void *
type of GetSomething
may return any type, let's check the mapping between C++ and C# types. Ok. So long
is a simple int
. Let's change the code.
static void Main(string[] args)
{
unsafe
{
int* variable = (int*)GetSomething();
Console.WriteLine(*variable);
Console.ReadKey();
}
}
Yay. It works!! Even if I add one more to int.max
(long.max
in C++), it will coherently overflow.
Let's Check How C# Can Pair Work with G++ Compiled DLL
Same code, just compiled with g++.
$g++ -c Source.cpp
$g++ -shared -o Source.dll Source.o
Change the DLL import part.
[DllImport("Source.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe extern static void* GetSomething();
[DllImport("Source.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe extern static void Free();
This just exits. No error, nothing. Just exit. We have to debug this. After some run of gdb, sometimes the output is like this:
$gdb --args xy.exe
...
1 [main] xy 13248 D:\...\xy\xy.exe: *** fatal error
- Incompatible cygwin .dll -- incompatible per_process info 0 != 168
(no debugging symbols found)
Hmm... Check it on the internet. It might be something related to cygwin. "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."
[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);
unsafe public delegate void *GetSomething();
static void Main(string[] args)
{
unsafe
{
IntPtr pcygwin = LoadLibrary("cygwin1.dll");
IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
init();
IntPtr getSomethingLibrary = LoadLibrary("Source.dll");
IntPtr pfn = GetProcAddress(getSomethingLibrary, "GetSomething");
GetSomething getSomething =
(GetSomething)Marshal.GetDelegateForFunctionPointer<GetSomething>(pfn);
int* variable = (int*)getSomething();
Console.WriteLine(*variable);
Console.ReadKey();
}
}
Wow. What a boilerplate. I guess it's needed. Now the program sometimes works, sometimes not. Sometimes freezes, sometimes writes the number. Gdb outputs nothing. (no debugging symbols found) is the most, that I get. Why?
How Does the C# Side Know the Lifecycle of the Variable?
Ok. Let's go into some detail. When the program works, the DLLs function is just running, and the var
is still in the memory. In the other case, the function's running is done when we want to access the value, and var
is disposed leading to the frozen behavior. Let's manage the variable differently.
#include <windows.h>
extern "C" {
__declspec(dllexport) void *GetSomething();
__declspec(dllexport) void Free();
}
#include "Header.h"
extern "C" {
static long *var;
__declspec(dllexport) void *GetSomething() {
var = new long();
*var = 2147483647l;
return var;
}
__declspec(dllexport) void Free() {
delete var;
}
}
C# code:
[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);
unsafe public delegate void *GetSomething();
unsafe public delegate void Free();
static void Main(string[] args)
{
unsafe
{
IntPtr pcygwin = LoadLibrary("cygwin1.dll");
IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
init();
IntPtr getSomethingLibrary = LoadLibrary("Source.dll");
IntPtr pfn = GetProcAddress(getSomethingLibrary, "GetSomething");
GetSomething getSomething =
(GetSomething)Marshal.GetDelegateForFunctionPointer<GetSomething>(pfn);
Free free = (Free)Marshal.GetDelegateForFunctionPointer<Free>(pfn);
int* variable = (int*)getSomething();
Console.WriteLine(*variable);
free();
variable = (int*)getSomething();
Console.WriteLine(*variable);
free();
Console.ReadKey();
}
}
Nope. Same behaviour. Sometimes works sometimes not (the value is printed 0, 1, 2 times). Sometimes even throws System.AccessViolationException
. What is the memory model of Windows? More important question: How can I allocate memory, that will survive, till the free method? The answer is here. I need less than 64 k memory for var
to store. LocalAlloc
is enough.
#include "Header.h"
extern "C" {
static long *var;
__declspec(dllexport) void *GetSomething() {
var = (long *)LocalAlloc(LPTR, 2);
*var = 2147483647l;
return var;
}
__declspec(dllexport) void Free() {
LocalFree(var);
}
}
And that's that. Now it's stable.
Points of Interest
You try to import a DLL, and you have not a trace of debug information what is wrong? Check this first.
History
- 1.0 Initial version created