Create native DLL in c++ and call it from c#

P

pigeonrandle

Hi,
This is an answer, not a question. I hope it helps you since i was
messing around for ages trying to get information on this:

For anyone else wanting to create a test c++nativedll/c# project follow
these instructions...

1) Create a new 'Visual C++ - Win32 Console Project' - call it 'test'
1b) When the wizard pops up, click the 'Application Settings' link on
the left
1c) Select 'DLL' under 'Application Type'
1d) Click 'Finish' (don't worry, everything remains in English)

2) In test.cpp you will need to ADD the following code at the bottom of
the auto-generated stuff:

//START

struct MyTestStruct
{
int iSomeNumber;
int iAnotherNumber;
};

extern "C" __declspec(dllexport) void
DoSomethingWithStruct(MyTestStruct* mts);

void DoSomethingWithStruct(MyTestStruct* mts)
{
if (mts != NULL)
{
mts->iSomeNumber = 12345;
mts->iAnotherNumber = mts->iAnotherNumber + 67890;
}
}

//END

That's the dll sorted. As you can see, DoSomethingWithStruct just sets
the value of the referenced structure's members.

3) Now File > Add Project > New Project > 'Visual C# - Windows
Application'
3b) Add a button to your form - leave it named 'button1'
3c) Add the button click event handler
3d) Paste the code below OVER the entire event handler 'private void
button1_Click( ... ) { ... }':

//START

struct MyTestStruct
{
int iSomeNumber;
int iAnotherNumber;
};

[System.Runtime.InteropServices.DllImport(@"REPLACE
_THIS_WITH_THE_PATH_TO_YOUR_TEST.DLL_FILE\test\Deb ug\test.dll")]
private static extern void DoSomethingWithStruct(ref MyTestStruct
theStruct);

private void button1_Click(object sender, System.EventArgs e)
{
MyTestStruct mts = new MyTestStruct();
DoSomethingWithStruct(ref mts);
}

//END

3e) change the path where it says
REPLACE_THIS_WITH_THE_PATH_TO_YOUR_TEST.DLL_FILE to point to your dll.

4) Set the C# Windows Application as the startup project

5) Set a breakpoint so you can see that mts has indeed been changed

6) Run the project.

Shamelessly adapted from
http://blogs.msdn.com/jonathanswift/archive/2006/10/02/780637.aspx, but
with some vital information added thanks to b0b - you know who you are.

Regards,
James Randle.

Also check out jonathan's followup article
http://blogs.msdn.com/jonathanswift...nmanaged-dll-from-.NET-_2800_C_23002900_.aspx
which eliminates the need to specify the location of the dll in the
DllImport attribute. Excellent work :).
 
M

Michael C

pigeonrandle said:
extern "C" __declspec(dllexport) void
DoSomethingWithStruct(MyTestStruct* mts);

Doesn't this cause the name DoSomethingWithStruct to be mangled?

Michael
 
B

Ben Voigt

Michael C said:
Doesn't this cause the name DoSomethingWithStruct to be mangled?

With extern "C", the compiler will just add a leading underscore. I think
P/Invoke is smart enough to find the export anyway. Other things it might
do is add "A" or "U" depending on whether you selected CharSet.Unicode or
not.
 
P

pigeonrandle

Michael,

I don't think so (taken from
http://blogs.msdn.com/jonathanswift/archive/2006/10/02/780637.aspx)...

Using extern "C" forces the compiler to use the actual function name
(as it would in C). This prevents us from overloading this function but
we're not bothered about that in this example.
On a related note, if you want to examine a dll to find out, amongst
other things, exported function names, you can use the dumpbin command
from the Visual Studio command prompt. Typing dumpin /exports filename
will list the exported function names from the dll. Try it on our
simple dll with and without the extern "C" keywords to see the
decoration in action.

....unless you have something you'd like to add?!

Cheers,
James.

Again, maximum respect to Jonathan Swift, whoever you are ;)
 
M

Mattias Sjögren

Using extern "C" forces the compiler to use the actual function name
(as it would in C).

Because you're using the cdecl calling convention. If you want to keep
it that way and not switch to stdcall (which is more common for Win32
APIs), you should specify that in your DllImport attribute.


Mattias
 
M

Michael C

Mattias Sjögren said:
Because you're using the cdecl calling convention. If you want to keep
it that way and not switch to stdcall (which is more common for Win32
APIs), you should specify that in your DllImport attribute.

So to use stdcall you need a DEF file?

Michael
 
M

Michael C

Mattias Sjögren said:
Because you're using the cdecl calling convention. If you want to keep
it that way and not switch to stdcall (which is more common for Win32
APIs), you should specify that in your DllImport attribute.

It's interesting that this example worked at all. C is using cdecl while c#
is using stdcall.

Michael
 
M

Mattias Sjögren

So to use stdcall you need a DEF file?

To get it exported with an unmangled name, yes.

To be able to call it from .NET, no. The stdcall naming scheme is

_<function name>@<parameter byte count>

