Click here to Skip to main content
15,891,744 members
Articles / Programming Languages / C# 5.0
Tip/Trick

How to Import Some (void *) Returning Type Function into a C# Project Compiled with cygwin g++

Rate me:
Please Sign up or sign in to vote.
4.70/5 (7 votes)
30 Jul 2015CPOL4 min read 15K   7   1
Cygwin DLL imported into C# project

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:

C++
 extern "C" {

__declspec(dllexport) 

LocalAlloc

LocalFree

long is like int

In the C++ code and...

C#
[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.

C++
// Header.h

extern void *GetSomething();

// Source.cpp

#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:

C#
[DllImport("Project1.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe extern static void* GetSomething();

Done. Usage:

C#
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.

C++
// Header.h

#include <windows.h>

extern "C" {
    __declspec(dllexport) void *GetSomething();
}

// Source.cpp

#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.

C#
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.

C#
[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."

C#
[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.

C++
// Header.h

#include <windows.h>

extern "C" {
    __declspec(dllexport) void *GetSomething();

    __declspec(dllexport) void Free();
}

// Source.cpp

#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:

C#
[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.

C++
// Source.cpp

#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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Hungary Hungary
BY DAY: Working in C#.

BY NIGHT: Programming fancier stuff. C#/F#. Playing Paladins, or something (of course after when the kids are sleeping). Drink beer.

FOR FUN: Archery. Talk with good ol' friends.

Br,
Márton

Comments and Discussions

 
QuestionCygwin Pin
Jason Curl31-Jul-15 8:21
professionalJason Curl31-Jul-15 8:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.