C#中使用OpenGL:(六)C#中调用C函数时的参数传递问题

C#中调用C函数,除了需要在C#中声明被调函数之外,还要考虑到参数传递的问题。虽然我在之前两篇文章中已经提到过如在C#中向C函数传递参数,但是在调用OpenGL函数时,仍然遇到不少难题,特别是关于指针方面。我试图在网络上搜索相关的方法,然而让人失望是,很多人的给出的是“为什么一定要在C#中使用指针呢?”之类的答案。额…,不是我偏爱指针,如果不是迫不得已,谁会在C#中使用指针呢! 为了解决C#调用OpenGL函数时的参数传递问题,我特地研究了两天,下面是一些成果。我的方法也许不是最好的,但基本能解决大部分问题。
C# OpenGL接口源码、C# OpenGL编程例子可在百度网盘下载:链接:https://pan.baidu.com/s/1dnIo1s-l6aqlE3IgaMJA5g 
提取码:wc0x

OpenGL函数的参数类型

总结了一下,OpenGL函数的参数类型有如下:

1.基本数据类型

int 、unsigned int、long long、unsigned long long、short、unsigned short、char、unsigned char、float、double。

2.复杂数据类型

数组、指针、二重指针、字符串、结构体、函数指针、空类型指针(void*)、句柄。

基本数据类型作为参数传递

基本数据类型作为参数传递比较简单,只要用C#相对应的数据类型替换即可。下表是C语言基本数据类型与C#基本数据类型的对应关系。

C语言C#语言
intint
unsigned intuint
long longInt64
unsigned long longUInt64
shortshort/char
unsigned shortushort
charsbyte
unsigned charbyte
floatfloat
doubledouble

复杂数据类型作为参数传递

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)指向一个变量的指针
指向单个变量的指针,通常用于接收函数中的返回值,比较常见的函数有glGet***之类的。针对这样的指针参数,在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。当然,使用IntPtr类型来代替指针也是可以的,不过需要用到不安全代码。
例如:
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类型处理。因为指针的类型不确定,比较难用确定类型的数组来传参。这种情况,有两种方案:第一是函数重载,即根据type的值不同而采用不同的函数;第二,用IntPtr类型来代表指针类型(需要用到不安全代码)。事实上,Intptr类型可以替代所有类型的指针、句柄和结构体。

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);
        }
    }
}

或者如下:

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,IntPtr pixels);
 
        static void Main(string[] args)
        {
	        
          byte[] A = new byte[1000];	
          IntPtr poniter;
          unsafe{
          fixed(byte* p=&A[0])poiter=(Intptr)p;
          }	  
		  glGetTexImage(1,0,1,0,pointer);
        }
    }
}

**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函数](http://blog.csdn.net/qq_28249373/article/details/78076406)
下一篇:[C#中使用OpenGL:(七)创建OpenGL渲染环境](http://blog.csdn.net/qq_28249373/article/details/78835336)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shifenglv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值