[DllImport("ScreenCaptureLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Init();
[DllImport("ScreenCaptureLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ScreenCapture(IntPtr img);

C#调用应该尽量避免用引用型的数组传递,因为c#里的数组不是连续的内存空间,c#里如果数组类型是简单类型那内存就是连续的。如果内存是引用类型那内存不是连续的,只是一个引用地址的数组,传给c++后c++操作连续内存空间c#里是得不到的。

C#的简单值类型数组传给c++的时候可以在c++端直接写指针,在c#端写数组,如下是成立的

int ScreenCapture(Pixcel (* image)[][][], int (* xyDataP)[], double (* scaleDataP)[]){
}
[DllImport("ScreenCaptureLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ScreenCapture(IntPtr img, int[] xyData, double[] scaleData);

但是只能做到单项传递,即c#数组传给c++,如果在c++中改变c#数组中的值,c#中的值是不会跟着变得。

如果想在c++改c#中的数组,只能用IntPtr p = Marshal.AllocHGlobal(3 * 20 * 240 * 240);申请空间,然后把IntPtr传给c++,ScreenCapture(p,xyData,scaleData);,然后用Marshal.Copy(p, imageData, 0, 20 * 240 * 240 * 3);,把值拷贝给c#数组。

参照最后一个项目:http://www.cnblogs.com/wangjixianyun/archive/2013/04/10/3012556.html

传参:

转自:http://blog.csdn.net/Mittermeyer/article/details/1586867

0、前言
从VB到C#,被人诟病比较多的就是交互性比较差,又集中表现在调用Win32 API上。如果说C/C++调用API只是调用函数这类轻松的活,在C#下却成了阻挡入门者的技术活。之所以产生这么大区别在于数据类型的差异,就是因为C#这类采用了“安全”的类型,我们避免了内存释放和内存访问错误的一些困扰,但是不得不面对调用API时的繁琐。有得必有失,关键看你选择了什么。
在调用API时,对于值类型的数据,不存在什么转换问题,只要搞清楚到底是Byte、Int16、Int32 还是Int64就可以了,比较麻烦的地方是指针,因为C#中没有办法显性的使用指针,有时需要借助unsafe code达到这个目的。如果都“unsafe”了,那还用C#干吗,本文的目的就是总结一下,怎样用“safe”的方式解决Win32 API中指针类型参数的问题。
 
1、 基本原则
在我们在调用API时,如果发现参数中有指针类型的时候,不要简单的用IntPtr去替换,或者直接就是用*来定义。虽然C#中能够使用指针,但是这样做就违背了C#设计时的初衷,此外DotNET Framework平台下使用unsafe代码多少会影响应用程序的效率。
当我们拿到一个API,阅读API的说明时,一定要关注以下几点:
l 每一个参数的数据类型是什么?如果是指针,指针指向的是一个什么数据结构,基本数据类型、字符串、结构还就是一块内存。不同的类型在C#下处理的模式是不同的。
l 指针所指向的数据结构是谁创建,该由谁释放?这也非常重要,它两层含义:一个是我们怎么定义接口,并且准备调用参数;另一个就是资源释放的问题,某些调用这申请,被调用这释放的资源,需要约定的方法申请或释放资源,反之亦然。
只要花点时间分析一下,就会发现即便是在复杂的结构,不用“unsafe code”也能够完成调用,只不过有时候过程有点繁琐,不如C/C++调用API那么畅快淋漓。但是我想说的是,如果选择了C#,那么就是C#的思想去解决问题,这样才能够发挥出C#所有的潜力。
 
2、实例分析
了解了基本原则,下面就逐一分析一下怎样雅致并且“安全”地解决不同类型指针的调用问题。
2.1、          字符串
字符串应该是我们接触到最多的情况,一般在API定义中被描述为“LPSTR/LPTSTR/LPCTSTR/LPWSTR”之类,我们在申明API接口的时候,如果是传入类型的参数,直接用String类型申明即可,例如:
        ///<summary>
        ///原型是:HMODULE LoadLibrary(LPCTSTR lpFileName); 
        ///</summary>
        ///<param name="lpFileName">DLL 文件名</param>
        ///<returns>函数库模块的句柄</returns>
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string lpFileName);
但是如果是传出类型的字符串参数,简单的这么写就不行了。我的理解是String变成LPSTR,是DotNET Framework的交互接口帮我们做了一次转换,创建了一个字符数组,将我们提供的String复制了一次,再传递给API,并非简单的指针传递,所以当我们要求在我们设定的一个地址区域去写数据时,就不能够直接申明为String,而应该是Byte或者Char数组,可以参考下面的例子:
函数声明:
 
        ///<summary>
        /// int GetClassName(HWND hWnd, LPTSTR lpClassName, int nMaxCount); 
        ///</summary>
        [DllImport("user32",CharSet=CharSet.Ansi)]
        public static extern Int32 GetClassName(IntPtr hwnd, Byte[] lpClassName, Int32 nMaxCount);
         调用事例:
         String sClassName = null;
         Byte[] abClassName = null;
         Int32 dwRet = 0;
 
         abClassName = new Byte[100];
         dwRet = GetClassName(this.Handle, abClassName, 100);
         sClassName = System.Text.ASCIIEncoding.ASCII.GetString(abClassName,0,dwRet);
         MessageBox.Show(sClassName);
还需要注意一点的就是Ansi还是Unicode的字符集了,申明的是什么就用什么转换。
 
2.2、          句柄—Handle
句柄严格意义上来说不能归在指针这一类,句柄是本宏定义掩盖了的一种数据结构,不过行为上和指针有些类似。最常见的有窗口句柄、Socket句柄还有内核对象的句柄等。总之H开头的一些定义基本都是句柄。
对于句柄来说我们通常无法直接访问句柄所代表的那个数据结构,只要记录句柄值就可以了,而且我们并不关心句柄这个值的内容,只要他有效就行了,所以句柄最容易处理。一般Win32下,句柄就是一个32位的整型,所以用Int32/UInt32或者IntPtr申明即可。还是上面那个例子,HMODULE就是一个句柄。
 
2.3、          基本类型的指针
两种情况下会出现基本类型的指针:一种是基本类型的地址,表示返回类型的参数;一种是表示传递一个基本类型的数组,这两种情况需要分别对待。
返回类型,C#中有专门的修饰符ref,表示参数传递按地址传送。缺省情况下参数都是按值传递的,如果希望按照地址传递,只要在参数前添加ref的修饰符即可。例如:
        ///<summary>
        ///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); 
        ///</summary>
        [DllImport("kernel32.dll")]
        public extern static Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead, ref Int32 lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);
对于C/C++中数组参数,就是一块连续内存的首地址。在C#中的数组默认都是从Array派生的类,虽然结构复杂了,但是内存布局应该是相同的,所以只要把参数定义为基本类型的数组就可以了。例如:
        ///<summary>
        /// BOOL GetCharWidth(HDC hdc,UINT iFirstChar,UINT iLastChar,LPINT lpBuffer);
        ///</summary>
        [DllImport("gdi32")]
        public static extern Int32 GetCharWidth(HDC hdc, Int32 wFirstChar, Int32 wLastChar, int32[] lpBuffer);
 
2.4、          结构
说到结构,先要解释一下C#中数据类型的分类。C#中的数据类型一般有两种,一种是值类型,就是Byte、Int32之流,出于反射的需要,值类型都是从ValueType派生而得;一种是引用类型,从Object派生出来的类都是引用类型。所谓值类型,就是赋值和传递了传的是数据本身,引用类型传递的是数据所对应实例的引用,C#中结构(以struct定义的)是值类型的,类(以class定义的)是引用类型的。
实际调用API时,API参数如果是一个自定义结构指针的话,通常把数据结构定义为struct,在申明时函数接口时用ref修饰。例如Guid就是DotNET类库中内建的一个结构,具体用法如下:
        ///<summary>
        ///原形:HRESULT WINAPI GetDeviceID(LPCGUID pGuidSrc, LPGUID pGuidDest);
        ///</summary>
        ///<param name="pGuidSrc"></param>
        ///<param name="pGuidDest"></param>
        ///<returns></returns>
        [DllImport("Dsound.dll")]
        private static extern Int32 GetDeviceID(ref Guid pGuidSrc, ref Guid pGuidDest);
如果自定义结构的话,结构在内存中占据的字节数务必要匹配,当结构中包含数组的时候需要用MarshalAsAttribute属性进行修饰,设定数组长度。具体可以参考下面的例子:
        ///<summary>
        ///原形:
        /// typedef struct tagPAINTSTRUCT { 
        ///     HDC hdc; 
        ///     BOOL fErase; 
        ///     RECT rcPaint; 
        ///     BOOL fRestore; 
        ///     BOOL fIncUpdate; 
        ///     BYTE rgbReserved[32]; 
        /// } PAINTSTRUCT;
        ///</summary>
        public struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public Boolean fErase;
            public RECT rcPaint;
            public Boolean fRestore;
            public Boolean fIncUpdate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]public Byte[] rgbReserved;
        }
 
        ///<summary>
        ///原形:HDC BeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint);
        ///</summary>
        [DllImport("user32")]
        public static extern IntPtr BeginPaint(IntPtr hwnd, ref PAINTSTRUCT lpPaint);
2.5、          结构数组
结构数组就比较复杂了,就我个人的经验,不同厂商提供的API,实现不同,需要采用的处理方式也不相同的。
一般情况下,参照基本类型数组的调用方式即可,例如:
        ///<summary>
        ///原形:
        /// typedef struct tagACCEL {
        ///     BYTE fVirt;
        ///     WORD key;
        ///     WORD cmd;
        /// } ACCEL, *LPACCEL;
        ///</summary>
        public struct ACCEL
        {
            public Byte fVirt;
            public UInt16 key;
            public UInt16 cmd;
        }
        ///<summary>
        ///原形:int CopyAcceleratorTable(HACCEL hAccelSrc,LPACCEL lpAccelDst,int cAccelEntries);
        ///</summary>
        ///<returns></returns>
        [DllImport("user32")]
        public static extern Int32 CopyAcceleratorTable(IntPtr hAccelSrc, ACCEL[] lpAccelDst, Int32 cAccelEntries);
但是也有特殊情况,对些厂商提供的API中,不知是否和内存复制的方式有关,类似的函数,如果采用上面相同的定义方法调用的话,调用正确,但是应该返回的数据没有被改写。这个时候就需要另一种方法来解决了。
众所周知,在逻辑上结构是一段连续的内存,数组也是一段连续内存,我们可以从堆中直接申请一段内存,调用API,然后将返回的数据再转换成结构即可。具体可以参看下面的例子。
结构定义以及API声明:
        [StructLayout(LayoutKind.Sequential, Pack = 8)]
        private struct CmBoxInfo
        {
            public static CmBoxInfo Empty = new CmBoxInfo();
 
            public byte MajorVersion;
            public byte MinorVersion;
            public ushort BoxMask;
            public uint SerialNumber;
            public ushort BoxKeyId;
            public ushort UserKeyId;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte[] BoxPublicKey;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte[] SerialPublicKey;
            public uint Reserve;
 
            public void Init()
            {
                BoxPublicKey = new byte[CM_PUBLIC_KEY_LEN];
                Debug.Assert(BoxPublicKey != null);
                SerialPublicKey = new byte[CM_PUBLIC_KEY_LEN];
                Debug.Assert(SerialPublicKey != null);
            }
        }
        ///<summary>
        ///原型:int CMAPIENTRY CmGetBoxes(HCMSysEntry hcmse, unsigned long idPort, CMBOXINFO *pcmBoxInfo, unsigned int cbBoxInfo) 
        ///</summary>
        [DllImport("xyz.dll")]
        private static extern Int32 CmGetBoxes(IntPtr hcmse, CmGetBoxesOption idPort,IntPtr pcmBoxInfo, Int32 cbBoxInfo);
调用示例
            IntPtr hcmBoxes = IntPtr.Zero;
            CmAccess cma = new CmAccess();
            CmBoxInfo[] aBoxList = null;
            Int32 dwBoxNum = 0, dwLoop = 0,dwBoxInfoSize = 0;
            IntPtr pBoxInfo = IntPtr.Zero;
 
            dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, IntPtr.Zero, 0);
            if (dwBoxNum > 0)
            {
                aBoxList = new CmBoxInfo[dwBoxNum];
                if (aBoxList != null)
                {
                    dwBoxInfoSize = Marshal.SizeOf(aBoxList[0]);
                    pBoxInfo = Marshal.AllocHGlobal(dwBoxInfoSize * dwBoxNum);
                    if (pBoxInfo != IntPtr.Zero)
                    {
                        dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, pBoxInfo, dwBoxNum);
                        for (dwLoop = 0; dwLoop < dwBoxNum; dwLoop++)
                        {
                            aBoxList[dwLoop] = (CmBoxInfo)Marshal.PtrToStructure((IntPtr)((UInt32)pBoxInfo + dwBoxInfoSize * dwLoop), CmBoxInfo.Empty.GetType());
                        }
                        Marshal.FreeHGlobal(pBoxInfo);
                        pBoxInfo = IntPtr.Zero;
                    }
                    else
                    {
                        aBoxList = null;
                    }
                }
            }
       最后提一句,Marshal类非常有用,其中包括了大量内存申请、复制和类型转换的函数,灵活运用的话,基本上可以避免unsafe code。
 
