Friday, August 25, 2006

Calling a C++ lib from C#

a few months back, I found myself having to use a C++ .lib file from C#. it took a bit of looking around to figure it out, so I figured that I'd publish my cheat sheet here. I posted a number of questions about this subject to the C# interop newsgroup and found that the MVPs were excellent. if you search, you'll find these posts and be able to get better code from there - I've tried to get the examples in this post right, but the line breaks may cause confusion.

BTW - I grabbed this stuff from all over. I've tried to credit the sources, but I may have missed some. I've created a separate post about handling the unsigned char* data type from a C .lib file.

first of all, this is an awkward thing to do. if you have the choice, get a DLL instead of LIB.

1. create a C++ Win32 DLL Project from VS.Net

from: http://www.codersource.net/win32_dlls.html

Creating a Win32 dll:
- Open the New Project Wizard.
- Select Win32 Dynamic-Link Library.
- Name the project "DemoDll."
- In the next screen, select "A dll that exports some symbols" radio button.
- Click Finish.
The dll project will be created.

Now open the file DemoDll.cpp. You can see a function definition for "DEMODLL_API int fnDemoDll(void)".

Replace this function with the following code:
DEMODLL_API int fnDemoDll(int a,int b){return a+b;}

Open the DemoDll.h file and insert the declaration "DEMODLL_API int fnDemoDll(int a,int b)"

Create a new file named "DemoDll.Def" and add the following code in that file.

; DemoDll.def : Declares the module parameters for the DLL. LIBRARY "DemoDll"
DESCRIPTION 'DemoDll Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
fnDemoDll PRIVATE

Remember to add this DemoDll.def file to the project.
Build the project. The DLL will be created.

Using the DLL
This part explains only the dynamic loading using loadlibrary. The following sample code has to be used in a new console application.

#include <windows.h>
#include <iostream.h>

typedef int (*ADDITIONFUNCTION)(int a,int b);
ADDITIONFUNCTION addFunction;

void main()
{
HINSTANCE hDll;
hDll = LoadLibrary("Pathofdll");
if(hDll == NULL)
{
cout<<"Error loading win32 dll"<<endl;
return ;
}
addFunction = (ADDITIONFUNCTION)GetProcAddress(hDll,"fnDemoDll");
int result;
if(addFunction != NULL)
cout<<(addFunction)(10,11)<<endl; else { cout<<"error:"<<GetLastError()<<endl; } } 2. test this DLL from a C# project by calling the example method. copy the file into the C# project bin/debug folder - you can't add a reference. http://www.codersource.net/win32_dlls.html

Now open a blank solution, and add a new C++ MFC Class Library Project. Let's call it MyUnmanagedDotNetVCWrapper. We will implemenet a wrapper for MyUnmanagedVC6Class:

#include "MyUnmanagedVC6Header.h"

class __declspec(dllexport) MyUnmanagedDotNetClass
{
private:
MyUnmanagedVC6Class* _class;
public:
MyUnmanagedDotNetClass(int x)
{
_class = new MyUnmanagedVC6Class(x);
}
void DoSomething(int x,double d)
{
_class->DoSomething(x,d);
}
}

This should be located in MyUnmanagedDotNetHeader.h.

Note: If you have all the sources for the original DLL you can open it in VisualStudio .NET, and it will convert it for you into a DLL you can use for the later step. This can save you the time for the first wrapper.

4. add methods to your new DLL that wrap the method in the original lib that you're trying to access from C#. newsgroup post about this topic.

Random Notes:

EXPORTS
; Explicit exports can go here
MyMMCPropertyChangeNotify @1
MyMMCFreeNotifyHandle @2
MyMMCPropPageCallback @3

after this I added the following line to mymmc.h #pragma comment(lib, "mmc.lib")

I changed one project setting and it compiled. In the General setting: Use MFC in a shared DLL.

reference this page if you have questions about marshalling data types:
http://msdn.microsoft.com/msdnmag/issues/02/08/CQA/

Example of syntax for calling the functions

// Cawood: import an ARTag sample function
[DllImport("ARTagWin32DLL.dll", EntryPoint="artag_create_marker_wrapped")]
//public static extern int artag_create_marker_wrapped(int artag_id, int scale, unsigned char *image);
public static extern int artag_create_marker_wrapped(int artag_id, int scale, [MarshalAs(UnmanagedType.LPStr)] StringBuilder image)

7 comments:

Anonymous said...

"this is an awkward thing to do. if you have the choice, get a DLL instead of LIB."

I'm convinced.

I'm trying to gain access to some library functions inside some libraries in an open source project called DCMTK (used for opening DICOM files, used in medical data transfer).

Your answer about call a c++ lib from c# makes it perfectly clear that I'm in way over my head.

Is there a simple (however long) explanation somewhere of how I could cause a c++ solution with many many many subprojects to produce .dll's instead of those .lib's?

And thanks for the detailed info on the c++/c# topic. If I get nothing else from this contact, I know to stop doing what I'm trying to do.

wannall@sbcglobal.net

Rick Wannall
Bioimaging Database Designer
University of Houston

Stephen Cawood said...

thanks for the comment. you're absolutely right - you don't want to do this if you have the choice.

if you have the code for the .lib, then work on compiling it into a .dll.

Anonymous said...

Hi,
the link:
http://www.codersource.net/win32_dlls.html
is not for creating win32 under .NET environment but VC++6.0 environment.

do you have the correct link. i do not see such an option in the .NET environment.

Thanks,
Eila

Stephen Cawood said...

Eila, you're right, but it's the best I could find. I used it as a starting point.

if you find a more recent post, please let me know.

cheers

Stephen Cawood said...

yes, you'll need to create wrapper functions for everything that you want to use.

I haven't looked at this in months, so I'm afraid this post is all I can offer. good luck!

RRave said...

Dear Sir,

I have a launched new web site for .NET programming resources. www.codegain.com. I would like to invite to the codegain.com as author and supporter. I hope you will joins with us soon.

Thank You
RRaveen
Founder www.codegain.com

xeeez said...

Here's a tip if its already not been posted..

While exposing the DLL thru the console application the GetProcAddress might not return the function correctly using "fnDemoDll" name which was exported from the DLL..I got error code 127 and found out that the DLL had registered the method as "?fnDemoDll@@YAHHH@Z"..so if somebody faces a similar error, just go thru the DLL using dependency walker and see what name is being used at ur end ;)