I have a new application written in WPF that needs to support an old API that allows it to receive a message that has been posted to a hidden window. Typically another application uses FindWindow to identify the hidden window using the name of its custom window class.
1) I assume to implement a custom window class I need to use old school win32 calls?
My old c++ application used RegisterClass and CreateWindow to make the simplest possible invisible window.
I believe I should be able to do the same all within c#. I don't want my project to have to compile any unmanaged code.
I have tried inheriting from System.Windows.Interop.HwndHost and using System.Runtime.InteropServices.DllImport to pull in the above API methods.
Doing this I can successfully host a standard win32 window e.g. "listbox" inside WPF. However when I call CreateWindowEx for my custom window it always returns null.
My call to RegisterClass succeeds but I am not sure what I should be setting the WNDCLASS.lpfnWndProc member to.
2) Does anyone know how to do this successfully?
A window class is supported by a window procedure. Your application can register a window class by using either RegisterClassA or RegisterClassW. New applications should typically use RegisterClassW.
A window class defines the attributes of a window, such as its style, icon, cursor, menu, and window procedure. The first step in registering a window class is to fill in a WNDCLASSEX structure with the window class information. For more information, see Elements of a Window Class.
WinAPI (also known as Win32; officially called the Microsoft Windows API) is an application programming interface written in C by Microsoft to allow access to Windows features. The main components of the WinAPI are: WinBase: The kernel functions, CreateFile, CreateProcess, etc.
A Windows window is identified by a "window handle" ( HWND ) and is created after the CWnd object is created by a call to the Create member function of class CWnd . The window may be destroyed either by a program call or by a user's action. The window handle is stored in the window object's m_hWnd member variable.
For the record I finally got this to work. Turned out the difficulties I had were down to string marshalling problems. I had to be more precise in my importing of win32 functions.
Below is the code that will create a custom window class in c# - useful for supporting old APIs you might have that rely on custom window classes.
It should work in either WPF or Winforms as long as a message pump is running on the thread.
EDIT: Updated to fix the reported crash due to early collection of the delegate that wraps the callback. The delegate is now held as a member and the delegate explicitly marshaled as a function pointer. This fixes the issue and makes it easier to understand the behaviour.
class CustomWindow : IDisposable
{
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.StructLayout(
System.Runtime.InteropServices.LayoutKind.Sequential,
CharSet = System.Runtime.InteropServices.CharSet.Unicode
)]
struct WNDCLASS
{
public uint style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
public string lpszMenuName;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
public string lpszClassName;
}
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern System.UInt16 RegisterClassW(
[System.Runtime.InteropServices.In] ref WNDCLASS lpWndClass
);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern IntPtr CreateWindowExW(
UInt32 dwExStyle,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
string lpClassName,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
string lpWindowName,
UInt32 dwStyle,
Int32 x,
Int32 y,
Int32 nWidth,
Int32 nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam
);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern System.IntPtr DefWindowProcW(
IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam
);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern bool DestroyWindow(
IntPtr hWnd
);
private const int ERROR_CLASS_ALREADY_EXISTS = 1410;
private bool m_disposed;
private IntPtr m_hwnd;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!m_disposed) {
if (disposing) {
// Dispose managed resources
}
// Dispose unmanaged resources
if (m_hwnd != IntPtr.Zero) {
DestroyWindow(m_hwnd);
m_hwnd = IntPtr.Zero;
}
}
}
public CustomWindow(string class_name){
if (class_name == null) throw new System.Exception("class_name is null");
if (class_name == String.Empty) throw new System.Exception("class_name is empty");
m_wnd_proc_delegate = CustomWndProc;
// Create WNDCLASS
WNDCLASS wind_class = new WNDCLASS();
wind_class.lpszClassName = class_name;
wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);
UInt16 class_atom = RegisterClassW(ref wind_class);
int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
throw new System.Exception("Could not register window class");
}
// Create window
m_hwnd = CreateWindowExW(
0,
class_name,
String.Empty,
0,
0,
0,
0,
0,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);
}
private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
private WndProc m_wnd_proc_delegate;
}
I'd like to comment the answer of morechilli:
public CustomWindow(string class_name){
if (class_name == null) throw new System.Exception("class_name is null");
if (class_name == String.Empty) throw new System.Exception("class_name is empty");
// Create WNDCLASS
WNDCLASS wind_class = new WNDCLASS();
wind_class.lpszClassName = class_name;
wind_class.lpfnWndProc = CustomWndProc;
UInt16 class_atom = RegisterClassW(ref wind_class);
int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
throw new System.Exception("Could not register window class");
}
// Create window
m_hwnd = CreateWindowExW(
0,
class_name,
String.Empty,
0,
0,
0,
0,
0,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);
}
In the constructor I copied above is slight error: The WNDCLASS instance is created, but not saved. It will eventually be garbage collected. But the WNDCLASS holds the WndProc delegate. This results in an error as soon as WNDCLASS is garbage collected. The instance of WNDCLASS should be hold in a member variable until the window is destroyed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With