Click here to Skip to main content
Click here to Skip to main content
Go to top

OpenGL for Both Native and .NET Environment

, 7 Jan 2013
Rate this:
Please Sign up or sign in to vote.
This article shows how you can bring OpenGL to both native and .NET environment.

Introduction

This article shows how you can bring OpenGL to both native and .NET environment.

Background

A few days ago, I wrote a visualization and charting library (called OpenGraph library) for both native and .NET environment based on my existing C++ OpenGL code base. I decided to reveal the essential code to the public in the hope that people can benefit from my work.

Using the Code

The demo code consists of four simple projects:

  1. OpenGraphLib: A C++ OpenGL Windows DLL
  2. OpenGraphWin32Client: A C++ Win32 OpenGL application
  3. OpenGraphLib_Cli: A C++/CLI class library that wraps the C++ OpenGL Windows DLL
  4. OpenGraphWinformDialogClient2010: A C# WinForms OpenGL application

Each project has Debug, Release, Unicode Debug, and Unicode Release configurations. Because of the size of each project and the time constraint, I cannot go into too much details describing the code. The following is a very brief description of each project.

Part One – OpenGraphLib

The part is a pure C++ Windows DLL (Unicode compliant). The main interface class is COpenGraph, which provides interfaces to setup and use OpenGL functions. The interface is clean because the actual implementation is done in the CDrawManager class. Clients (callers) of this code are agnostic about OpenGL. For example, the native Win32 or MFC clients do not need to include OpenGL library headers.

//
// forward declaration
struct CGLFont2d;
class CRubberBandTracker;
class CDrawManager;

class OPENGRAPHLIB_API COpenGraph
{
public:
    COpenGraph(void);
    virtual ~COpenGraph(void);

public:
    enum {modeNONE = -1,modeTRACK, modeROTATE };

private:
    enum {GRID_LIST = 0, NODE_LIST, LINE_LIST, TRIANGLE_LIST, QUAD_LIST, TEXT_LIST,END_LIST};

public:
    bool Initialize(HWND hWnd);
    void SetMode(int mode);
    void SetWindowSize(int cx, int cy);
    void Render();
    void ForceRender();
    void ShowAxes(bool bShow);
    BOOL OnLButtonDown(RECT& rect, UINT nFlags, POINT point);
    void OnMouseMove(UINT nFlags, POINT point);
    HCURSOR GetModeCursor();
    void OnSetCursor();
    void Rotate(double fRx, double fRy, double fRz);
    bool TrackRect(HWND hWnd, POINT point);
    RECT GetTrackRect()const; // un-normalized
    void Clear();
    void SelectAll();
    void UnSelectAll();
    void ReverseSelectAll();
    void FreeMemory(void * p);
    void FreeMemoryArray(void * p);
    CIdentity AddNode(int id,CPointf pt, int nSize, COLORREF color,COLORREF colorSelected, 
                      int nStatus = STATUS_UNSELECTED, LPCTSTR tag = 0, LPCTSTR userData = 0); 
    CIdentity AddLine(int id,CPointf pt1,CPointf pt2, int nThickness, COLORREF color,
                      COLORREF colorSelected, int nStatus = STATUS_UNSELECTED, 
                      LPCTSTR tag = 0, LPCTSTR userData = 0 );

private:
    CDrawManager* m_pDrawManager;
};

Part Two – OpenGraphWin32Client

