使用 C# 获取 OpenGL 版本的最简单方法

Easiest way to get the OpenGL Version with C#

本文关键字:最简单 方法 版本 OpenGL 获取 使用      更新时间:2023-10-16

我使用使用OpenGL的外部C++DLL。但是,当正在运行的系统上没有OpenGL时,外部DLL会崩溃。我试图从 C# 代码中的 dll 中捕获异常,但似乎我无法捕获它,因为异常是在 c++ dll 中引发的。

所以我的下一个想法是实现一个简单的函数来检查安装了哪个版本的OpenGL。

我完全不知道OpenGL及其工作原理,但是有人告诉我,我需要创建一个上下文来获取它的版本。 有没有一种简单的方法来实现在 C# 中执行此操作的函数?我不想导入像 OpenTK 这样的繁重 dll 只是为了获得版本。

我可以直接调用 opengl32.dll 并创建一个上下文来获取版本吗?

例如

[DllImport("opengl32.dll")]
public static extern string glGetString(string glVersion);

我知道这个片段不起作用,但它需要什么才能让它工作? 我如何创建这样的上下文,因为如果这不起作用,我可能已经知道没有 OpenGL,如果我能抓住它,我的问题就会得到解决。

正如你提到的:

具体来说,c++ dll 实际上是 C++ dll 的 C# 包装版本。所以我引用它,因为它将是一个普通的 C# dll。

您很幸运,引用的程序集在第一次被调用之前不会加载(因为否则答案的复杂性将大大增加我的知识)。通过这种方式,您可以在实际加载包装器之前检查 OpenGL 版本(并且由于不存在 OpenGL,可能会崩溃)。

我的建议是使用此问题中描述的信息来确定OpenGL的安装位置。

然后,您可以参考有关如何获取版本的问题。

该C++代码大致转换为以下 C# 代码:

