4
\$\begingroup\$

The title pretty much sums it up. I'd like to know how I can get the HWND of my C# Panel object to my DirectX engine so I can render to it. I know it involves making a C++/CLI wrapper, but I'm not quite sure what to do beyond that.

\$\endgroup\$
  • \$\begingroup\$ You mentioned Windows Forms in one, now-deleted comment. You tagged the question WPF. Which is it? \$\endgroup\$ – user1430 Jun 21 '16 at 15:31
8
\$\begingroup\$

You actually do not need C++/CLI for this, although you could use it if you wanted. I don't really recommend it. C#'s platform invoke facilities should be sufficient for you.

You'll need to make your engine available as a DLL. Doing so is beyond the scope of this question, but there's plenty of information about making DLLs available on the web. Once you have that, you need to expose a C-style function that can initialize your engine with a provided HWND:

// in C++:
extern "C" {
  void InitializeMyEngine(HWND window);
}

You can then then use P/Invoke in C# to access that function:

// in C#:
class Engine {
  [DllImport("engine.dll")]
  public static extern void Initialize(IntPtr window);
}

You can then call Engine.Initialize(someWindowHandle) in your C# code to initialize your engine. You may also want to expose P/Invoke wrappers to manually pump your engine's update or render loop from the C# side of things, for example from a variant of the canonical Windows Forms game loop.

All you need now is the HWND of the Panel. If you were using the Windows Forms Panel class, this would be very straightforward: it's just the panel's Handle property, because Windows Forms objects are mostly 1:1 mappings to Win32 counterparts and all have underlying handles.

WPF objects do not, however, so it's more difficult to get the WPF HWND from a WPF Panel. You can use the HwndSource class to do so:

HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(yourPanel);
Engine.Initialize(hwndSource.Handle);
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Will I still need to use the function CreateWindowEx() and will I still need to create WNDCLASSEX wc; ? Since I am rendering to a Panel? \$\endgroup\$ – Arjan Singh Jul 5 '16 at 7:36
  • \$\begingroup\$ No, on the C++ side you won't need to create your own window in this case. \$\endgroup\$ – user1430 Jul 5 '16 at 13:52
  • \$\begingroup\$ So all I need to do with the HWND from C# is use it in my WndProc function? I know I will need to pass the HINSTANCE from C# to a few functions but will the HWND need to be used anywhere else? Sorry for these stupid questions, I only started learning directx a month ago. \$\endgroup\$ – Arjan Singh Jul 7 '16 at 16:47
  • \$\begingroup\$ You don't even need a WndProc in this case, as the Form has already created one. You just need to be able to give the HWND to the D3D initialization stuff. \$\endgroup\$ – user1430 Jul 7 '16 at 17:27
5
\$\begingroup\$

So like Josh has described above I have written an Engine in a DLL and used p/invoke in C# to use it. If anyone is interested here is the code:

DLL Header.h

#pragma once

#define DllExport __declspec(dllexport)

#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include <DirectXMath.h>

using namespace DirectX;

// include the Direct3D Library file
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")

// global declarations
IDXGISwapChain *swapchain;             // the pointer to the swap chain interface
ID3D11Device *dev;                     // the pointer to our Direct3D device interface
ID3D11DeviceContext *devcon;           // the pointer to our Direct3D device context
ID3D11RenderTargetView *backbuffer;    // the pointer to our back buffer

extern "C"
{
    __declspec(dllexport) void InitD3D(HWND hWnd, int Width, int Height);    // sets up and initializes Direct3D
}

extern "C"
{
    __declspec(dllexport) void RenderFrame(void);     // renders a single frame
}

extern "C"
{
    __declspec(dllexport) void CleanD3D(void);        // closes Direct3D and releases memory
}

DLL Main.cpp

#include "Header.h"

void InitD3D(HWND hWnd, int Width, int Height)
{
    // create a struct to hold information about the swap chain
    DXGI_SWAP_CHAIN_DESC scd;

    // clear out the struct for use
    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

    HRESULT hr = S_OK;

    // fill the swap chain description struct
    scd.BufferCount = 2;                                    // one back buffer
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;     // use 32-bit color
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;      // how swap chain is to be used
    scd.OutputWindow = hWnd;                                // the window to be used
    scd.SampleDesc.Count = 8;                               // how many multisamples
    scd.SampleDesc.Quality = 1;
    scd.Windowed = TRUE;
    scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;     // allow full-screen switching// windowed/full-screen mode
                                                            // create a device, device context and swap chain using the information in the scd struct

    hr = D3D11CreateDeviceAndSwapChain(NULL,
        D3D_DRIVER_TYPE_HARDWARE,
        NULL,
        NULL,
        NULL,
        NULL,
        D3D11_SDK_VERSION,
        &scd,
        &swapchain,
        &dev,
        NULL,
        &devcon);

    // get the address of the back buffer
    ID3D11Texture2D *pBackBuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

    // use the back buffer address to create the render target
    dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
    pBackBuffer->Release();

    // set the render target as the back buffer
    devcon->OMSetRenderTargets(1, &backbuffer, NULL);

    // Set the viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = Width;
    viewport.Height = Width;

    devcon->RSSetViewports(1, &viewport);

}

void RenderFrame(void)
{
    // clear the back buffer to a deep blue
    devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 1.0f, 1.0f, 1.0f));

    // do 3D rendering on the back buffer here

    // switch the back buffer and the front buffer
    swapchain->Present(0, 0);
}

void CleanD3D(void)
{
    // close and release all existing COM objects
    swapchain->Release();
    backbuffer->Release();
    dev->Release();
    devcon->Release();
}

C# Project

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DirectXinForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Dengine.InitD3D(panel1.Handle, panel1.Width, panel1.Height);
            Dengine.RenderFrame();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Dengine.CleanD3D();
        }
    }

    public class Dengine
    {
        [DllImport("DirectXENGINEDLL.dll")]
        public static extern void InitD3D(IntPtr window, int Width, int Height);

        [DllImport("DirectXENGINEDLL.dll")]
        public static extern void RenderFrame();

        [DllImport("DirectXENGINEDLL.dll")]
        public static extern void CleanD3D();
    }

}

You must make sure that the C# project is compiled as 64 bit if your DLL is 64 bit (same goes for 32 bit), if you compile with AnyCPU you will get a SystemBadImage error.

For some reason I could not add a reference to the DLL in Visual Studio 2015 but I just added it to the project by dragging and dropping the DLL which worked. However each time you make a change to the DLL you need to delete the one that you previously added and add it again in the same manner.

For the added .DLL file, under properties and copy to output directories set it as 'Copy Always'.

enter image description here

Hopefully this has helped some of you :).

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.