2.6、          函数指针(回调函数)
C#中采用委托(delegate)和函数指针等同的功能,当API函数的参数为回调函数时,我们通常使用委托来替代。与C和C++ 中的函数指针相比,委托实际上是具体一个Delegate派生类的实例,它还包括了对参数和返回值,类型安全的检查。
先看一下下面的例子:
        ///<summary>
        ///原形:typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID);
        ///</summary>
        public delegate Boolean LPDSENUMCALLBACK(IntPtr guid, String sDesc, String sDevName, ref Int32 dwFlag);
        ///<summary>
        ///原形:HRESULT WINAPI DirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
        ///</summary>
        [DllImport("Dsound.dll")]
        public static extern Int32 DirectSoundCaptureEnumerate(LPDSENUMCALLBACK pDSEnumCallBack, ref Int32 dwFlag);
具体调用方法如下:
          dwRet = DirectSoundEnumerate(new LPDSENUMCALLBACK(DSoundEnumCallback),ref dwFlag);
 
       这里需要特别注意的就是委托实际上是一个实例,和普通的类实例一样,是被DotNET Framework垃圾收集机制所管理,有生存周期的。上文例子的定义方式其实函数级别的局部变量,当函数结束时,将被释放,如果回调仍然在继续的话,就会产生诸如非法访问的错误。所以在使用回调函数的时候一定要比较清楚的了解,回调的作用周期是多大,如果回调是全局的,那么定义一个全局的委托变量作为参数。
 
