**
背景
**:最近做到一个日本项目的时候与日本软件工程师有一些对接的地方,他们使用c++封装dll,我们使用c#进行调用去传输或者获取一些数据进行交互。
愿景
***:感觉已经人家封装成DLL了,以前也引用过别人家的嘛,不过别人家c#的,应该区别不大。添加引用,实例化,调用方法,传参,简简单单,so easy,再封装到一起,泛型 继承一下,展现一下我高超的面详对象技术,好好教这帮呆板的Japanese做人
实际: 如下图:
我当时就惊呆了,在我更换了一遍.net freamwork版本无济于事之后我意识到,可能不是这个问题。开始祭出我的大招-百度。
调研
俩天内我翻遍了墙内外的c# 关于C++调用的博客,问题解决的方案,开始有点眉目。因为c++属于非托管代码,而c#属于托管代码。
知识点1:托管代码与非托管代码
*什么是托管代码?
- 托管代码是一microsoft的中间语言,他主要的作用是在.NET
FRAMEWORK的CLR执行代码前去编译源代码,也就是说托管代码充当着翻译的作用,源代码在运行时分为两个阶段:源代码编译为托管代码,(所以源代码可以有很多种,如VB,C#,J#)
2.托管代码编译为microsoft的平台专用语言。 编译器把代码编译成中间语言(IL),而不是能直接在你的电脑上运行的机器码。中间语言被封装在一个叫程序集(assembly)的文件中,程序集中包含了描述你所创建的类,方法和属性(例如安全需求)的所有元数据。你可以拷贝这个程序集到另一台服务器上部署它。 托管代码在公共语言运行库(CLR)中运行。这个运行库给你的运行代码提供各种各样的服务,通常来说,他会加载和验证程序集,以此来保证中间语言的正确性。当某些方法被调用的时候,运行库把具体的方法编译成适合本地计算机运行的机械码,然后会把编译好的机械码缓存起来,以备下次调用。(这就是即时编译)随着程序集的运行,运行库会持续地提供各种服务,例如自动垃圾回收、运行库类型检查和安全支持等。这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。Visual Basic .NET和C#只能产生托管代码。如果你用这类语言写程序,那么所产生的代码就是托管代码。如果你愿意,Visual C++.NET可以生成托管代码。当你创建一个项目的时候,选择名字是以.Managed开头的项目类型。例如.Managed C++ application。
什么是非托管代码?
- 非托管代码就是在Visual Studio .NET 2002发布之前所创建的代码。例如Visual Basic 6, Visual C++ 6, 最糟糕的是,连那些依然残存在你的硬盘中、拥有超过15年历史的陈旧C编译器所产生的代码都是非托管代码。托管代码直接编译成目标计算机的机械码,这些代码只能运行在编译出它们的计算机上,或者是其它相同处理器或者几乎一样处理器的计算机上。非托管代码不能享受一些运行库所提供的服务,例如安全和内存管理等。如果非托管代码需要进行内存管理等服务,就必须显式地调用操作系统的接口,通常来说,它们会调用Windows SDK所提供的API来实现。就最近的情况来看,非托管程序会通过COM接口来获取操作系统服务。 跟Visual Studio平台的其他编程语言不一样,Visual C++可以创建非托管程序。当你创建一个项目,并且选择名字以M
FC,ATL或者Win32开头的项目类型,那么这个项目所产生的就是非托管程序。
区别:
1、托管代码是一种中间语言,运行在CLR上;
托管代码 | 非托管代码 |
---|---|
托管代码是一种中间语言,运行在CLR上 | 非托管代码被编译为机器码,运行在机器上 。 |
托管代码独立于平台和语言,能更好的实现不同语言平台之间的兼容 | 非托管代码依赖于平台和语言 |
内存管理(内存申请,内存释放,垃圾回收之类的)全部都是.net的CLR来管理 | 非托管代码需要自己提供安全检测、垃圾回收等操作。 |
托管的意思,你不用直接操作内存,你需要的时候跟我说。我替你申请,然后给你用,你用完可以告诉我,我帮你释放,如果你忙,忘记告诉我了,我也会在定期去帮你释放的。 这就是托管,你打交道的不是直接的内存,而是.net clr。
非托管的意思就是你要自己负责管理内存,这里所说的内存管理。实际上只是堆上的内存管理,栈内存和以前的一样,函数退出则释放,heap上的内存,非托管内存需要自己分配,调用构造函数(c需要,c++里用new替代这部操作了),使用完毕后,需要自己释放这个内存,如果你不小心,吧只想内存的指针弄丢了,就造成内存泄露了,这个内存泄露在你程序退出之前是无法弥补的,所以要小心。(virtualMalloc的情况不在此描述了。因为分配的也不是heap上的内存,属于扩展内存空间,题外话了。)
知识点2:混合编程(引自百度百科)
混合编程是指使用两种或两种以上的程序设计语言来开发应用程序的过程,
- java与C++强强联合
C++调用JAVA主要用到了SUN公司的JNI技术, JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。 [1]
- C#和C++混合编程
由于历史原因,很多时候我们的代码并不完全是使用.NET写成的。这时候和以往C++代码的混合编程就显得相当重要了。一些常见问题的将方法简要记述如下。 [2]
- VC++与MATLAB混合编程
通过使用C/C++与Matlab混合编程,既可以享受到C代码快速执行的速度,又可以方便的使用Matlab众多的库函数和强大的绘图功能。让 Matlab调用C函数是通过DLL文件实现的,而这个DLL的开发过程不仅仅可以使用Matlab自带的mex命令,还可以使用VC++开发环境,使用 VC有很多好处,一是让我回到了原来熟悉的开发环境中,二能够使用更加标准的C++编译器,第三点也是最爽的,可以使用VC强大的调试功能。
重点:
- 混合编程的关键问题是参数传递。(这句话刚看百度百科的时候没感觉,单慢慢的就觉得这句话确实有道理)
知识点3:MarshalAs的使用
定义 Marshal Class
命名空间:System.Runtime.InteropServices
Assemblies:System.Runtime.InteropServices.dll, mscorlib.dll, netstandard.dll
提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。
具体详细内容参照微软官网:https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.interopservices.marshal?view=netframework-4.8
知识点4:c# c++类型转换(引用自https://blog.csdn.net/xqf222/article/details/5877795)
//C++中的DLL函数原型为
//extern “C” __declspec(dllexport) bool 方法名一(const char* 变量名1, unsigned char* 变量名2)
//extern “C” __declspec(dllexport) bool 方法名二(const unsigned char* 变量名1, char* 变量名2)
//C#调用C++的DLL搜集整理的所有数据类型转换方式,可能会有重复或者多种方案,自己多测试
//c++:HANDLE(void *) ---- c#:System.IntPtr
//c++:Byte(unsigned char) ---- c#:System.Byte
//c++:SHORT(short) ---- c#:System.Int16
//c++:WORD(unsigned short) ---- c#:System.UInt16
//c++:INT(int) ---- c#:System.Int16
//c++:INT(int) ---- c#:System.Int32
//c++:UINT(unsigned int) ---- c#:System.UInt16
//c++:UINT(unsigned int) ---- c#:System.UInt32
//c++:LONG(long) ---- c#:System.Int32
//c++:ULONG(unsigned long) ---- c#:System.UInt32
//c++:DWORD(unsigned long) ---- c#:System.UInt32
//c++:DECIMAL ---- c#:System.Decimal
//c++:BOOL(long) ---- c#:System.Boolean
//c++:CHAR(char) ---- c#:System.Char
//c++:LPSTR(char *) ---- c#:System.String
//c++:LPWSTR(wchar_t *) ---- c#:System.String
//c++:LPCSTR(const char *) ---- c#:System.String
//c++:LPCWSTR(const wchar_t *) ---- c#:System.String
//c++:PCAHR(char *) ---- c#:System.String
//c++:BSTR ---- c#:System.String
//c++:FLOAT(float) ---- c#:System.Single
//c++:DOUBLE(double) ---- c#:System.Double
//c++:VARIANT ---- c#:System.Object
//c++:PBYTE(byte *) ---- c#:System.Byte[]
//c++:BSTR ---- c#:StringBuilder
//c++:LPCTSTR ---- c#:StringBuilder
//c++:LPCTSTR ---- c#:string
//c++:LPTSTR ---- c#:[MarshalAs(UnmanagedType.LPTStr)] string
//c++:LPTSTR 输出变量名 ---- c#:StringBuilder 输出变量名
//c++:LPCWSTR ---- c#:IntPtr
//c++:BOOL ---- c#:bool
//c++:HMODULE ---- c#:IntPtr
//c++:HINSTANCE ---- c#:IntPtr
//c++:结构体 ---- c#:public struct 结构体{};
//c++:结构体 **变量名 ---- c#:out 变量名 //C#中提前申明一个结构体实例化后的变量名
//c++:结构体 &变量名 ---- c#:ref 结构体 变量名
//c++:WORD ---- c#:ushort
//c++:DWORD ---- c#:uint
//c++:DWORD ---- c#:int
//c++:UCHAR ---- c#:int
//c++:UCHAR ---- c#:byte
//c++:UCHAR* ---- c#:string
//c++:UCHAR* ---- c#:IntPtr
//c++:GUID ---- c#:Guid
//c++:Handle ---- c#:IntPtr
//c++:HWND ---- c#:IntPtr
//c++:DWORD ---- c#:int
//c++:COLORREF ---- c#:uint
//c++:unsigned char ---- c#:byte
//c++:unsigned char * ---- c#:ref byte
//c++:unsigned char * ---- c#:[MarshalAs(UnmanagedType.LPArray)] byte[]
//c++:unsigned char * ---- c#:[MarshalAs(UnmanagedType.LPArray)] Intptr
//c++:unsigned char & ---- c#:ref byte
//c++:unsigned char 变量名 ---- c#:byte 变量名
//c++:unsigned short 变量名 ---- c#:ushort 变量名
//c++:unsigned int 变量名 ---- c#:uint 变量名
//c++:unsigned long 变量名 ---- c#:ulong 变量名
//c++:char 变量名 ---- c#:byte 变量名 //C++中一个字符用一个字节表示,C#中一个字符用两个字节表示
//c++:char 数组名[数组大小] ---- c#:MarshalAs(UnmanagedType.ByValTStr, SizeConst = 数组大小)] public string 数组名; ushort
//c++:char * ---- c#:string //传入参数
//c++:char * ---- c#:StringBuilder//传出参数
//c++:char *变量名 ---- c#:ref string 变量名
//c++:char *输入变量名 ---- c#:string 输入变量名
//c++:char *输出变量名 ---- c#:[MarshalAs(UnmanagedType.LPStr)] StringBuilder 输出变量名
//c++:char ** ---- c#:string
//c++:char **变量名 ---- c#:ref string 变量名
//c++:const char * ---- c#:string
//c++:char[] ---- c#:string
//c++:char 变量名[数组大小] ---- c#:[MarshalAs(UnmanagedType.ByValTStr,SizeConst=数组大小)] public string 变量名;
//c++:struct 结构体名 *变量名 ---- c#:ref 结构体名 变量名
//c++:委托 变量名 ---- c#:委托 变量名
//c++:int ---- c#:int
//c++:int ---- c#:ref int
//c++:int & ---- c#:ref int
//c++:int * ---- c#:ref int //C#中调用前需定义int 变量名 = 0;
//c++:*int ---- c#:IntPtr
//c++:int32 PIPTR * ---- c#:int32[]
//c++:float PIPTR * ---- c#:float[]
//c++:double** 数组名 ---- c#:ref double 数组名
//c++:double*[] 数组名 ---- c#:ref double 数组名
//c++:long ---- c#:int
//c++:ulong ---- c#:int
//c++:UINT8 * ---- c#:ref byte //C#中调用前需定义byte 变量名 = new byte();
//c++:handle ---- c#:IntPtr
//c++:hwnd ---- c#:IntPtr
//c++:void * ---- c#:IntPtr
//c++:void * user_obj_param ---- c#:IntPtr user_obj_param
//c++:void * 对象名称 ---- c#:([MarshalAs(UnmanagedType.AsAny)]Object 对象名称
//c++:char, INT8, SBYTE, CHAR ---- c#:System.SByte
//c++:short, short int, INT16, SHORT ---- c#:System.Int16
//c++:int, long, long int, INT32, LONG32, BOOL , INT ---- c#:System.Int32
//c++:__int64, INT64, LONGLONG ---- c#:System.Int64
//c++:unsigned char, UINT8, UCHAR , BYTE ---- c#:System.Byte
//c++:unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t ---- c#:System.UInt16
//c++:unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT ---- c#:System.UInt32
//c++:unsigned __int64, UINT64, DWORDLONG, ULONGLONG ---- c#:System.UInt64
//c++:float, FLOAT ---- c#:System.Single
//c++:double, long double, DOUBLE ---- c#:System.Double
//Win32 Types ---- CLR Type
//Struct需要在C#里重新定义一个Struct
//CallBack回调函数需要封装在一个委托里,delegate static extern int FunCallBack(string str);
//unsigned char** ppImage替换成IntPtr ppImage
//int& nWidth替换成ref int nWidth
//int*, int&, 则都可用 ref int 对应
//双针指类型参数,可以用 ref IntPtr
//函数指针使用c++: typedef double (*fun_type1)(double); 对应 c#:public delegate double fun_type1(double);
//char* 的操作c++: char*; 对应 c#:StringBuilder;
//c#中使用指针:在需要使用指针的地方 加 unsafe
行动
以上知识点多多少少了解了解或者知道一下, 方便自己再使用的时候不那么迷茫,单其实真正了解里面多少,我不是很深入。因为没有写过c++,只是上学的时候写过什么九九乘法表,水仙花数什么之后的启蒙编程语言,真正使用的时候没有用过。所以在翻阅博客的时候大概明白了c++如何封装方法 封装dll,好像见过c++通过某种封装方式可以让c# 直接使用的,但我这个项目不可能要求人家格式,毕竟多家供应商都在使用