Introduction
I haven't written an article in a long long time, so, I'm here today with an awesome one. :)
This article is for C++/QT programmers who want to use amazing features of WPF within their C++ code.
If you're a .NET programmer, just walk away, it's not yours. (You can Use Invoking & DllImport stuff :v)
Background & Backstory
When we talk about WPF, we talk about an easy-to-use, user-friendly & high performance framework created by Microsoft for .NET users.
It has the power of DirectX Renderer combined with HTML/CSS styling and it's open source! There are too many reasons which makes this little guy better than any other solution for Desktop Applications.
Better than QT, Winforms, HTML/CSS, Here's why...
- QT Framework: It's not free if you need to use static linking. It has too many dependencies, also, it has poor component library and you need to buy third party libraries or you should create your components yourself.
- Winforms: Very well designed, still a hero, but it's a little bit old and uses GDI+.
- HTML/CSS: Needs heavy sized dependencies like chromium, etc. You should ship 60+ MB for a simple hello world app developed in HTML/CSS and if you choose Electron, it comes in 80+ MB.
- WPF: Uses GPU Rendering, It has a amazing designer, is fully customizable and ships with Windows itself, there is no need for any dependencies, Microsoft is using it in Windows over and over again. Also, .NET Core 3.0 has support of WPF which means it can be used for cross-platform applications too!
But the issue is it's only for .NET and C/C++ or Delphi developers cannot use it for their GUI if they want to. They should switch on C# language which has low security and less performance than C++ and if they want to mix it up with current solutions on the internet, it becomes unsafe, unstable and not cool at all!
But sometimes doing something complex is so much simpler than it looks! How? Let me quote from a KING:
Heisenberg:
Awh nothing special... it's just the Basics!
I always wanted to make this work!... Using C++ as my backend application and WPF/Winform for GUI and frontend because it's safe/fast in backend and pretty/easy-to-use in frontend.
In this solution, you can use WPF/Winforms and QT and whatever else you want together.
Current Existing Methods
Okay before we start, let's take a look at the current methods on the market and make a brief review...
- ActiveX: The first thing you always hear from embedding a WPF/Winform inside C++ ActiveX control but it's bad, really bad! Creating another platform for using two another platforms ... awh that's mixed up! and on top of everything, it's visible and accessible to everyone and they can use it too... an advice from your programmer friend... never use COM for UI...
- CLRHosting: Second thing everyone finds on net is CLR Hosting Interface, yeah it's good and stable but not enough for mixing only UI and frontend, I prefer this to run a whole .NET app inside my native application.
- C++/CLI: The last thing anyone wants to mess with. Needs invoking and lots of pain, also CLR/C++ assemblies are hard to integrate.
- Noesis GUI: Another possible way, But eh ... needs implanting everything, blah blah ...
- WPF Rendering Redirection: In this method, you run a hidden instance of WPF window and renders it in a texture2D, then you show it in a C++ window and redirect every received window messages to WPF hidden window. It works! but ... you know... we are here for a clean and professional way... :D
My Pretty Basic Method
This method I will teach you is a part of my big tutorial pack "Architecting Your Application Like A Boss". I will publish it part by part on CodeProject so ... take a seat and enjoy the show! :)
Steps we're going to follow are:
- Creating a host window in C++
- Creating our WPF GUI library in C#
- Using our library in C++ to create WPF GUI
- Exchanging functions with C# from C++ and reverse
- Creating callbacks and installing Windows hooks
- There's no step 6, it's pretty basic remember ? :D
In My Method ...
Only thing you need is a single handle...
Okay, enough talk, let's do this! :)
Creating a Host Window
Open Visual Studio and create a C++ Console Application in x64, use this basic code as your main cpp file:
#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
cout << "C++ Main App Started..." << endl;
cout << "C++ Main App Finished." << endl;
getchar();
}
Now let's create our unmanaged Host Window ...
- Define global objects under
//// Global Objects
and define Configs in //// Global Configs
:
WNDCLASSEX HostWindowClass; MSG loop_message; HINSTANCE hInstance = GetModuleHandle(NULL); HWND cpphwin_hwnd; HWND wpf_hwnd;
const wchar_t cpphwinCN[] = L"CppMAppHostWinClass"; bool isHWindowRunning = false;
- Create window class using (After
/// We Code Here ...
):
HICON app_icon = LoadIcon(GetModuleHandle(0),MAKEINTRESOURCE(IDI_APPICON));
HostWindowClass.cbSize = sizeof(WNDCLASSEX); HostWindowClass.lpfnWndProc = HostWindowProc;
HostWindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
HostWindowClass.cbClsExtra = 0; HostWindowClass.style = 0;
HostWindowClass.cbWndExtra = 0; HostWindowClass.hInstance = hInstance;
HostWindowClass.hIcon = app_icon; HostWindowClass.hIconSm = app_icon;
HostWindowClass.lpszClassName = cpphwinCN; HostWindowClass.lpszMenuName = NULL;
Why not setting 'hbrBackground'? If you choose a background for your native window, it will flick over WPF rendering, just don't set it up.
- Add a callback to host window using:
LRESULT CALLBACK HostWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
isHWindowRunning = false;
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
- Register host window class using:
if (!RegisterClassEx(&HostWindowClass))
{
cout << "Error, Code :" << GetLastError() << endl;
getchar(); return 0;
}
- Ok time to create the window, but hidden...
cpphwin_hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
cpphwinCN,
GetSTR_Res(APPDATA_HWINDOW_NAME),
WS_THICKFRAME | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 500,
NULL, NULL, hInstance, NULL);
if (cpphwin_hwnd == NULL)
{
cout << "Error, Code :" << GetLastError() << endl;
getchar(); return 0;
}
- [Optimal] If you want to make your window fixed size, use:
::SetWindowLong(cpphwin_hwnd, GWL_STYLE,
GetWindowLong(cpphwin_hwnd, GWL_STYLE) & ~WS_SIZEBOX);
Here we go, now we have a native host window...
Configuring and Showing Host Window
Ok, I know most of you know all these things, but there's very small details in parameters you need to be careful about, that's why I'm writing this code for you. ;)
- Center your host window using:
RECT window_r; RECT desktop_r;
GetWindowRect(cpphwin_hwnd, &window_r); GetWindowRect(GetDesktopWindow(), &desktop_r);
int xPos = (desktop_r.right - (window_r.right - window_r.left)) / 2;
int yPos = (desktop_r.bottom - (window_r.bottom - window_r.top)) / 2;
::SetWindowPos(cpphwin_hwnd, 0, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
- And finally, display the window using:
ShowWindow(cpphwin_hwnd, SW_SHOW);
UpdateWindow(cpphwin_hwnd);
BringWindowToTop(cpphwin_hwnd);
isHWindowRunning = true;
- Add message loop to avoid application freezing using:
while (GetMessage(&loop_message, NULL, 0, 0) > 0 && isHWindowRunning)
{
TranslateMessage(&loop_message);
DispatchMessage(&loop_message);
}
Main Unmanaged Application
Ok, time to develop your C++ application functionality, interfaces, etc. In this tutorial, I created a simple C++ application which compresses/decompresses files using LZ4 algorithm.
Of course! .NET can do LZ4 compression just as perfect as C++ but this is an example for using a native library (lz4 library) and the point is using WPF just as GUI, your native application can be anything... there's no limitation.
Also at the end of the article, I tell you how you can use this method on Plugins and SDKs for which you don't have full access to entire parts.
Here's the code of Main app but it's not a part of this tutorial, so I will not explain it... you can use anything you want. :)
C++ LZ4 Compressor/Decompressor Application
#pragma region Main App Codes
#include "SDK\\lz4.h"
#pragma comment(lib, "SDK\\liblz4_static_vc2019.lib")
#include <vector>
#include <fstream>
ofstream file;
using buffer = vector<char>;
BOOL lz4_compress(const buffer& in, buffer& out)
{
auto rv = LZ4_compress_default(in.data(), out.data(), in.size(), out.size());
if (rv < 1) { return FALSE; }
else { out.resize(rv); return TRUE;}
}
BOOL lz4_decompress(const buffer& in, buffer& out)
{
auto rv = LZ4_decompress_safe(in.data(), out.data(), in.size(), out.size());
if (rv < 1) { return FALSE; }
else { out.resize(rv); return TRUE; }
}
std::vector<char> readFile(const char* filename)
{
std::basic_ifstream<char> file(filename, std::ios::binary);
return std::vector<char>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
BOOL LZ4_Compress_File(char* filename) {
buffer org_filedata = readFile(filename);
if(org_filedata.size() == 0){ return FALSE; }
const size_t max_dst_size = LZ4_compressBound(org_filedata.size());
vector<char> compressed_data(max_dst_size);
BOOL compress_data_with_lz4 = lz4_compress(org_filedata, compressed_data);
if (!compress_data_with_lz4) { return FALSE; }
string out_put_file_name = filename + string("_.lz4");
file.open(out_put_file_name, ios::binary | ios::out);
file.write((char*)compressed_data.data(), compressed_data.size());
file.close();
SecureZeroMemory(org_filedata.data(), org_filedata.size());
return TRUE;
}
BOOL LZ4_Decompress_File(char* filename,long originalSize) {
vector<char> decompressed_data;
decompressed_data.resize(originalSize);
buffer org_filedata = readFile(filename);
if (org_filedata.size() == 0) { return FALSE; }
BOOL decompress_data_with_lz4 = lz4_decompress(org_filedata, decompressed_data);
string out_put_file_name(filename);
out_put_file_name = out_put_file_name.replace
(out_put_file_name.find("_.lz4"), sizeof("_.lz4") - 1, "");
file.open(out_put_file_name, ios::binary | ios::out);
file.write((char*)decompressed_data.data(), decompressed_data.size());
file.close();
return TRUE;
}
#pragma endregion
Quick Note: Original size can be appended in the beginning or end of your compressed file.
Creating WPF/Winform User Interface
Okay, Now it's time to create our WPF or Winform user interface, Create a x64 C# Library with your desired platform, I use WPF because it's what I promise in this tutorial, but you can use Winform as well.
The main key of this method is DllExport
from your C# library. We run a C# application right inside the heart of a C++ unmanaged application using DllExport
s and everything will be done by the Operating System, there is no need to use CLRHosting and ActiveX.
For exporting your functions in a managed DLL, you need DllExport
nuget package which you can grab from here.
Create a WPF/Winform window and design your layouts, skins and stuff.
Here's what I designed for my simple compressor with WpfPlus library.
As you can see, there are some buttons and a list for storing files and a log view for data debug, etc.
Quick Note: Before building your GUI library, make sure you set your Window Style to None and Unresizable.
Magic of Connection
Well, it's time to do some magic (science) to connect our Unmanaged world to Managed one.
For creating a clean and good communication between our C++ and C# app, we're going to use function pointers and delegating.
We have two API functions:
LZ4_Compress_File
with boolean output and one filename input LZ4_Decompress_File
with boolean output and two filename and size input
Now we have to get pointers of our functions, add this function pointers typedef
s in your global objects:
typedef void (*LZ4_Compress_File_Ptr)(void);
typedef void (*LZ4_Decompress_File_Ptr)(void);
- In C# project, add a class and name it
UIBridge
and add this function into it based on your platform:
For WPF:
using System;
using DllExportLib; using System.Windows.Interop;
namespace ManagedUIKitWPF
{
class UIBridge
{
public static MainView mainview_ui;
[DllExport]
static public IntPtr CreateUserInterface(IntPtr api_1_ptr, IntPtr api_2_ptr)
{
mainview_ui = new MainView(api_1_ptr, api_2_ptr)
{
Opacity = 0,
Width = 0,
Height = 0
};
mainview_ui.Show();
return new WindowInteropHelper(mainview_ui).Handle;
}
[DllExport]
static public void DisplayUserInterface()
{
mainview_ui.Opacity = 1;
}
[DllExport]
static public void DestroyUserInterface()
{
mainview_ui.Close();
}
}
}
For Winform:
using System;
using DllExportLib; using System.Windows.Forms;
namespace ManagedUIKitWPF
{
class UIBridge
{
public static MainView mainview_ui;
[DllExport]
static public IntPtr CreateUserInterface(IntPtr api_1_ptr, IntPtr api_2_ptr)
{
mainview_ui = new MainView(api_1_ptr, api_2_ptr)
mainview_ui.Opacity = 0f;
mainview_ui.Show();
return mainview_ui.Handle;
}
[DllExport]
static public void DisplayUserInterface()
{
mainview_ui.Opacity = 1.0f;
}
[DllExport]
static public void DestroyUserInterface()
{
mainview_ui.Close();
mainview_ui.Dispose();
}
}
}
Why opacity is 0 in window creation? It's simple, because of flicking free window creation, if you don't use 0 opacity, it flicks randomly when you're trying to show it, we manually display the Window/Form when our host will be ready...
What to do if I had too many functions? Just try to pass an IntPtr
list or array or even simpler, a string
or json data, whatever you want is possible, just find your desired way!
Also, for printing log data from your native app to .NET GUI, use the same method to add PrintLog(string log_str) export and assign them in C++.
- Okay, now go in your window backend code and add delegates of your functions:
using System;
using System.Windows;
using System.Runtime.InteropServices;
namespace ManagedUIKitWPF
{
public partial class MainView : Window
{
delegate bool LZ4_Compress_File_Ptr(string filename);
delegate bool LZ4_Decompress_File_Ptr(string filename,long filesize);
LZ4_Compress_File_Ptr LZ4_Compress_File;
LZ4_Decompress_File_Ptr LZ4_Decompress_File;
public MainView(IntPtr api_1_ptr, IntPtr api_2_ptr)
{
InitializeComponent();
}
}
}
- Now we need to get functions in C# using pointers we passed from C++. We have something amazing in .NET known as
Marshal.GetDelegateForFunctionPointer
which does exactly what we need.
Add this code to your window code right after InitializeComponent()
:
InitializeComponent();
LZ4_Compress_File = (LZ4_Compress_File_Ptr)Marshal.GetDelegateForFunctionPointer
(api_1_ptr, typeof(LZ4_Compress_File_Ptr));
LZ4_Decompress_File = (LZ4_Decompress_File_Ptr)Marshal.GetDelegateForFunctionPointer
(api_2_ptr, typeof(LZ4_Decompress_File_Ptr));
- And finally, it's time to pass the functions from C++. First of all, we need to Load our .NET GUI library, define these objects and values in your Global Objects:
typedef HWND(*CreateUserInterfaceFunc)(LZ4_Compress_File_Ptr, LZ4_Decompress_File_Ptr);
CreateUserInterfaceFunc CreateUserInterface;
typedef void(*DisplayUserInterfaceFunc)(void);
DisplayUserInterfaceFunc DisplayUserInterface;
typedef void(*DestroyUserInterfaceFunc)(void);
DestroyUserInterfaceFunc DestroyUserInterface;
Quick Note: You can use DisplayUserInterfaceFunc
type for both DisplayUserInterface
and DestroyUserInterface
because they're using the same type, but you know ...
- Use
LoadLibrary
and GetProcAddress
to Load your .NET GUI library and functions:
HMODULE dotNetGUILibrary = LoadLibrary(L"ManagedUIKitWPF.dll");
CreateUserInterface = (CreateUserInterfaceFunc)GetProcAddress
(dotNetGUILibrary,"CreateUserInterface");
DisplayUserInterface = (DisplayUserInterfaceFunc)GetProcAddress
(dotNetGUILibrary, "DisplayUserInterface");
DestroyUserInterface = (DestroyUserInterfaceFunc)GetProcAddress
(dotNetGUILibrary, "DestroyUserInterface");
< Add this code before ShowWindow(cpphwin_hwnd, SW_SHOW); >
- Use
CreateUserInterface
just before ShowWindow
and pass function pointers into dotNet Library...
wpf_hwnd = CreateUserInterface(
(LZ4_Compress_File_Ptr)&LZ4_Compress_File,
(LZ4_Decompress_File_Ptr)&LZ4_Decompress_File);
Here we go... now build your application and library and give it a try. If you get error or crash, please review your code with more details...
IMPORTANT NOTE :
If you received any error about STA thread, just add CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
in your code before calling CreateUserInterface function...
WARNING :
If you're using WPF UI for your application don't use Double Buffering (WS_EX_COMPOSITED) feature on your c++ host window, WPF uses DirectX renderer and has already double buffering, If you use it in your host window it will break WPF rendering.
Good job! Now your unmanaged app is connected to your managed GUI under the skin. It's time to connect them in frontend too!
Displaying GUI in C++ Environment
Remember that I riped off the basic part from Heisenberg? Well... it's on ...
A) Displaying WPF Window as a Custom Control (child) in our Host Window
Here's the magic code to turn your WPF window to a child and host it perfectly in your native window:
RECT hwin_rect;
if (wpf_hwnd != nullptr) {
SendMessage(cpphwin_hwnd, WM_SETREDRAW, FALSE, 0);
long dwExStyle = GetWindowLong(cpphwin_hwnd, GWL_EXSTYLE);
dwExStyle &= ~WS_EX_COMPOSITED;
SetWindowLong(cpphwin_hwnd, GWL_EXSTYLE, dwExStyle);
SetWindowLong(wpf_hwnd, GWL_STYLE, WS_CHILD);
GetClientRect(cpphwin_hwnd, &hwin_rect);
MoveWindow(wpf_hwnd, 0, 0, hwin_rect.right - hwin_rect.left,
hwin_rect.bottom - hwin_rect.top, TRUE);
SetWindowPos(wpf_hwnd, HWND_TOP, 0, 0, hwin_rect.right - hwin_rect.left,
hwin_rect.bottom - hwin_rect.top, SWP_NOMOVE);
SetParent(wpf_hwnd, cpphwin_hwnd);
ShowWindow(wpf_hwnd,SW_RESTORE);
DisplayUserInterface();
}
This code should be between CreateUserInterface and ShowWindow
B) Post Control Updates And Messaging
If the user resizes the host window, our control needs to be resized too, go back in HostWindowProc
callback and add resize event and close event into it:
LRESULT CALLBACK HostWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
DestroyUserInterface(); DestroyWindow(hwnd);
break;
case WM_DESTROY:
isHWindowRunning = false;
break;
case WM_SIZE: if (wpf_hwnd!=nullptr) {
GetClientRect(cpphwin_hwnd, &hwin_rect);
MoveWindow(wpf_hwnd, 0, 0, hwin_rect.right - hwin_rect.left,
hwin_rect.bottom - hwin_rect.top, TRUE);
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Okay. :) Time to build your application and library ... and here you go ... you have a smooth and flicker free WPF UI inside your C++ application.
Issue 1: Application Runs But It Freezes
If you launched your EXE and it simply went frozen, don't panic... It's totally natural!
In some cases like Console Apps or Standalone apps in which threading is running as default, WPF UI runs in the same thread and its message loop breaks the main application thread.
In this case, you should Use Multi Threading and create a separate thread for your GUI which makes your application work in two different threads, Backend Thread and Frontend Thread.
It goes even more safer and standard as a complex application. Okay, go in your Managed Bridge and change the code to this:
using System.Threading;
class UIBridge
{
public static MainView mainview_ui;
public static Thread gui_thread;
public static IntPtr mainview_handle = IntPtr.Zero;
[DllExport]
static public IntPtr CreateUserInterface
(IntPtr api_1_ptr, IntPtr api_2_ptr) {
gui_thread = new Thread(() =>
{
mainview_ui = new MainView(api_1_ptr, api_2_ptr)
{Opacity = 0,Width = 0,Height = 0};
mainview_ui.Show();
mainview_handle = new WindowInteropHelper(mainview_ui).Handle;
System.Windows.Threading.Dispatcher.Run();
});
gui_thread.SetApartmentState(ApartmentState.STA); gui_thread.Start();
while (mainview_handle == IntPtr.Zero) { }
return mainview_handle;
}
[DllExport]
static public void DisplayUserInterface() {
try
{
mainview_ui.Opacity = 1;
}
catch {
mainview_ui.Dispatcher.BeginInvoke((Action)(() => {
mainview_ui.Opacity = 1;
}));
}
}
[DllExport]
static public void DestroyUserInterface() {
try {
mainview_ui.Close();
}
catch {
mainview_ui.Dispatcher.BeginInvoke((Action)(()=> {
mainview_ui.Close();
}));
}
}
}
Now build and try again... It's working now! :)
Issue 2: I Don't Have Access to Host Window Creation
Ok folks, there are some situations when we don't have full access to host window creation, like when we want to use our WPF UI in some native plugin for applications like 3ds Max, Photoshop, QT Apps, etc.
As a plugin, SDKs lets you create Child Windows, Panels, Rollups, Rollouts, Custom Controls and returns a handle to it, but underneath all of this guys are Window!
In this case, there are three issues:
- We don't have access to the host window WndProc Callback
- We don't have access to the host window Handle Pointer
- We don't know the structure of host window in target application
1) How to Deal With Lack of WndProc Control
In this case, you need to set a Window Hook on your host window to catch its messages, this method has security side effects if you try to use it on another process window because this is what keyloggers do, but in our case it's a plugin and it's running inside the same process so... there's no problem and any issue to doing this and Windows defender doesn't bust your ass on this. :)
To setup a windows hook on your host window, you only need its handle and most of the SDKs return the handle of host window/panel and we use this handle via SetWindowSubclass
to set an alternative message capture callback to our host window/panel.
Create the same HostWindowProc
in your plugin code and add this code just after you showed your window/panel:
SetWindowSubclass(<Window/Panel Handle>, &HostWindowProc, <Subclass UID example '6663'>, 0);
And don't forget to apply two changes in your WndProc
:
- Add
RemoveWindowSubclass(hWnd, &HostWindowProc, 1);
in WM_DESTROY
- Change your
HostWindowProc
CALLBACK
parameters to:
LRESULT CALLBACK HostWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
Done! Now you can handle resizing and other message events on any kind of window and panel.
2) How to Deal With Missing Handles
In this case, you need to query your target application windows and find the created host window/panel to be able to do the rest. If SDK doesn't give you the handle, it lets you provide a title for it, you just need to query and find it by its title.
To find all child windows in your process, you need to use EnumChildWindows
and GetWindowText
or GetClassName
:
HWND host_window_handle;
BOOL CALLBACK HostWindowQueryCB(HWND hwnd, LPARAM lParam)
{
TCHAR* target_class_name = L"Window/Panel Class Name";
TCHAR* target_text = L"Window/Panel Title Text";
TCHAR wnd_class_name[MAX_PATH];
GetClassName(hwnd, wnd_class_name, _countof(wnd_class_name));
TCHAR wnd_title_text[MAX_PATH];
GetWindowText(hwnd,wnd_title_text,MAX_PATH);
if (_tcscmp(wnd_title_text, target_text) == 0) {
host_window_handle = hwnd;
return FALSE; }
if (_tcscmp(wnd_class_name, target_class_name ) == 0) {
host_window_handle = hwnd;
return FALSE; }
return TRUE;
}
And use this function to start the query ...
EnumChildWindows(<Your Application Main Window Handle>, HostWindowQueryCB, 0);
Optimization Tip: You can use GetWindowThreadProcessId
and GetCurrentProcessId
in an IF_ELSE
to check if window belongs to your current process or not, if yes, then do the rest getting & comparing and stuff.
3) How to Deal Unknown Structures of Target Application
It's really simple! You can use a great and lightweight tool from Microsoft "Spy++". You can find this little guy in your Visual Studio folder Microsoft Visual Studio\20XX\Enterprise\CommonX\Tools.
Programming Frontend UI
Congratulations! You could make your way to the final stage... It's time to develop our managed frontend and make everything work just fine...
In Frontend development, we have two different strategies:
- Do everything in backend and just use the C# to calling functions, displaying data or drawing custom things.
- Use C# more than just a UI and gain more power in development!
Example: In my compressor, I can use buttons to add files in backend C++ list and do everything behind and just display data in WPF list and use buttons to call compress/decompress functions or I can use C# to manage files, compressing and decompressing processes which saves me a lot of time!
It completely depends on your decision and we're living in a free world, right? ;)
Using Your Native API in Your Managed App Is Like
private void Compress_Button_Click(object sender, RoutedEventArgs e)
{
string file_path = get_selected_file();
bool compression_process = LZ4_Compress_File(file_path);
if (compression_process)
PrintLog($"File '{Path.GetFileName(file_path)}' has been compressed successfully!");
else
PrintLog($"File '{Path.GetFileName(file_path)}' compression failed.");
}
After finishing your UI programming, build your application and library. Run your C++ application.
Now you're watching the magic of science... :)
Optimal Solutions
Packing Your GUI in C++ Application
You can use an amazing free application "Enigma Virtual Box" which you can grab from here on the official site.
Select your C++ EXE and add your WPF GUI library .dll file in root of virtual box, then build your EXE to a single one and use compression in settings.
Now you have a clean, lightweight and single file C++ application with a beautiful, smooth, flicker free WPF user interface.
Improving Security of Your Native Functions
To secure your C++ API pointers from unofficial assemblies, you can use MD5 or SHA hashing technique to check if requester has been modified or not.
Let me introduce you to an amazing library Digest++ which gives you a perfect hashing methods and it's header-only!
To secure your native functions:
- After building the final version of GUI library, DLL get its hash in MD5, SHA512, SHA1, etc.
- Write it down and store the hash as XOR Obfuscated String (Or AES256) in your main C++ application.
- Before using
LoadLibrary
function to load your GUI library DLL, get its hash again and compare it to pre-calculated hash you generated before and if it matched, continue the processing...
Converting to Windows SubSystem
After you're done with debugging, you can turn your console application to Windows SubSystem following:
- replace int
main()
with int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
- Go in C++ Project Settings>Linker>System and change
SubSystem
from Console (/SUBSYSTEM:CONSOLE)
to Windows (/SUBSYSTEM:WINDOWS)
Using this Method in QT Framework
Everything in QT is like Native WinAPI, you just need to follow these tips:
- Use this function to get Native Handle of your QT Window/Widget:
HWND GetQTNativeHwnd = (HWND)MyQTWindow->winId();
- Instead of using
Rect
and GetClientArea
, use:
QSize QTHostSize = MyQTWindow->size();
int win_w = QTHostSize.width();
int win_h = QTHostSize.height();
MoveWindow(wpf_hwnd, 0, 0, win_w, win_h, TRUE);
SetWindowPos(wpf_hwnd, HWND_TOP, 0, 0, win_w, win_h, SWP_NOMOVE);
Source Code
You can download the full source code and final binary from the links below:
What's Next?
In the next section of my article series, I will teach you how to directly use all of your C++ Memory and Values inside C# managed application so you don't have to build new values, pass them or allocate more memory.
This technique is a great help when you're dealing with huge amount of data!
I hope this tutorial has been helpful to you. :)
Also, feel free to follow my latest application under development in its official discord server.
Happy New Year!
History
- 2nd January, 2020: First release
- 6nd January, 2020: Qt fully-featured example source code added.