2.7、          表示多种类型的指针—LPVOID以及其它
指针是C/C++的精髓所在,一个void能够应付所有的问题,我们遇到最多的可能就是LPVOID这样的参数。LPVOID最常用的有两种情况,一种就是表示一个内存块,另一种情况可能是根据其它参数的定义指向不同的数据结构。
第一种情况很好处理,如果是一个内存块,我们可以他当作一个Byte数组就可以了,例如:

        ///<summary>
        ///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); 
        ///</summary>
        [DllImport("kernel32.dll")]
        public extern static Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead, ref Int32 lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);
第二种情况比较复杂,C#中类型转换是有限制的,一个Int32是没法直接转换成为Point的,这个时候之能够根据不同的参数类型定义不同的重载函数了。例如GetProcAddress函数的lpProcName既可以是一个字符串表示函数名,又可以是一个高字为0的Int32类型,表示函数的序号,我们可以这样分别定义:
        ///<summary>
        ///原型是: FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
        ///</summary>
        [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
        private extern static IntPtr GetProcAddress(IntPtr hModule, String sFuncName);
        [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
        private extern static IntPtr GetProcAddressByIndex(IntPtr hModule, Int32 dwIndex);
 
在这里总结了调用API时有关指针的一些常见问题,你会发现大多数情况下C#依靠自身的能力就能解决问题,希望对大家有帮助。

c#和c++互操作(平台调用相关)的更多相关文章

  1. C# CLRInsideOut 托管代码与非托管代码互操作,产生相关调用代码的好工具 C++ 头文件转C# 的好工具(转

    http://www.cnblogs.com/jxsoft/archive/2011/08/04/2127250.html

  2. CA1060

    Move P/Invokes to NativeMethods class 规则描述: 平台调用服务访问非托管代码. 平台调用方法(使用了System.Runtime.InteropServices. ...

  3. 【转帖】C# DllImport 系统调用使用详解 托管代码的介绍 EntryPoint的使用

    1      DLLImport的使用 using System; using System.Runtime.InteropServices; //命名空间 class Example { //用Dl ...

  4. 嵌入式单片机STM32应用技术(课本)

    目录SAIU R20 1 6 第1页第1 章. 初识STM32..................................................................... ...

  5. DICOM标准相关资料

    由于需要阅读影像,对DICOM需要先熟悉起来.关于DICOM,找了一些资料,可以学习.如下: DICOM标准:http://dicom.nema.org/standard.html 中文 DICOM ...

  6. Android平台调用WebService详解

    上篇文章已经对Web Service及其相关知识进行了介绍(Android开发之WebService介绍 ),相信有的朋友已经忍耐不住想试试在Android应用中调用Web Service.本文将通过 ...

  7. JAVA面试题相关基础知识

        1.面向对象的特征有哪些方面 ①抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节 ...

  8. VS C# 嵌入互操作类型

    SQLDMO.Restore oRestore = new SQLDMO.RestoreClass(); SQLDMO.SQLServer oSQLServer = new SQLDMO.SQLSer ...

  9. 2014Esri全球用户大会之标准和互操作

    1.   ArcGIS是一个开放协作的平台吗?Esri是怎样看待"开放"的? 是的,ArcGIS是一个开放协作的平台.Esri公司致力于支持我们的用户更加easy的在异构环境下工作 ...

随机推荐

  1. Django学习笔记之Django Form表单

    Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否 ...

  2. OpenGL核心技术之Gamma校正

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你/2.2次幂.Gamma校正后的暗红色就会成为(0.5,0.0 ...

  3. 使用buildroot创建自己的交叉编译工具链【转】

    本文转载自:https://blog.csdn.net/linczone/article/details/45894181 使用buildroot创建自己的交叉编译工具链 关键字:buildroot ...

  4. 使用shiro缓存用户身份信息的时候报:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource

    最近在使用shiro缓存用户的身份信息的时候,报了simpleByteSource不能序列化,跟进源码一看,原来这个类没有实现序列化的接口,但是我在缓存身份信息的实现又要用到这个类,解决方法:重写一个 ...

  5. How to create and manage configuration backups in Internet Information Services 7.0

    https://support.microsoft.com/en-us/help/954872/how-to-create-and-manage-configuration-backups-in-in ...

  6. LuManager升级php5.6.27方法

    LuManager自带的PHP在FastCGI模式是 5.2.17版,非FastCGI模式是 5.3.14版.由于360网盘的停服,已不敢再用那些免费的网盘,干脆自己搭个私有云.查了下相关资料准备先试 ...

  7. 5分钟理解Centos7防火墙firewalld

    版权声明:本内容为原创内容,转载请声明出处. 原文地址:http://www.excelib.com/article/287/show firewalld简介 Centos7中默认将原来的防火墙ipt ...

  8. Android -- listview的使用, Inflater打气筒创建View对象,三种数据Adapter

    1. 代码示例,( 数据库来源上篇的数据) MainActivity.java public class MainActivity extends Activity { private ListVie ...

  9. linux下gzip压缩同样内容大小不一样

    一份数据,两种传输方式进行收集. 一份数据:有多台数据采集节点或者多个数据源 两种方式:一种是从依次多个采集节点或者多个数据源将数据拷贝过来,合并为一个文件 另外一种是多个采集节点或者数据源同时向汇总 ...

  10. Binary Differences

    https://csacademy.com/contest/archive/task/binary-differences n个数,只有0和1,求所有子区间价值不相同的有多少中,价值是0的个数-1的个 ...