and like Ben mentioned, .NET can and will try that if the function
isn't found by its unmangled name.


Mattias
 
P

pigeonrandle

All,
Perhaps one of you would like to modify the example I posted to reflect
what you have said? The reason i posted this in the first place was to
give people a starting point that was free from confusion!

Cheers,
James.
 
W

Willy Denoyette [MVP]

Michael C said:
It's interesting that this example worked at all. C is using cdecl while c# is using
stdcall.

Michael

Yep, this results in a corrupted stack, but you don't call C directly from C#, the PInvoke
interop layer fixes the calling convention mismatch.
Anyway, this is a bug that should be corrected by the author of the code, you should not
rely on the CLR to do the right thing.

Willy.
 
W

Willy Denoyette [MVP]

Willy,
I'm happy to make the correction :), what is it?

James.

The default calling convention as expected by PInvoke is __stdcall, when calling a __cdecl
function, you'll have to tell PInvoke by applying the CallingConvention parameter like this:
[DllImport("yourdll", CallingConvention=CallingConvention.Cdecl) static extern .....
Note that the majority of the Win32 API's are __stdcall, and all Win32 callbacks must be
__stdcall. This is probably the reason why it has been taken as the default.
Most of the CRT API's however, are __cdecl and the VC compiler defaults to _cdecl, so, if
you want to decalre your C function as __stdcall you have to declare your function like so:

extern "C" void __declspec(dllexport) __stdcall function(...);
In this case you can go with the default in your PInvoke declaration by leaving out the
CallingConvention , but I would prefer to be explicit and always include the
CallingConvention.

Willy.
 
M

Michael C

Willy,
I'm happy to make the correction :), what is it?

Willy's method modifies the C# definition to use cdecl but IMO it is better
to modify C++ so it uses stdcall. Just about every API I've ever used on
windows from MS or other vendors has been stdcall so it is better to go with
the standard. I'm no expert on C++ but here's my example of a dll with a
single function called AddOne. It takes an integer and returns an integer
which has had 1 added to it. I just created a new Win32 dynamic link library
project and replace the default code with the code below. You will need to
create a DEF file also so C++ doesn't mangle the function names. The DEF
file is only 2 lines and is below also. There is no option to add a DEF file
but just add a text file and rename it to something.def. The .h file isn't
strictly needed but is good to have. In the .cpp file where is says #include
"TestDll.h" you should rename this to whatever you've called your .h file.
That's it, besides what C++ adds by default there's only 5 lines of code.
Compile the dll and view it with dependency walker or quick view and have a
look at the exports to make sure it all worked, you should see AddOne
unmangled. You can call this from C# in the way you originally called it. If
anyone sees any issues with my C++ code please let me know. :)



--------------------------------------------
..h file:

extern int __stdcall AddOne(int Value);


----------------------------------
..cpp file:

#include "stdafx.h"
#include "TestDll.h"

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

int __stdcall AddOne(int Value)
{
return Value + 1;
}
 
W

Willy Denoyette [MVP]

Michael C said:
Willy,
I'm happy to make the correction :), what is it?

Willy's method modifies the C# definition to use cdecl but IMO it is better to modify C++
so it uses stdcall. Just about every API I've ever used on windows from MS or other
vendors has been stdcall so it is better to go with the standard. I'm no expert on C++ but
here's my example of a dll with a single function called AddOne. It takes an integer and
returns an integer which has had 1 added to it. I just created a new Win32 dynamic link
library project and replace the default code with the code below. You will need to create
a DEF file also so C++ doesn't mangle the function names. The DEF file is only 2 lines and
is below also. There is no option to add a DEF file but just add a text file and rename it
to something.def. The .h file isn't strictly needed but is good to have. In the .cpp file
where is says #include "TestDll.h" you should rename this to whatever you've called your
.h file. That's it, besides what C++ adds by default there's only 5 lines of code. Compile
the dll and view it with dependency walker or quick view and have a look at the exports to
make sure it all worked, you should see AddOne unmangled. You can call this from C# in the
way you originally called it. If anyone sees any issues with my C++ code please let me
know. :)



--------------------------------------------
.h file:

extern int __stdcall AddOne(int Value);


----------------------------------
.cpp file:

#include "stdafx.h"
#include "TestDll.h"

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

int __stdcall AddOne(int Value)
{
return Value + 1;
}

------------------------------------
.def file

EXPORTS
AddOne


Be careful, all C runtime and C++ Standard Library exports are __cdecl. The default export
for VC++ is __cdecl.
So when using PInvoke, the message should be, read the docs of the API you need to call,
don't assume any default.

Willy.
 
M

Michael C

Willy Denoyette said:
Be careful, all C runtime and C++ Standard Library exports are __cdecl.
The default export for VC++ is __cdecl.
So when using PInvoke, the message should be, read the docs of the API you
need to call, don't assume any default.

Good advice, especially seeing dot net apparently fixes the stack corruption
without error so you might never know. Although I've only ever encountered 1
cdecl api.

Michael
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top