C#调用C++一些技巧

这段时间经常用C#调用自己写的和别人写的dll,有一点感觉,今天下午随手记下:

---------------------------------------------------CLRInsideOut---------------------------------------------------

1、C#调用C++,自己少不了一个工具CLRInsideOut,这个工具切到第三个选项卡,把C++代码(结构体,导出函数)粘上,生成一下,右边出现相应c#代码。

   public const string dllName = "test.dll";

     [System.Runtime.InteropServices.DllImportAttribute(dllName, EntryPoint = "HIK_DVR_SetLogPath", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        public static extern bool HIK_DVR_SetLogPath(string savepath);

记得把dllname的位置改一下,还有C++一致导出stdcall.

2、CLRInsideOut蛮神奇的,特别是对C++位域的转换,它的思路是对的,不过一般转出来都是错的,我们可以学着它思路,自己对齐好位,这就要去回顾一下各种类型的长度了。

3、总而言之CLRInsideOut生成的东西,有时候会有问题,但是它就像一个小老师一样,教你怎么pinvoke.

-------------------------------------------小技巧--------------------

技巧1:wrapper

一般你可以拿到dll,lib,h,so等文件,这些SDK往往是偏向于被C++调用的,所以你直接去pinvoke它当然可以,不过写起来古古怪的。最好加一层自己的wrapper,本文基于这样的方式。这样的wrapper更适合java,c#的语言风格,方便使用。

技巧2:最好都是由C#来分配内存

按照谁分配谁释放原则,编写wrapper层的时候,最好不管理内存,内存统一由C#来分配,C#有垃圾回收,可以省去不少时间,但要注意垃圾回收带来的内存移位问题。

技巧3:如何返回不定长对象数组

比如有一个查找日志的api是这样的,它的第4个参数是一个数组指针,指向一个日志对象数组,第五个是数组有效元素个数。

HIK_API BOOL __stdcall SearchLog(LONG lUserID, LPF_TIMESTRU startTime,LPF_TIMESTRU stopTime,LPF_DVR_LOG logs,int* Count)

  

=〉

    [System.Runtime.InteropServices.DllImportAttribute(dllName, EntryPoint = "SearchLog")]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        public static extern bool SearchLog(int lUserID, ref F_TIMESTRU startTime, ref F_TIMESTRU stopTime, [Out] F_DVR_LOG[] logs, ref int count);

 

哦,我加了一个[Out]这个东西,这样就可以返回了,但是内存是在C#分配的。

 F_DVR_LOG[] logs = new F_DVR_LOG[10000];
             int count=0;
             HikDll.SearchLog(userid, ref struStartTime, ref struStopTime, logs,ref count);
             var allLogs =( from x in logs.Take(count).ToList()
                            select new { StartTime = x.StartTime, MajorType = x.MajorType, MinorType = x.MinorType, Info = x.Info,PanelUser=x.panelUser, NetUser = x.netUser }).ToList();
             dataGridView1.DataSource = allLogs;


先在C#分配10000个元素(应该够用了),用linq 的take 把数组压缩一下基本上可以用了。

 

技巧4:如何使用回调

c++-----------------------------------

typedef void (CALLBACK *tagFDVRException) (int code);
tagFDVRException FDVRException;//定制报警回调

//设置异常回调
HIK_API BOOL __stdcall HIK_DVR_SetExceptionCallBack(tagFDVRException callback){}

 

c#申明如下:------------------------------------

[System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(System.Runtime.InteropServices.CallingConvention.StdCall)]
        public delegate void tagFDVRException(int code);

        //设置异常回调
        [System.Runtime.InteropServices.DllImportAttribute(dllName, EntryPoint = "HIK_DVR_SetExceptionCallBack", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        public static extern bool HIK_DVR_SetExceptionCallBack(tagFDVRException callback);

 

 

如下使用即可:------------------------------------

 

HikDll.HIK_DVR_SetExceptionCallBack((code) =>
            {
                switch (code)
                {
                    case 7:
                        MessageBox.Show("设备下线了");
                        break;
                    default:
                        break;
                }
            });

 回调也可以返回数组,而且这个数组可以是C++的栈内变量,不存在回收问题,但是为了传回一个不定长对象数组,要专门写一个回调。这种解决方法与上面说的都可以使用,回调方式多走一步,但是不用在c#端开劈大空间。

 

技巧5:如何传入传出对象、结构体

C++如下:有个结构体obj

struct obj
{
    int i;
};

extern "C" DLL_API void __stdcall test(obj* inobj,obj* outobj) //5.给C#调用要加上extern "C"。
{
    outobj->i=inobj->i+1;
}

对应的C#

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct obj
    {
        public int i;
    }

    [System.Runtime.InteropServices.DllImportAttribute("CsharpInvokeDll.dll", EntryPoint = "test", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        public static extern void test(ref obj inobj, ref obj outobj);
        private void button1_Click(object sender, EventArgs e)
        {
            obj inobj = new obj();
            inobj.i = 1;
            obj outobj = new obj();
            test(ref inobj, ref outobj);
        }

注意:其中的红色ref,不管是输入还是输出,都要加ref。

 

技巧6:如何传入、传出一个字符串char *

例如我在C++中写到,test函数有两个char*参数,instr是输入,outstr是输出:

extern "C" DLL_API void __stdcall test(char* instr,char * outstr) 
{
    sprintf(outstr,"你输入了:%s",instr);
}

 

那在c#可以如下:注意:都未加ref,instr的对应string ,outstr的对应StringBuilder.

static extern void test(string instr,StringBuilder outstr);

 

如下调用:

           StringBuilder outstr = new StringBuilder(10);
            string instr = "wulala";
            test(instr,outstr);

 

技巧6:如何联调。
在C#的项目属性中如下设置:

:将C++的代码生成dll生成路径直接设置到到C#的debug目录下。这样在C++代码里打断点,每次编译,即可从C#调试到C++代码,不用再拷贝一次dll。

 


 

 

 

 

posted @ 2013-07-04 15:05  wulala  阅读(759)  评论(0编辑  收藏  举报