C#中使用OpenGL:(六)C#中调用C函数时的参数传递问题
来源:互联网 发布:iphone5越狱软件 编辑:程序博客网 时间:2024/05/17 19:20
C#中调用C函数,除了需要在C#中声明被调函数之外,还要考虑到参数传递的问题。虽然我在之前两篇文章中已经提到过如在C#中向C函数传递参数,但是在调用OpenGL函数时,仍然遇到不少难题,特别是关于指针方面。我试图在网络上搜索相关的方法,然而让人失望是,很多人的给出的是“为什么一定要在C#中使用指针呢?”之类的答案。额……,不是我偏爱指针,如果不是迫不得已,谁会在C#中使用指针呢! 为了解决C#调用OpenGL函数时的参数传递问题,我特地研究了两天,下面是一些成果。我的方法也许不是最好的,但基本能解决大部分问题。*
OpenGL函数的参数类型
总结了一下,OpenGL函数的参数类型有如下:
1.基本数据类型
int 、unsigned int、short、unsigned short、char、unsigned char、float、double。
2.复杂数据类型
数组、指针、二重指针、字符串、结构体、函数指针、空类型指针(void*)、句柄。
基本数据类型作为参数传递
基本数据类型作为参数传递比较容易,只要用C#相对应的数据类型替换即可。下表是C语言基本数据类型与C#基本数据类型的对应关系。
复杂数据类型作为参数传递
1.被调C函数参数为数组
C语言的数组与C#数组是相对应的。C语言函数以一维数组为参数,则C#也是传递一维数组;若C语言函数以二维数组为参数,则C#要传递二维数组。
例如:
C语言函数
//以一维数组为参数_declspec(dllexport)void FUNC1(int A[]){ int n=sizeof(A)/4; for (int i = 0;i< n;i++) { printf("%d ", A[i]); }}//以二维数组为参数_declspec(dllexport)void FUNC2(int A[][2]){ for(int i=0;i<2;i++) for(int j=0;j<2;j++) printf("%d ", A[i][j]);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;//调用C语言的dll需要引用此命名空间namespace Csharp调用C函数的参数传递{ class Program { //外部函数声明 [DllImport("mydll.dll", EntryPoint = "FUNC1", ,CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC1(int[] A); [DllImport("mydll.dll", EntryPoint = "FUNC2", CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC2(int[,] A); static void Main(string[] args) { int[] A1 = new int[4] { 1, 2, 2, 3 }; int[,] A2 = new int[2, 2] { { 1,2}, { 3,4} }; FUNC1(A1); FUNC2(A2); } }}
2.被调C函数的参数为普通指针
当C函数的参数为普通指针时,有两种情况,分别是“指向一般变量的指针”和“指向内存块的指针”。当然,这两种类型在C语言是没有区别的,都是指向首地址嘛。在C#中调用C函数时,才有稍稍的不同。
(1)指向一个变量的指针
如果C函数的参数一个指向单个变量的指针,则在C#中可以将一个变量的引用(ref)传递过去,或者把一个只有一个元素的数组传过去。
例如:
C函数代码
_declspec(dllexport)void FUNC3(int *A){ int c; c = *A; printf("%d ", c);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;//调用C语言的dll需要引用此命名空间namespace Csharp调用C函数的参数传递{ class Program { //传递变量的引用 [DllImport("mydll.dll", EntryPoint = "FUNC3", ,CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC3(ref int A); /*传递单个元素的数组 [DllImport("mydll.dll", EntryPoint = "FUNC3", CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC3(int[] A); */ static void Main(string[] args) { int[] A = new int[1]{1}; int d=1; FUNC3(ref d); /*或者 FUNC3(A); */ } }}
(2)指向一块内存的指针
C语言的普通指针可用C#的一维数组替代。int *p 作为参数,则C#中调用要传递int[] A。
例如:
C语言函数代码
_declspec(dllexport)void FUNC4(int *A,int n){ for(int i=0;i<n;i++) printf("%d ", A[i]);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;//调用C语言的dll需要引用此命名空间namespace Csharp调用C函数的参数传递{ class Program { [DllImport("mydll.dll", EntryPoint = "FUNC4", ,CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC4(int[] A,int n); static void Main(string[] args) { int[] A = new int[4]{1,2,3,4}; FUNC3(A,4); } }}
3.被调C函数的参数为结构体
如果被调C函数的参数是简单的结构体(不含数组和指针成员),那么和C#的结构体也是可以对应的。如果结构体包含了数组和指针成员,则要用到不安全代码才能解决。
例如:
C函数代码
struct MyStruct{ int x; double y;}_declspec(dllexport)void FUNC5(struct MyStruct s){ printf("%d %lf", s.x,s.y);}
C#调用C语言函数
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;//调用C语言的dll需要引用此命名空间namespace Csharp调用C函数的参数传递{ struct myStruct { public int x; public double y; }; class Program { [DllImport("mydll.dll", EntryPoint = "FUNC5", ,CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC5(int[] A,int n); static void Main(string[] args) { myStruct S; s.x=1; s.y=12.0; FUNC5(S); } }}
如果结构体包含数组成员,需要使用不安全代码。VS编译器要先勾选“允许使用不安全”才能编译通过。先在VS的菜单栏上的“项目->属性->生成”页面勾选“允许不安全代码”,然后在代码中使用unsafe关键字对不安全代码块进行标识。
例如:
C函数代码
struct myStruct{ int n; int data[10];};_declspec(dllexport)void FUNC6(struct myStruct s){ for(int i=0;i<s.n;i++) printf("%d ",s.data[i]);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;//调用C语言的dll需要引用此命名空间namespace Csharp调用C函数的参数传递{ unsafe struct myStruct { public int n; public fixed int data[10];//fixed关键字表示分配固定的空间 } class Program { [DllImport("mydll.dll", EntryPoint = "FUNC6", ,CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC6(myStruct s); static void Main(string[] args) { unsafe//不安全代码块 { myStruct S; S.n=10; for(inti=0;i<10;i++)S.data[i]; FUNC6(S); } }}
4.被调C函数的参数为二重指针
C语言的普通指针,在C#中可用一维数组替代(int[] A),但C语言的二重指针,在C#中不能用二维数组替代(int[,] A)。C语言的二重指针应该用C#的“数组的数组”,即交错数组来替代(int[][] A)。C#调用含有二重指针的C函数,我没有发现更好的办法,只能使用不安全代码,使用指针,即使是是这样,仍然感觉很麻烦。下面用一个例子来说明。
C函数代码
_declspec(dllexport)void FUNC7(int **A, int row,int col){ for (int i = 0;i<row;i++) for(int j=0;j<col;j++) printf("%d ", A[i][j]);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;namespace Csharp调用C函数的参数传递{ class Program { [DllImport("mydll.dll", EntryPoint = "FUNC7", CallingConvention = CallingConvention.Cdecl)] public static extern void FUNC7(IntPtr[] p,int row,int col); static void Main(string[] args) { //交错数组 int[][] a = new int[2][] { new int[2] { 1, 2 }, new int[2] { 3, 4 } }; unsafe { //IntPtr类型可等同于指针 IntPtr[] ptr = new IntPtr[2]; //循环将数组的指针存到IntPtr类型的数组中 for (int i = 0; i < 2; i++) { fixed (int* p = a[i])//取得数组指针必须要用fixed关键字先将数组内存固定 { ptr[i] = (IntPtr)p; } } FUNC7(ptr, 2, 2); } } }}
5.被调C函数的参数为空类型的指针(void*)
在C语言中,参数为空类型的指针,意味可以接受任何类型的指针。由于C语言没有函数重载这种功能,不能定义几个同名的函数,只能使用空类型的指针来接收任意类型的指针,以实现类似于C++中的函数重载的功能。在OpenGL中,有好多函数都是以空类型指针作为参数的。如:
//C语言函数void glGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, void *pixels);
参数pixels事实上可以是char类型的,也可以是float类型的,具体是什么类型的,由参数type决定。如果type为GL_CHAR,则函数按照char类型指针处理,如果是GL_FLOAT,则按照float类型处理。
如果是C++,可以利用函数重载来将一个函数,分解为几个同名的,但参数类型不一样的函数,这样就不需要参数type来限定pixels为何种类型的指针了。
如:
//C++函数重载void glGetTexImage (GLenum target, GLint level, GLenum format, GLchar *pixels);void glGetTexImage (GLenum target, GLint level, GLenum format, GLfloat *pixels);
C#和C++一样都是面向对象的语言,也有函数的重载。因此,我用函数重载的方式,将一个函数分解为几个同名的函数来解决空类型(void*)指针的问题。
例子:
C函数代码
//pixels可以是char、float或者short类型的指针void glGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, void *pixels);
C#调用C函数
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;namespace Csharp调用C函数的参数传递{ class Program {//函数重载一[DllImport("opengl32.dll", EntryPoint = "glGetTexImage ",CallingConvention=CallingConvention.StdCall)] public static extern void glGetTexImage (uint target,int level,uint format,uint type,byte[] pixels); //函数重载二 [DllImport("opengl32.dll", EntryPoint = "glGetTexImage",CallingConvention=CallingConvention.StdCall)] public static extern void glGetTexImage (uint target,int level,uint format,uint type,short[] pixels); //函数重载三[DllImport("opengl32.dll", EntryPoint = "glGetTexImage ",CallingConvention=CallingConvention.StdCall)] public static extern void glGetTexImage (uint target,int level,uint format,uint type,float[] pixels); static void Main(string[] args) { int[] A = new int[100]; short[] B = new short[100]; float[] C = new float[100]; glGetTexImage(1,0,1,0,A); glGetTexImage(1,0,2,1,B); glGetTexImage(1,0,3,2,C); } }}
6.被调C函数的参数为函数指针
C#中接收和传递函数指针都可以用Intptr类型的变量。即使是取得函数指针,也不能直接在C#中通过函数指针调用函数,而要通过委托来执行。关于函数指针,在OpenGL函数的参数中出现机会很少,但在调用高版本的OpenGL函数(1.3版本以上)总是需要通过函数指针来调用,后面会经常用到,所以要好好研究研究。下面的例子是通过wglGetAddress函数获取OpenGL函数指针,然后在C#中调用。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;namespace Csharp调用C函数的参数传递{ class Program { [DllImport("opengl32.dll", EntryPoint= "wglGetAddress",CharSet=CharSet.Ansi CallingConvention = CallingConvention.StdCall)] public static extern IntPtr wglGetAddress(string funcName); //声明一个委托 delegate void Color3f(float r,float g,float b); static void Main(string[] args) { //定义函数指针 IntPtr funcPointer; //定义委托 delegate glColor3f; //获得glColor3f的函数指针呢 funcPointer=wglGetAdress("glColor3f"); Type t=typeof(Color3f); //将函数指针转变为委托 glColor3f=Marshal.GetDelegateForFunctionPointer(funcPointer,t); //通过委托来执行函数 glColor3f(0.5f,0.5f,0.5f); } }}
7.被调函数的参数或返回值为对象句柄
这个对象句柄,可能是函数指针,或者窗口句柄,或者其他的模块句柄。在winAPI中,经常用到的是窗句柄(HWND),设备上下文句柄(HDC)等等。这种句柄类的变量,都可以用IntPtr类型来接收。例如:
winAPI
HDC GetDC(HWND win);
C#调用winAPI
//声明外部函数[DllImport("gdi32.dll", EntryPoint = "GetDC",CallingConvention=CallingConvention.StdCall)] public static extern IntPtr GetDC(IntPtr handle);//获得pictureBox的设备上下文IntPtr hdc;hdc=GetDC(pictureBox1.handle);
8.被调C函数的参数为字符串
C语言的字符串一般用const char*或者char str[]表示,C#中表示字符串的是string或者char[]。由于C语言的char类型是一个字节的(Ansi),而C#的char类型是两个字节的,string类型的元素也是两个字节的(Unicode),因此不能直接将C#的char[]和string直接作为参数传到C函数中。要想正确传递字符串,可以有以下两种方法。
(1)使用string和char[]作为参数传递
string类型和char[]字符数组是可以将字符串传递给C函数的,只不过在传递的时候需要在C#中将字符集(CharSet)设定为ansi,这样系统就知道应当把char类型处理成一个字节的,而不是两个字节。
例子:
C函数代码
_declspec(dllexport)void FUNC8(const char* str){ printf("%s", str);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;namespace Csharp调用C函数的参数传递{ class Program { [DllImport("mydll.dll",EntryPoint="FUNC8",CharSet=CharSet.Ansi,CallingConvention=CallingConvention.cdecl)] public static extern void FUNC8 (sting str); static void Main(string[] args) { string str="hello world"; FUNC8(str); } }}
(2)使用byte类型的数组作为字符串传递(byte[])
C#的byte类型是一个字节的,与C语言的char类型刚好对应,因此完全可以用byte替代C的char。在C#中可以先将string,char[]转为byte[],然后再往C函数中传送。
例子:
C函数代码
_declspec(dllexport)void FUNC8(const char* str){ printf("%s", str);}
C#调用C函数代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;namespace Csharp调用C函数的参数传递{ class Program { [DllImport("mydll.dll",EntryPoint="FUNC8",CallingConvention=CallingConvention.cdecl)] public static extern void FUNC8 (byte[] str); static void Main(string[] args) { string str="hello world"; byte[] s=Encoding.UTF8.GetBytes(str); FUNC8(s); } }}
结语
整整花了两天才把参数传递这个问题搞定,耽误了不少时间,但也为以后的项目扫清了道路。下一步的工作是在C#中搭建OpenGL渲染环境。
上一篇:C#中使用OpenGL:(五)1.1版本的OpenGL函数
下一篇:C#中使用OpenGL:(七)创建OpenGL渲染环境
- C#中使用OpenGL:(六)C#中调用C函数时的参数传递问题
- C#调用C函数(DLL)传递参数问题
- C#中使用OpenGL:(二)C#调用C/C++的dll
- C#调用C函数(DLL)传递参数问题
- C# 线程中使用Timer,并向其触发的事件(函数)中传递参数
- C# 线程中使用Timer,并向其触发的事件(函数)中传递参数
- C#中调用非托管的DLL及参数传递
- C#中调用非托管的DLL及参数传递
- 【c#】c#中 函数参数的注意事项
- C#中参数传递
- c#调用存储过程时 SqlParameter 中传递参数时ref参数的设置方法
- C#中数组作为参数传递的问题
- 关于C#中调用C++dll传递回调函数的问题
- C++函数调用中参数传递的问题
- C#中使用匿名函数解决EventHandler参数传递的难题
- 关于在C#中调用C++ DLL 时的参数传递
- C#中调用非托管的DLL及参数传递 (一)
- C#调用C++dll 结构体参数传递问题
- 转载:工作与人生中应熟练掌握的七个工具
- 大话性能优化
- C#中的两把双刃剑:抽象类和接口
- 删了maevn dependency包怎么找回找回
- 第8章 Java集合—Collection和Set集合
- C#中使用OpenGL:(六)C#中调用C函数时的参数传递问题
- java常见考题(上)
- 高效求最短路SPFA算法(C语言)
- 线上服务内存OOM问题定位三板斧
- 机器学习二(决策树)
- 思维导图使用技巧:手把手教你怎么画思维导图
- 2017 icpc 南宁赛区 B.Train Seats Reservation(水题)
- Django项目与Django应用的关系及django.apps模块
- android接入百度SDK