您的位置:首页 > 编程语言 > C语言/C++

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

2017-08-15 19:59 344 查看
在C#中使用OpenGL图形库为业余的图形编程人员提供了很大的便利,可是官方并没有向用户提供C#版本的OpenGL图形接口,在民间有好一些人开发了C#版的OpenGL接口,使之能够在C#中使用。这些第三方的C#版OpenGL应该说用起来还是不错的,如果说有什么缺点的话,那应该是这些OpenGL的版本都不是最新的,一般在4.0以下,而现在OpenGL都4.6版本了。如果要使用最新的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++
byteunsigned char
sbytechar
shortshort
ushort、charunsigned short
intint、long
uintunsigned int、unsigend long
longlong long
ulongunsigned long long
floatflaot
doubledouble
decimal
boolbool
byte[]const char*、char[]
数组数组
结构体结构体
数组指向内存块的指针
一个元素的数组指向一个基本变量的指针
IntPtr对象句柄、指针
由于C#一般不用指针,C#中的函数参数传递遇到指针,可用数组替代。若函数返回值是指针,则不能用用数组接收,而要用C#的IntPtr类型的变量接收。IntPtr类型是用来代表指针或句柄的平台特定类型,实际上就相当于C语言的指针。C#可以接收C语言函数返回的指针,但不能通过指针来操作内存,所以,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文件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息