This is a Win32 Windows application that uses the Windows native DLL (OpenGraph.dll). The COpenGraph object is associated with the main window at InitInstance(). Windows messages are handled appropriately in WndProc() for OpenGL window size setup, mouse events, and rendering. The WM_ERASEBKGND message is ignored so we do not have flickering when the window is being resized. The actual drawing objects are added by some helper functions. They are pretty simple and are not shown here.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, 
                       CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   m_openGraph.Initialize(hWnd);

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		m_openGraph.Render();
		EndPaint(hWnd, &ps);
		break;

    // jxu
    case WM_ERASEBKGND:
        break;

    case WM_SIZE:
        {
            m_nCx    = LOWORD(lParam );
            m_nCy = HIWORD(lParam );
            m_openGraph.SetWindowSize(m_nCx, m_nCy);
        }
        break;

    case WM_LBUTTONDOWN:
        {
            RECT rect;
            UINT nFlags = GET_KEYSTATE_WPARAM(wParam);
            POINT pt;
            pt.x = LOWORD(lParam);
            pt.y = HIWORD(lParam); 
            m_openGraph.OnLButtonDown(rect, nFlags, pt);
        }
        break;

    case WM_MOUSEMOVE:
        {
            m_openGraph.OnSetCursor();  // jxu-  Win32 app is different from MFC
            UINT nFlags = GET_KEYSTATE_WPARAM(wParam);
            POINT pt;
            pt.x = LOWORD(lParam);
            pt.y = HIWORD(lParam); 
            m_openGraph.OnMouseMove(nFlags, pt);
        }
        break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

Part Three – OpenGraphLib_Cli

The part is a C++/CLI .NET class library (OpenGraph_Cli.dll). It serves as a bridge between the native C++ DLL and the .NET client. For more details about C++/CLI, please refer to my related CodeProject article entitled “Leveraging your Existing C++ Code for Use in the .NET Environment”.

Part Four – OpenGraphWinformDialogClient2010

This is WinForms application that displays OpenGL on a user control. It only interfaces with the C++/CLI .NET class library (OpenGraph_Cli.dll). Please note that the platform target must be x86.

The user control OpenGLUserControl is empty and only contains some delegates that will be set by the parent WinForm.

public partial class OpenGLUserControl : UserControl
{
    public delegate void OpenGLUserControl_Load_Type(object sender, EventArgs e);
    public delegate void OpenGLUserControl_Resize_Type(object sender, EventArgs e);
    public delegate void OpenGLUserControl_MouseDown_Type(object sender, MouseEventArgs e);
    public delegate void OpenGLUserControl_MouseMove_Type(object sender, MouseEventArgs e);
    public delegate void OpenGLUserControl_Paint_Type(object sender, PaintEventArgs e);

    public OpenGLUserControl_Load_Type Load_CallBack = null;
    public OpenGLUserControl_Resize_Type Resize_CallBack = null;
    public OpenGLUserControl_MouseDown_Type MouseDown_CallBack = null;
    public OpenGLUserControl_MouseMove_Type MouseMove_CallBack = null;
    public OpenGLUserControl_Paint_Type Paint_CallBack = null;

    public OpenGLUserControl()
    {
        InitializeComponent();
    }

