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.
- \$\begingroup\$ You mentioned Windows Forms in one, now-deleted comment. You tagged the question WPF. Which is it? \$\endgroup\$user1430– user14302016-06-21 15:31:05 +00:00Commented Jun 21, 2016 at 15:31
2 Answers
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); - \$\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– Arjan Singh2016-07-05 07:36:01 +00:00Commented Jul 5, 2016 at 7:36
- \$\begingroup\$ No, on the C++ side you won't need to create your own window in this case. \$\endgroup\$user1430– user14302016-07-05 13:52:12 +00:00Commented Jul 5, 2016 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– Arjan Singh2016-07-07 16:47:18 +00:00Commented Jul 7, 2016 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– user14302016-07-07 17:27:11 +00:00Commented Jul 7, 2016 at 17:27
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'.
Hopefully this has helped some of you :).
