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

来源:互联网 发布:域名top 编辑:程序博客网 时间:2024/05/22 01:46

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 fileusing (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;}}#elseTTest.Run();#endifConsole.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 compilerbyte* 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定义的类型以及如何调用对象的方法一下步再研究。(待续)