    private void OpenGLUserControl_Load(object sender, EventArgs e)
    {
        if (Load_CallBack != null)
        {
            Load_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_Resize(object sender, EventArgs e)
    {
        if (Resize_CallBack != null)
        {
            Resize_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_MouseDown(object sender, MouseEventArgs e)
    {
        if (MouseDown_CallBack != null)
        {
            MouseDown_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_MouseMove(object sender, MouseEventArgs e)
    {
        if (MouseMove_CallBack != null)
        {
            MouseMove_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_Paint(object sender, PaintEventArgs e)
    {
        if (Paint_CallBack != null)
        {
            Paint_CallBack(sender, e);
        }
    }
}

The WinForm sets the delegates for the user control, sets the appropriate drawing mode, and adds drawing objects.

public partial class Form1 : Form
{
    COpenGraph_Cli _openGraph_Cli = new COpenGraph_Cli();
    private int m_nCx = 0;
    private int m_nCy = 0;
    private bool m_bShowAxes = true;

    public Form1()
    {
        InitializeComponent();

        openGLUserControl.Load_CallBack = this.Delegate_Load;
        openGLUserControl.MouseMove_CallBack = this.Delegate_MouseMove;
        openGLUserControl.MouseDown_CallBack = this.Delegate_MouseDown;
        openGLUserControl.Paint_CallBack = this.Delegate_Paint;
        openGLUserControl.Resize_CallBack = this.Delegate_Resize;
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void Form1_Resize(object sender, EventArgs e)
    {
        m_nCx = this.openGLUserControl.Width;
        m_nCy = this.openGLUserControl.Height;
        _openGraph_Cli.SetWindowSize(m_nCx, m_nCy);
        _openGraph_Cli.Render();
    }


    private void Delegate_Load(object sender, EventArgs e)
    {
        _openGraph_Cli.Initialize(openGLUserControl.Handle);
        m_nCx = this.openGLUserControl.Width;
        m_nCy = this.openGLUserControl.Height;
        _openGraph_Cli.SetWindowSize(m_nCx, m_nCy);
        _openGraph_Cli.Render();
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeNONE);

    }

    private void Delegate_MouseMove(object sender, MouseEventArgs e)
    {
        _openGraph_Cli.OnSetCursor();
        if (e.Button == MouseButtons.Left)
        {
            CRect_Cli rect = new CRect_Cli();
            CPoint_Cli pt = new CPoint_Cli();
            pt.X = e.X;
            pt.Y = e.Y;

            uint nFlags = (int)COpenGraph_Cli.ButtonFlag.flagMK_LBUTTON;
            if (_openGraph_Cli != null)
            {
                _openGraph_Cli.OnMouseMove(nFlags, pt);
            }
        }
    }

    private void Delegate_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            CRect_Cli rect = new CRect_Cli();
            CPoint_Cli pt = new CPoint_Cli();
            pt.X = e.X;
            pt.Y = e.Y;
            uint nFlags = 0;
            if (_openGraph_Cli != null)
            {
                _openGraph_Cli.OnLButtonDown(ref rect, nFlags, pt);
            }
        }
    }

    private void Delegate_Paint(object sender, PaintEventArgs e)
    {
        if (_openGraph_Cli != null)
        {
            _openGraph_Cli.Render();
        }
    }

    private void Delegate_Resize(object sender, EventArgs e)
    {
        if (_openGraph_Cli != null)
        {
            m_nCx = this.ClientSize.Width;
            m_nCy = this.ClientSize.Height - menuStrip1.Height;
            _openGraph_Cli.SetWindowSize(m_nCx, m_nCy);
        }
    }


    // draw -------------------------------------------------------
    private void drawNodesToolStripMenuItem_Click(object sender, EventArgs e)
    {
        int fontId = _openGraph_Cli.AddFont("Times New Roman", 30, 0, 0);
        Random random = new Random();

        const int nNodes = 400;
        const int nBatches = 10;
        const int nNodesPerBatch = nNodes / nBatches;
        for (int j = 0; j < nBatches; j++)
        {
            CColor_Cli color = new CColor_Cli((byte)(random.Next(0, 256)), 
                       (byte)(random.Next(0, 256)), (byte)(random.Next(0, 256)));
            CColor_Cli colorSelected = new CColor_Cli(255, 0, 0);
            List<CPointf_Cli> pPt = new List<CPointf_Cli>();
            List<int> pId = new List<int>();
            int nSize = (random.Next(0, 256) % 8);
            string strTag;
            strTag = string.Format("Tag_{0}", j);
            for (int i = 0; i < nNodesPerBatch; i++)
            {
                double x = ((double)random.Next(0, 256) / 256 - 0.5) * 4;
                double y = ((double)random.Next(0, 256) / 256 - 0.5) * 4;
                double z = ((double)random.Next(0, 256) / 256 - 0.5) * 4;
                string strText;
                int id = j * nNodesPerBatch + i + 1;
                strText = string.Format("{0}", id);
                if (i % 2 == 0)
                {
                    _openGraph_Cli.AddText(id, 0, new CPointf_Cli(x, y, z), 
                               strText, new CColor_Cli(125, 123, 0), colorSelected,
                               CTextAlignment_Cli.ALIGN_LEFT | 
                               CTextAlignment_Cli.ALIGN_BOTTOM, CStatus_Cli.STATUS_UNSELECTED);
                }
                else
                {
                    _openGraph_Cli.AddText(id, fontId, new CPointf_Cli(x, y, z), strText, 
                        new CColor_Cli(0, 123, 123), colorSelected,
                        CTextAlignment_Cli.ALIGN_LEFT | CTextAlignment_Cli.ALIGN_BOTTOM, 
                        CStatus_Cli.STATUS_UNSELECTED);
                }
                pId.Add(id);
                pPt.Add(new CPointf_Cli(x, y, z));
            }
            List<CIdentity_Cli> pIdentity = new List<CIdentity_Cli>();
            // for performance reason, you should send in your data in batches
            _openGraph_Cli.AddNodes(ref pIdentity, pId, pPt, nSize, color, 
                       colorSelected, CStatus_Cli.STATUS_UNSELECTED, strTag, null);
        }

        _openGraph_Cli.AddNode(99999, new CPointf_Cli(), 15, new CColor_Cli(255, 255, 0), 
                   new CColor_Cli(0, 255, 255), 0, null, null);
        _openGraph_Cli.AddText(99999, 0, new CPointf_Cli(), "99999", 
            new CColor_Cli(125, 123, 0), new CColor_Cli(0, 255, 255),
            CTextAlignment_Cli.ALIGN_LEFT | CTextAlignment_Cli.ALIGN_BOTTOM, 
            CStatus_Cli.STATUS_UNSELECTED);


        _openGraph_Cli.Render();
    }

    private void defaultToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeNONE);
    }

    private void trackToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeTRACK);
    }

    private void rotateToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeROTATE);
    }

