C#调用C/C++ 动态链接库DLL(三)

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("Steve.Jobs@apple.com", 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));
		}
	}

需要特别注意的是 CopyPointerArray2D与CopyArray2D虽然两个函数的声明完全一样,但在C++中实际处理的方式是完全不同的,产者是看作一维指针数组来处理的,可以允许“二维数组”是不连续的,而后者是将其看作连续的存储空间,即C/C++中的二维数组的数据存储方式处理的,所以在示例代码中使用CopyArray2D处理byte[][]类型的锯齿数组是不可以的,会造成内存的非法访问破坏内存数据。


还没有研究多维数组的传递,其传递方式会比较复杂,应该不会用到吧,如果确实出现的话是该考虑一下设计问题了。


对于C++ class定义的类型以及如何调用对象的方法一下步再研究。(待续)

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页