Website of Markus Griesser
Latest Update: 29.12.2007
So, what is the main problem discussed here? My current problem is, that I have an
old but very large C++ application which I want to extend with .NET code and UI for
improving the development process. On the one hand side, it is somehow easy to
import C++ functions in C#, as you can see here:
namespace Connex{
public class OldImports{
[DllImport("pathtotheolddll.dll")]
public extern static void OldFunction();
}
public class TestMe{
public void CallOldFunction(){
// Call the imported function
OldImports.OldFunction();
}
}
}
Well, this is not calling C# in C++ but the other way. Because it is the
easier part and therefore also needed in many cases, I will start with this.
Remeber, that this method only works, if OldFunction is exported by the
dll with its "clear" name OldFunction (using the .def file) and not masked
like it would be if you use extern __declspec(dllexport). If so, you have to use
the Entrypoint parameter in [DllImport].
But what, if the function takes some arguments? No problem if we are talking about
simple types like integers or doubles. See code:
namespace Connex{
public class OldImports{
[DllImport("pathtotheolddll.dll")]
public extern static int OldFunctionWithArgs(int nValue,
ref double dValue);
}
public class TestMe{
public void CallOldFunction(){
// Call the imported function
int nValue = 4;
double dValue = 10.3;
int nRet = OldImports.OldFunction(nValue, ref dValue);
MessageBox.Show(dValue.ToString());
}
}
}
Now the OldFunction takes two arguments, one int and one double. The integer is passed by value and the double by reference. The functions is also returning an integer. In C++ the function would have the following look:
extern __declspec(dllexport) int OldFunction(int nValue, double &dValue){
dValue *= nValue;
return 1;
}
Nothing difficult up to now. But what if we want to pass a struct or a string to the C++ function? For passing a struct you have much more to do. The first thing is to declare the struct in your .NET language. Here in C#:
namespace Connex{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MFCStruct {
public int nI;
public double dL;
}
}
The StructLayout attribute is important, because it tells
the compiler that this struct is passed to unmanaged code. If you are aware
of the different datatypes in C# and C++ you can simply use LayoutKind.Sequential
and let the compiler do the work. But take care, that int in C++ and int in C# must
not be of same length (depends on how the C++ project was compiled). In such cases
you may want to use LayoutKind.Explicit. For detailed information have a
look at MSDN.
The CharSet attribute tells how the unmanaged code accepts strings. If your
old application is build using UNICODE, than you have to use CharSet.Unicode.
When using ANSI the prefered value would be CharSet.ANSI.
And now the function call:
namespace Connex{
public class OldImports{
[DllImport("pathtotheolddll.dll")]
public extern static int OldFunctionWithArgs(MFCStruct t);
}
public class TestMe{
public void CallOldFunction(){
MFCStruct t = new MFCStruct();
int nRet = OldImports.OldFunction(t);
}
}
}
In this example the struct is passed by value. If you want to pass it by reference
you can use the same way as with the double I described before.
Now a string that is passed by reference, manipulated by the old C++ function and
of course printed out by the C# function.
namespace Connex{
public class OldImports{
[DllImport("pathtotheolddll.dll")]
public extern static int OldFunctionWithArgs(
[MarshalAs(UnmanagedType.BStr)]ref string s);
}
public class TestMe{
public void CallOldFunction(){
string s = "Hello World";
int nRet = OldImports.OldFunction(ref s);
MessageBox.Show(s);
}
}
}
What is this MarshalAs attribute about? The marshaller is part of the .NET
framework and handles conversion between managed and unmanaged code. Depending on how
the string is accepted on the unmanaged side (the C++ function) you will have to tell
the marshaller what to do. For detailed description have again a look at MSDN.
In my case, the C++ function originally was defined with a CString (MFC) reference, which
I currently could not get to work. So I changed the parameter to BSTR &. The MFC
seems to be a big problem when connecting to C#...