internal static class Imports
{
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FreeLibrary(IntPtr hModule);
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)]string procName);
}
internal sealed class UnmanagedLibrary : IDisposable
{
private bool disposed = false;
public UnmanagedLibrary(string path)
{
Handle = Imports.LoadLibrary(path);
if (Handle == IntPtr.Zero)
{
throw new Exception($"Failed to load library "{path}" ({Marshal.GetLastWin32Error()}).");
}
}
~UnmanagedLibrary()
{
Dispose(false);
}
public void Dispose()
{ 
Dispose(true);
GC.SuppressFinalize(this);           
}
private void Dispose(bool disposing)
{
if (!disposed)
{
Imports.FreeLibrary(Handle);

disposed = true;
}
}
public IntPtr Handle
{
get;
private set;
}
}
internal static class OpenGLHelper
{
[StructLayout(LayoutKind.Explicit)]
private struct PIXELFORMATDESCRIPTOR 
{
[FieldOffset(0)]
public UInt16 nSize;
[FieldOffset(2)]
public UInt16 nVersion;
[FieldOffset(4)]
public UInt32 dwFlags;
[FieldOffset(8)]
public Byte iPixelType;
[FieldOffset(9)]
public Byte cColorBits;
[FieldOffset(10)]
public Byte cRedBits;
[FieldOffset(11)]
public Byte cRedShift;
[FieldOffset(12)]
public Byte cGreenBits;
[FieldOffset(13)]
public Byte cGreenShift;
[FieldOffset(14)]
public Byte cBlueBits;
[FieldOffset(15)]
public Byte cBlueShift;
[FieldOffset(16)]
public Byte cAlphaBits;
[FieldOffset(17)]
public Byte cAlphaShift;
[FieldOffset(18)]
public Byte cAccumBits;
[FieldOffset(19)]
public Byte cAccumRedBits;
[FieldOffset(20)]
public Byte cAccumGreenBits;
[FieldOffset(21)]
public Byte cAccumBlueBits;
[FieldOffset(22)]
public Byte cAccumAlphaBits;
[FieldOffset(23)]
public Byte cDepthBits;
[FieldOffset(24)]
public Byte cStencilBits;
[FieldOffset(25)]
public Byte cAuxBuffers;
[FieldOffset(26)]
public SByte iLayerType;
[FieldOffset(27)]
public Byte bReserved;
[FieldOffset(28)]
public UInt32 dwLayerMask;
[FieldOffset(32)]
public UInt32 dwVisibleMask;
[FieldOffset(36)]
public UInt32 dwDamageMask;
}
private const byte PFD_TYPE_RGBA = 0;
private const sbyte PFD_MAIN_PLANE = 0;
private const uint PFD_DOUBLEBUFFER = 1;
private const uint PFD_DRAW_TO_WINDOW = 4;
private const uint PFD_SUPPORT_OPENGL = 32;
private const int GL_VERSION = 0x1F02;
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int ChoosePixelFormatDelegate(IntPtr hdc, IntPtr ppfd);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int SetPixelFormatDelegate(IntPtr hdc, int format, IntPtr ppfd);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate IntPtr wglCreateContextDelegate(IntPtr arg1);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int wglDeleteContextDelegate(IntPtr arg1);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int wglMakeCurrentDelegate(IntPtr arg1, IntPtr arg2);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate IntPtr glGetStringDelegate(int name);
public static string GetVersion()
{
using (UnmanagedLibrary openGLLib = new UnmanagedLibrary("opengl32.dll"))
using (UnmanagedLibrary gdi32Lib = new UnmanagedLibrary("Gdi32.dll"))
{
IntPtr deviceContextHandle = Imports.GetDC(Process.GetCurrentProcess().MainWindowHandle);
if (deviceContextHandle == IntPtr.Zero)
{
throw new Exception("Failed to get device context from the main window.");
}
IntPtr choosePixelFormatAddress = Imports.GetProcAddress(gdi32Lib.Handle, "ChoosePixelFormat");
if (choosePixelFormatAddress == IntPtr.Zero)
{
throw new Exception($"Failed to get ChoosePixelFormat address ({Marshal.GetLastWin32Error()}).");
}
ChoosePixelFormatDelegate choosePixelFormat = Marshal.GetDelegateForFunctionPointer<ChoosePixelFormatDelegate>(choosePixelFormatAddress);
PIXELFORMATDESCRIPTOR pfd = new PIXELFORMATDESCRIPTOR
{
nSize = (UInt16)Marshal.SizeOf(typeof(PIXELFORMATDESCRIPTOR)),
nVersion = 1,
dwFlags = (PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER),
iPixelType = PFD_TYPE_RGBA,
cColorBits = 32,
cRedBits = 0,
cRedShift = 0,
cGreenBits = 0,
cGreenShift = 0,
cBlueBits = 0,
cBlueShift = 0,
cAlphaBits = 0,
cAlphaShift = 0,
cAccumBits = 0,
cAccumRedBits = 0,
cAccumGreenBits = 0,
cAccumBlueBits = 0,
cAccumAlphaBits = 0,
cDepthBits = 24,
cStencilBits = 8,
cAuxBuffers = 0,
iLayerType = PFD_MAIN_PLANE,
bReserved = 0,
dwLayerMask = 0,
dwVisibleMask = 0,
dwDamageMask = 0
};
IntPtr pfdPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PIXELFORMATDESCRIPTOR)));
try
{
Marshal.StructureToPtr(pfd, pfdPtr, false);
int pixelFormat = choosePixelFormat(deviceContextHandle, pfdPtr);
if (pixelFormat == 0)
{
throw new Exception($"Failed to choose pixel format ({Marshal.GetLastWin32Error()}).");
}
IntPtr setPixelFormatAddress = Imports.GetProcAddress(gdi32Lib.Handle, "SetPixelFormat");
if (setPixelFormatAddress == IntPtr.Zero)
{
throw new Exception($"Failed to get SetPixelFormat address ({Marshal.GetLastWin32Error()}).");
}
SetPixelFormatDelegate setPixelFormat = Marshal.GetDelegateForFunctionPointer<SetPixelFormatDelegate>(setPixelFormatAddress);
if (setPixelFormat(deviceContextHandle, pixelFormat, pfdPtr) <= 0)
{
throw new Exception($"Failed to set pixel format ({Marshal.GetLastWin32Error()}).");
}
IntPtr wglCreateContextAddress = Imports.GetProcAddress(openGLLib.Handle, "wglCreateContext");
if (wglCreateContextAddress == IntPtr.Zero)
{
throw new Exception($"Failed to get wglCreateContext address ({Marshal.GetLastWin32Error()}).");
}
wglCreateContextDelegate wglCreateContext = Marshal.GetDelegateForFunctionPointer<wglCreateContextDelegate>(wglCreateContextAddress);
IntPtr wglDeleteContextAddress = Imports.GetProcAddress(openGLLib.Handle, "wglDeleteContext");
if (wglDeleteContextAddress == IntPtr.Zero)
{
throw new Exception($"Failed to get wglDeleteContext address ({Marshal.GetLastWin32Error()}).");
}
wglDeleteContextDelegate wglDeleteContext = Marshal.GetDelegateForFunctionPointer<wglDeleteContextDelegate>(wglDeleteContextAddress);
IntPtr openGLRenderingContext = wglCreateContext(deviceContextHandle);
if (openGLRenderingContext == IntPtr.Zero)
{
throw new Exception($"Failed to create OpenGL rendering context ({Marshal.GetLastWin32Error()}).");
}
try
{
IntPtr wglMakeCurrentAddress = Imports.GetProcAddress(openGLLib.Handle, "wglMakeCurrent");
if (wglMakeCurrentAddress == IntPtr.Zero)
{
throw new Exception($"Failed to get wglMakeCurrent address ({Marshal.GetLastWin32Error()}).");
}
wglMakeCurrentDelegate wglMakeCurrent = Marshal.GetDelegateForFunctionPointer<wglMakeCurrentDelegate>(wglMakeCurrentAddress);
if (wglMakeCurrent(deviceContextHandle, openGLRenderingContext) <= 0)
{
throw new Exception($"Failed to make current device context ({Marshal.GetLastWin32Error()}).");
}
IntPtr glGetStringAddress = Imports.GetProcAddress(openGLLib.Handle, "glGetString");
if (glGetStringAddress == IntPtr.Zero)
{
throw new Exception($"Failed to get glGetString address ({Marshal.GetLastWin32Error()}).");
}
glGetStringDelegate glGetString = Marshal.GetDelegateForFunctionPointer<glGetStringDelegate>(glGetStringAddress);
IntPtr versionStrPtr = glGetString(GL_VERSION);
if (versionStrPtr == IntPtr.Zero)
{
// I don't think this ever goes wrong, in the context of OP's question and considering the current code.
throw new Exception("Failed to get OpenGL version string.");
}
return Marshal.PtrToStringAnsi(versionStrPtr);
}
finally
{
wglDeleteContext(openGLRenderingContext);
}
}
finally
{
Marshal.FreeHGlobal(pfdPtr);
}
}
}
}

然后,您可以通过以下方式获取版本字符串:

OpenGLHelper.GetVersion()

这在我的机器上给了我以下输出:

4.5.0 - Build 22.20.16.4836

从 MSDN:

GL_VERSION字符串以版本号开头。版本号使用以下形式之一:

major_number.次要号码

major_number.次要编号.发布编号

重要的部分是,在实际从包装器 DLL 调用任何内容之前执行此检查。

此代码的工作方式是动态检索opengl32.dllGdi32.dll中的函数地址。它动态加载提到的两个库并检索我们需要调用的函数地址。这与DllImport不同,因为它从已加载的库中导入。所以从某种意义上说,我们所做的与DllImport完全相同,除了手动加载/卸载非托管库。我建议您在MSDN上搜索函数名称,它清楚地解释了每个函数的作用和返回的内容。

警告:此代码假定您的应用程序具有与之关联的窗口。尽管GetDCnull指针有效(根据 MSDN 应返回整个屏幕的设备上下文),但我不知道这是否始终有效。

注意:您还应该实现自己的Exception,而不是抛出基本。