    private void showAxesToolStripMenuItem_Click(object sender, EventArgs e)
    {
        m_bShowAxes = !m_bShowAxes;
        _openGraph_Cli.ShowAxes(m_bShowAxes);
        _openGraph_Cli.Render();
    }
}

Windows Presentation Foundation Form

For a WPF form, you can use the same WinForms user control inside the WPF WindowsFormsHost control. For example:

<Window x:Class="OpenGraphWpfClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wf="clr-namespace:OpenGraphWpfClient"  
        Title="MainWindow" Height="520" Width="934" SizeChanged="Window_SizeChanged">
    <Grid>
        <WindowsFormsHost Margin="182,49,40,31" Name="windowsFormsHost1">
            <wf:OpenGLUserControl Name="openGLUserControl"></wf:OpenGLUserControl>
        </WindowsFormsHost>
    </Grid>
</Window>

Points of Interest

The code here may be augmented for actual production use. For example, you can add more interactivity to the code by allowing the user to zoom in, pan, or select individual objects. I leave those features to you to exercise your talents. You can also refer to www.cg-inc.com for help or more information. Thanks for reading and happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Junlin Xu
Software Developer
United States United States
Junlin Xu is the founder of Computations & Graphics, Inc. (www.cg-inc.com) and author of OpenGraph Library and Real3D-Analysis. He has seventeen years of software development experience. His expertise includes C++, C#, C++/CLI, ObjectiveC, MFC, OpenGL, DirectX, WIN32 API, COM/COM++, WinForms, MS SQL, MySql, ASP.NET, WCF, WPF, Mirth Connect. He is also an expert in Finite Element Method procedure. He is currently a software engineer at a company in Colorado. He can be reached at junlin.xu@gmail.com.

Comments and Discussions

 
GeneralMy vote of 5 Pinmembercode_junkie4-Mar-13 7:30 
QuestionNot much use without the GL files PinmemberCreate an account dammit4-Mar-13 1:04 
Not much use without gl/glaux.h, glaux.lib, etc. Can you either include the ones you are using (if that is appropriate), else provide help in what SDK's etc you need to download which is further complicated by the fact that glaux.lib has been deprecated since 2008 (see for example http://social.msdn.microsoft.com/Forums/en/windowssdk/thread/b66e5f7a-91f6-4dbe-b388-0c131008b08f[^]).
AnswerRe: Not much use without the GL files PinmemberJunlin Xu4-Mar-13 2:46 
GeneralMy vote of 5 PinmemberMihai MOGA16-Feb-13 19:20 
GeneralRe: My vote of 5 PinmemberJunlin Xu18-Feb-13 4:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140905.1 | Last Updated 7 Jan 2013
Article Copyright 2013 by Junlin Xu
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid