C#中使用OpenGL:(二)C#调用C/C++的dll

在C#中使用OpenGL图形库为业余的图形编程人员提供了很大的便利,可是官方并没有向用户提供C#版本的OpenGL图形接口,在民间有好一些人开发了C#版的OpenGL接口,使之能够在C#中使用。这些第三方的C#版OpenGL应该说用起来还是不错的,如果说有什么缺点的话,那应该是这些OpenGL的版本都不是最新的,一般在4.0以下,而现在OpenGL都4.6版本了。如果要使用最新的OpenGL图形接口,那还得自己多动动手。
C# OpenGL接口源码、C# OpenGL编程例子可在百度网盘下载:链接:https://pan.baidu.com/s/1dnIo1s-l6aqlE3IgaMJA5g 
提取码:wc0x

C#如何利用OpenGL?

官方的C#版本的OpenGL接口是不存在的,要想在C#中使用OpenGL,可有两种方法:从C语言的OpenGL动态链接库(opengl32.dll)中获取函数接口,或者直接从硬件驱动中获取函数指针。
如果只利用1.1版本的OpenGL,可以选择调用opengl32.dll里面的300多条函数。opengl32.dll可以在windows系统的系统盘里找到,其中的函数都是调用约定为stdcall的C函数;
如果要使用高版本的OpenGL,则需要到硬件驱动里获取函数指针。windows系统只支持1.1版本的OpenGL,而1.2之后的版本都不再支持,这意味着我们不能通过opengl32.dll这个库去调用新版本的函数,但是可以利用opengl32.dll中的wglGetProcAddress函数从显卡驱动中获取OpenGL函数指针,然后通过函数指针来调用相应的函数。
不管怎么样,要想在C#中使用OpenGL,调用opengl32.dll这个C语言动态链接库是在所难免的。因此特地花一些时间来研究C#如何调用C/C++函数。

C#如何调用C/C++的dll?

C#调用dll的方法一般由两种,分别是静态调用和动态调用。

C#静态调用dll的方法:

首先,引用名称空间System.Runtime.InteropServices。
其次,要声明一个外部的方法,其基本形式如下:

[DLLImport("XXX.dll")]修饰符 extern 返回变量类型 方法名称 (参数列表)

其中:
**1.DLLImport:**这是必不可少的,而且必须要有中括号“[]”括起来,它有若干可选的参数,叫DllImportAttribute。它至少有一个参数,这个必需的参数是dll的文件名。它也可以有多个参数,这些参数可以全部写,也可以只写一部分,要看具体情况。这些参数分别是:

*CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;
*SetLastError 指示方法是否保留 Win32"上一错误"SetLastError=true;
*ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;
*PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;
*CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Cdec。
*EntryPoint指明入口点,必须是dll中实际的函数名,该项不写的话,那么方法名称必须要与dll中的函数名称一致。

**2.修饰符:**访问修饰符,除了abstract以外,声明方法时可以使用的任意一种修饰符。如static ,private,public等。一般情况下是public+static一起使用。
**3.返回变量类型:**在DLL文件中你需调用方法的返回变量类型。
**4.方法名称:**在DLL文件中你需调用方法的名称。
**5.参数列表:**在DLL文件中你需调用方法的列表。

例子:

//引用名称空间
System.Runtime.InteropServices;

//导入外部函数
[DllImport("opengl32.dll",ExactSpelling =false,EntryPoint = "glBegin",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern  void glBegin(uint mode);

//指明了函数入口点,则方法名可以与函数名不一致。
[DllImport("opengl32.dll",ExactSpelling =false,EntryPoint = "glEnd",CharSet = CharSet.Auto,CallingConvention = CallingConvention.StdCall)]
public static extern  void End();

注意事项:
1.常见的错误是没有引用名称空间System.Runtime.InteropServices。
2.来自外部的函数,修饰符中必须要有static修饰。
3.调用约定必须要与dll文件中函数的调用约定一致,VC/VS一般默认的调用约定为Cdec,C#中默认的调用约定也为Cdec,如果dll文件的函数使用的是其他的调用约定,则C#中必须指明。OpenGL函数的调用约定是StdCall,因此CallingConvention = CallingConvention.StdCall。
4.C#可以顺利地调用C语言的dll,一般只要调用约定和入口点写得正确,调用是没有问题的。但如果dll是由C++生成的,那么就略显麻烦。由于C++的函数编译为dll时,函数名称会被改变,导致在C#中使用时会出现找不到入口点。解决办法是,使用depends工具查看dll中函数的名称,然后在C#将EntryPoint指定为该函数在dll中的名称。比如Add函数,用C++编译为dll时,它在dll中的名称是?Add@@YAHHH@Z,而不再是Add,因此必须设定EntryPoint = “?Add@@YAHHH@Z”。

C#动态调用dll的方法:

动态调用的好处就是需要时装载,不需要时可以释放。动态调用C的dll需要用到两个winAPI,分别是LoadLibrary和GetPrcAddress。它们的功能分别是将dll文件载入到内存和从内存中的dll中获取函数指针。除了要用到winAPI之外,还要用到C#的委托。使用winAPI获取函数指针,然后用委托来执行函数。下面通过示例代码来说明。

(1)首先声明外部函数

//参数为string类型,字符集应设为CharSet = CharSet.Ansi。
[DllImport("kernel32.dll",ExactSpelling =false,EntryPoint = "Loadlibrary",CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static IntPtr Loadlibrary(string fileName);

[DllImport("kernel32.dll",ExactSpelling =false,EntryPoint = "GetProcAddress",CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static IntPtr GetProcAddress(IntPtr hmodule,string functionName);

(2)声明委托(以glBegin函数为例)

internal delegate void FUNC(uint mode);

(3)动态调用opengl32.dll中的glBegin函数
声明了外部函数和委托之后,就可以动态调用dll了。

//载入opengl32.dll到内存
IntPtr hmodule=LoadLibrary("opengl32.dll");
//获得opengl32.dll中的glBegin函数指针
IntPtr funcPointer=GetProcAddress(hmodule,"glBegin");
//将函数指针转换为委托
FUNC glBegin=Marshal.GetDelegateForFunctionPointer(funcPointer,typeof(FUNC));
//执行函数
glBegin(mode);

C#调用C/C++函数时的参数传递问题

下表是C#与C/C++的参数类型的对应关系

C#C/C++OpenGL自定义类型
byteunsigned charGLubyte/GLboolean
sbytecharGLchar/GLbyte
shortshortGLshort
ushort、charunsigned shortGLushort
intint/longGLint/GLsizei
uintunsigned int/unsigend longGLuint/GLemun/GLbitfield/GLulong
longlong longGLint64
ulongunsigned long longGLuint64
floatflaotGLfloat/GLclampf
doubledoubleGLdouble/GLclampd
decimal
boolboolGlboolean
byte[]unsigned char*、unsigned char[]GLuchar*
sbyte[]char*/char[]GLchar*
数组数组/内存块指针指针
结构体结构体结构体
ref和out修饰的参数变量指针变量的指针
IntPtr对象句柄、指针指针

由于C#一般不用指针,C#中的函数参数传递遇到指针,可用数组替代,也可以用IntPtr替代。若函数返回值是指针,则不能用用数组接收,而要用C#的IntPtr类型的变量接收。IntPtr类型是用来代表指针或句柄的平台特定类型,实际上就相当于C语言的指针。
当然,对于一个熟练地使用C语言的程序员来说,不用指针就好像缺了什么东西一样。实际上,指针真的很好用,微软也是知道的,于是给C#留了一个后门。特殊情况下,C#也是可以像C语言一样使用指针。只要给使用指针的代码块用关键字unsafe标识,并且在工程中菜单栏“项目->属性”页面中勾选“允许不安全代码”,就可使用指针了。具体操作,可以参考百度经验:C#使用指针(不安全代码)

例子:

C语言中的函数定义

#include<stdlib.h>
//结构体
typedef struct MyStruct
{
	int x;
	int y;
}MyStruct;

//函数FUNC1,参数是数组,返回值是指针
_declspec(dllexport)int * FUNC1(int A[])
{
	int n;
	n = sizeof(A);
	int *B = (int*)malloc(n * sizeof(int));
	for (int i = 0;i < n;i++)
	{
		B[i] = A[i] + 1;
	}
	return B;
}
//函数FUNC2,参数有两个指针
_declspec(dllexport)void FUNC2(int *A,int n,int *B)
{
	for (int i = 0;i < n;i++)
	{
		B[i] = A[i] + 1;
	}
}
//函数FUNC3,参数是两个数组
_declspec(dllexport)void FUNC3(int A[],int B[])
{
	int n;
	n = sizeof(A);
	for (int i = 0;i < n;i++)
	{
		B[i] = A[i] + 1;
	}
}
//函数FYNC4,参数和返回值都是结构体
_declspec(dllexport)MyStruct FUNC4(MyStruct s)
{
	MyStruct *B = (MyStruct*)malloc(sizeof(MyStruct));
	(*B).x = s.x + 1;
	(*B).y = s.y + 1;
	return *B;
}

C#不使用指针调用和C语言的函数:

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC1", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr FUNC1(Int32[] a);

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC2", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void FUNC2(Int32[] a, Int32 n, Int32[] b);

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC3", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void FUNC3(Int32[] a, Int32[] b);

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC4", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
 public static extern MYSTRUCT FUNC4(MYSTRUCT S);

C#使用指针调用C语言的函数:

unsafe{
[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC1", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int* FUNC1(int[] a);

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC2", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void FUNC2(int* a, Int32 n,int* b);

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC3", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void FUNC3(Int32[] a, Int32[] b);

[DllImport("mydll.dll", ExactSpelling = false, EntryPoint = "FUNC4", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
 public static extern MYSTRUCT FUNC4(MYSTRUCT S);
}

结语

本文也没有真正进入“开发C#版OpenGLj接口”这个主题中,只是一个前期的技术储备。后期开发C#版的OpenGL接口,必然会用到以上这些知识。俗话说,磨刀不误砍柴工,有好的准备能很好地进入主题,所以在真正大刀阔斧开始干的时候,会花上一段时间进行技术储备。下一篇文章,将介绍如何将一个.lib文件直接编译为.dll文件。

上一篇:C#中使用OpenGL:(一)前面的话
下一篇:C#中使用OpenGL:(三)将.lib文件编译为.dll文件

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页