【混合编程】C#调用C++

来源:互联网 发布:历史大事年表知乎 编辑:程序博客网 时间:2024/06/17 03:20

之前在学校写写图个方便,现在上班了发现这种技术简直逆天,C++/CLR也写过,但是语法上就只好呵呵了,而且运行起来跟原生的C#感觉差很多。唯一的好处就是不用考虑托管与非托管的问题。


直接上代码吧,很实用

【C#端相关开发与配置】

<span style="white-space:pre"></span>static private IntPtr instance = IntPtr.Zero;<span style="white-space:pre"></span>//DLL实例变量,是个IntPtr        [DllImport("Kernel32.dll")]        public static extern IntPtr LoadLibrary(string lpFileName);<span style="white-space:pre"></span>//动态加载DLL的API        [DllImport("kernel32.dll", SetLastError = true)]        public static extern int GetProcAddress(IntPtr hModule, string lpProcName);<span style="white-space:pre"></span>//用于获取函数指针        [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]<span style="white-space:pre"></span>//动态卸载DLL的API        public static extern bool FreeLibrary(IntPtr hModule);
至此,是Windows平台的相关API导入。下面首先封装一个函数,获取函数指针的同时转为委托,因为C#里无法直接使用函数指针。

//获取函数指针        static private Delegate GetAddress(IntPtr dllModule, string functionname, Type t)        {            int addr = GetProcAddress(dllModule, functionname);<span style="white-space:pre"></span>//获取指针            if (addr == 0)                return null;            else                return Marshal.GetDelegateForFunctionPointer(new IntPtr(addr), t);<span style="white-space:pre"></span>//转成委托        }
然后就是生成所需函数对应的委托声明组:

 <span style="white-space:pre"></span>delegate int Delegate_int_initWithBmp(char[] filename,IntPtr memStream );<span style="white-space:pre"></span>//定义委托,注意签名与DLL函数要一直<span style="white-space:pre"></span>Delegate_int_initWithBmp VideoInitWithBmp;<span style="white-space:pre"></span>//声明一个委托变量
OK,以上都是C#端在加载函数前的准备工作。下面就开始加载了,比如,在初始化函数里这么写:

<span style="white-space:pre"></span>void DLLInit()<span style="white-space:pre"></span>//默认私有,如果需要被外部类调用注意相关函数公有制        {                        instance = LoadLibrary(Application.StartupPath + "\\" + "FFCodecSupport.dll");<span style="white-space:pre"></span><span style="font-family: Arial, Helvetica, sans-serif;">//加载DLL</span>            if (instance.ToInt32() == 0)            {                throw new Exception("加载失败,错误号:"+Marshal.GetLastWin32Error());            }            //获取版本号<span style="white-space:pre"></span>//版本号验证有时候兼容性检验还是需要的            //获取函数地址,即加载<span style="white-space:pre"></span><pre name="code" class="csharp"><span></span>//使用了一个函数可以省了好多代码,尤其是在需要加载很多函数的时候。
VideoInitWithBmp = (Delegate_int_initWithBmp)GetAddress(instance, "InitWithBmp", typeof(Delegate_int_initWithBmp));}



【C++端相关开发与配置】

建立工程,当然是DLL,记得勾选导出符号(X)选项,这样后面方便点。

然后会看到工程里有这么几个文件:dllmain.cpp、FFCodecSupport.cpp、stdafx.cpp、FFCodecSupport.def、FFCodecSupport.h、stdafx.h、targetver.h

许多文件都不需要动它,FFCodecSupport.cpp、FFCodecSupport.def、FFCodecSupport.h这三个是需要重点修改的。

FFCodecSupport.cpp

里主要写被调用的函数实现,写不写到类里都无所谓,写了反而麻烦。做好内存回收工作!因为GC真的管不到这儿!关键的地方就是函数签名:

不对外开放的函数签名该怎么来怎么来,需要被上层调用的函数签名得这么写:

FFCODECSUPPORT_API char * CALLBACK InitWithBmp(char * filename);
InitWithBmp需要与上面GetAddress函数的第二个参数一样,

FFCODECSUPPORT_API 实际上是 #define FFCODECSUPPORT_API __declspec(dllexport)

FFCodecSupport.def

是导出符号表,像这样写:

LIBRARY "FFCodecSupport"EXPORTSInitWithBmp
首行是库名,次行固定内容,第三行开始每一行写一个需要被上层调用的导出函数

FFCodecSupport.h

文件主要就是写函数签名,跟普通的头文件编写没什么太大区别,多注意些语法上的问题,比如typedef一般放在最前面,这个大家都懂的。


最后分享一些用过的传参参数表(仅供参考)

char *可直接对应char[]

void *用IntPtr,不过肯定要用unsafe了

byte **之类的二阶指针,最好还是用IntPtr

比如IntPtr Addr,Addr对应byte *,而&Addr对应byte **,但是传参的时候都用IntPtr。用的时候要格外小心就是了。

Dispose(void *ptr)这种直接传任意指针参数,然后delete ptr可以有效释放,但要注意是一阶指针,二阶的需要自己转成一阶的。


【其它一些语言共性】

C#与C++的输出目录要一致,可以省很多事。特别是C#上不需要配置任何东西,你只需要写一个Wrapper,因为LoadLibrary可以直接从当前路径搜索DLL。

0 0