2。 在C#中创建DLL接口的声明
C#没有全局函数,必须使用静态函数实现全局函数。
1) DllImport类似C++中的__declspec(dllimport),第一个参数为必选参数,为DLL的路径,一般以相对路径即可,只需要将DLL文件放到工作目录中即可
2) EntryPoint表示对应的函数名称,这个与C++ DLL工程中.def文件中导出的函数名同
使用C#调用C++时不支持C++的函数名重载(至少还没有找到办法),如果参数不同必须使用不同的函数名用以区分,但在C#中可以使用相同的函数名
3) C#中的声明的函数名不一定与实际的函数名一样,比如
public static extern int PassString(string msg);
中的PassString可以使用任何名称,与C++中的对应关系只需要DllImport中的EntryPoint参数保持一致。
一般地只需要给出DLL文件名、EntryPoint两个参数就可以了。
using System; using System.Runtime.InteropServices; using Noock.TTest;
public static class CFuncs { [DllImport("dlldemo.dll", EntryPoint = "PassString", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall, ExactSpelling = true)] public static extern int PassString(string msg); [DllImport("dlldemo.dll", EntryPoint = "Power", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall, ExactSpelling = true)] public static extern double Power(double x, int y); public struct Location { public int x; public int y; } [DllImport("dlldemo.dll", EntryPoint = "GetDistance1D", CharSet = CharSet.Auto)] public static extern int GetDistance(int x1, int x2); [DllImport("dlldemo.dll", EntryPoint = "GetDistance2D", CharSet = CharSet.Auto)] public static extern double GetDistance(Location x1, Location x2); [DllImport("dlldemo.dll", EntryPoint = "CopyValues", CharSet = CharSet.Auto)] public static extern int CopyValues(out int dst, ref int src, int length); [DllImport("dlldemo.dll", EntryPoint = "GetValue", CharSet = CharSet.Auto)] public static extern int GetValue(out int value); [DllImport("dlldemo.dll", EntryPoint = "CopyArray2D", CharSet = CharSet.Auto)] public unsafe static extern int CopyArray2D(ref byte* dst, ref byte* src, int m, int n); [DllImport("dlldemo.dll", EntryPoint = "CopyPointerArray2D", CharSet = CharSet.Auto)] public unsafe static extern int CopyPointerArray2D(ref byte* dst, ref byte* src, int m, int n); [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct Person{ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)] public string name; public byte age; [MarshalAs(UnmanagedType.U1)] public bool isFemale; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)] public string email; }; [DllImport("dlldemo.dll", EntryPoint = "GetPerson", CharSet = CharSet.Auto)] public static extern bool GetPerson(ref Person p, string name); [DllImport("dlldemo.dll", EntryPoint = "SetEnable", CharSet = CharSet.Auto)] public static extern bool SetEnable( bool enabled); }需要注意的是在结构体Persion中的isFemail字段采用的是bool类型,C#与C++中的bool都是通过1个字节来实现的,而且实现机制非常类似,所以将期视作单字节的无符号类型处理。
3。 在C#中调用C++的函数,下面的测试代码使用了前面实现的TTest测试框架(http://blog.csdn.net/nocky/article/details/7687559)。
class Program { static void Main(string[] args) { Console.WriteLine("Press any key to quit"); #if WRITE_TO_FILE // Write to file using (FileStream fs = new FileStream(string.Format("Test_{0}.log", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")),FileMode.CreateNew)) { using (StreamWriter sw = new StreamWriter(fs)) { Test.Out = sw; Test.Run(); Test.Out = Console.Out; } } #else TTest.Run(); #endif Console.Read(); } [TestCase(Target="int __stdcall PassString(char* msg)")] private static void TestPassString() { string msg1 = "Message #1"; int ret = CFuncs.PassString(msg1); TTest.AreEqual(0, ret, "return value error"); } [TestCase(Target="long double __stdcall Power(double x, int y)")] private static void TestPower() { double x = 2.0; int y = 3; double p = CFuncs.Power(x, y); TTest.AreEqual(8.0, p); } [TestCase(Target="int _stdcall GetDistance(int x1, int x2)")] private static void TestGetDistance() { int x = 20, y = 30; int dist = CFuncs.GetDistance(x, y); TTest.AreEqual(10, dist); } [TestCase(Target = "double __stdcall GetDistance(Location p1, Location p2)")] private static void TestGetDistance2() { CFuncs.Location p1 = new CFuncs.Location() { x = 0, y = 0 }; CFuncs.Location p2 = new CFuncs.Location() { x = 3, y = 4 }; double dist = CFuncs.GetDistance(p1, p2); TTest.AreEqual(5, dist); } [TestCase(Target = "int __stdcall CopyValues(int* dst, int* src, int length)")] private static void TestCopyValues() { int[] src = new int[] { 0, 1, 5, 10, 15, 20, 25, 30 }; int[] dst = new int[src.Length]; int ret = CFuncs.CopyValues(out dst[0], ref src[0], src.Length); TTest.AreEqual(src.Length, ret); for (int i = 0; i < src.Length; ++i) { TTest.AreEqual(src[i], dst[i]); } } [TestCase(Target = "int __stdcall GetValue(int& dst)")] private static void TestGetValue() { int value; int ret = CFuncs.GetValue(out value); TTest.AreEqual(ret, value); } [TestCase(Target = "int __stdcall CopyArray2D(unsigned char** dst, unsigned char** src, int m, int n)")] private static void TestCopyArray2D() { byte[,] src = new byte[2, 5] { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 } }; byte[,] dst = new byte[2, 5]; unsafe { fixed (byte* psrc = &src[0,0], pdst = &dst[0,0]) { // psrc and pdst are fixed pointer, they are not allowed to pass as arguments // error CS1657: Cannot pass 'psrc' as a ref or out argument because it is a 'fixed variable' // the following 2 lines are used to cheat compiler byte* psrc2 = psrc; byte* pdst2 = pdst; int n = CFuncs.CopyArray2D(ref pdst2, ref psrc2, 2, 5); TTest.AreEqual(10, n); // CFuncs.CopyPointerArray2D(ref pdst2, ref psrc2, 2, 5); // OK } } TTest.AreEqual(src, dst); } [TestCase(Target = "int __stdcall CopyPointerArray2D(unsigned char** dst, unsigned char** src, int m, int n)")] private static void TestCopyPointerArray2D() { byte[][] src = new byte[2][] { new byte[] { 1, 2, 3, 4, 5 }, new byte[]{ 6, 7, 8, 9, 10} }; byte[][] dst = new byte[2][] { new byte[5], new byte[5] }; unsafe { byte*[] psrc = ConvertType(src); byte*[] pdst = ConvertType(dst); int n = CFuncs.CopyPointerArray2D(ref pdst[0], ref psrc[0], 2, 5); TTest.AreEqual(10, n); // CFuncs.CopyArray2D(ref pdst[0], ref psrc[0], 2,5); // ERROR } TTest.AreEqual(src, dst); } private static unsafe byte*[] ConvertType(byte[][] pptr) { if (pptr == null) throw new ArgumentNullException("pptr"); var len = pptr.GetUpperBound(0) + 1; var buffer = new byte*[len]; for (var i = 0; i < len; ++i) { if (pptr[i] == null) throw new NullReferenceException(string.Format("pptr[{0}]", i)); fixed (byte* ptr = pptr[i]) { buffer[i] = ptr; } } return buffer; } [TestCase(Target="bool __stdcall GetPerson(Person* p, char* name)")] private static void TestGetPerson( ) { string name = "Jobs"; CFuncs.Person jobs = new CFuncs.Person(); jobs.age = sizeof(bool); bool b = CFuncs.GetPerson(ref jobs, name); TTest.AreEqual(true, b); TTest.AreEqual("Steve Jobs", jobs.name,false, "Check name"); TTest.AreEqual(100, jobs.age, "Check Age"); TTest.AreEqual("[email protected]", jobs.email, false, "Check email"); TTest.AreEqual(false, jobs.isFemale, "Check Sex"); } [TestCase(Target = "bool __stdcall SetEnable(bool enabled)")] private static void TestSetEnable() { TTest.AreEqual(false, CFuncs.SetEnable(true)); TTest.AreEqual(true, CFuncs.SetEnable(false)); } }
还没有研究多维数组的传递,其传递方式会比较复杂,应该不会用到吧,如果确实出现的话是该考虑一下设计问题了。
对于C++ class定义的类型以及如何调用对象的方法一下步再研究。(待续)