Posted by: Zeeshan Amjad | March 3, 2010

Calling WPF Dialog from Unmanaged Code


We have already saw example of calling WPF dialog from VC++ here. But in that program we compile our VC++ program with /clr switch. It means our VC++ program is actually a managed program.

This time we are going to see how we can call the WPF from unmanaged code. To use .Net assembly from unmanaged code, we have to expose it as a COM component and use it as a COM from unmanaged code. There is one .Net utility called RegAsm to register .net assembly. We can also create the type library from the .net component and create the MFC class wrapper from it.

We have to create an interface in our .net project and implement that interface in our .net class. We also give the guid attribute to our class and interface. We can create a guid from guidgen utility. Here is simple WPF based program.

  1: using System;
  2: using System.Windows;
  3: using System.Reflection;
  4: using System.Runtime.InteropServices;
  5: 
  6: [ComVisible(true)]
  7: [Guid("B0242D62-DE04-452d-BB4B-81E4C072339F")]
  8: public interface IWpfDialog
  9: {
 10:     [DispId(1)]
 11:     void DisplayDialog();
 12: }
 13: 
 14: [ComVisible(true)]
 15: [ClassInterface(ClassInterfaceType.None)]
 16: [Guid("D1CC2EAB-34E1-4844-A7BB-0FDF19CBED32")]
 17: [ProgId("MyWpfDialog")]
 18: public class MyDialog : IWpfDialog
 19: {
 20:     private Window win = new Window();
 21: 
 22:     [ComVisible(true)]
 23:     public void DisplayDialog()
 24:     {
 25:         win.Title = "WPF Dialog";
 26:         win.Width = 400;
 27:         win.Height = 300;
 28:         win.Content = "Hello world";
 29:         win.Show();
 30:     }
 31: }
 32: 

Now we are going to create a MFC wrapper class from Visual Studio. Here is our wrapper MFC class.

  1: // Machine generated IDispatch wrapper class(es) created with Add Class from Typelib Wizard
  2: 
  3: #import "C:\\zamjad\\Projects\\WpfDialog\\WpfDialog\\obj\\Debug\\wpfdialog.tlb" no_namespace
  4: // CWpfDialog wrapper class
  5: 
  6: class CWpfDialog : public COleDispatchDriver
  7: {
  8: public:
  9: 	CWpfDialog(){} // Calls COleDispatchDriver default constructor
 10: 	CWpfDialog(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
 11: 	CWpfDialog(const CWpfDialog& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}
 12: 
 13: 	// Attributes
 14: public:
 15: 
 16: 	// Operations
 17: public:
 18: 
 19: 
 20: 	// IWpfDialog methods
 21: public:
 22: 	void DisplayDialog()
 23: 	{
 24: 		InvokeHelper(0x1, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
 25: 	}
 26: 
 27: 	// IWpfDialog properties
 28: public:
 29: 
 30: };
 31: 

Now we have to create class of this and call the method of .net assembly. Here is a piece of code to show this.

  1: CWpfDialog dlg;
  2: 
  3: try
  4: {
  5: 	if (dlg.CreateDispatch(__uuidof(MyDialog)) == FALSE)
  6: 	{
  7: 		AfxMessageBox(_T("Can not create object"));
  8: 		return;
  9: 	}
 10: 
 11: 	dlg.DisplayDialog();
 12: }
 13: catch (...)
 14: {
 15: 	MessageBox(_T("ERror"), _T("Unkown Error"));
 16: }	
 17: 

We create a small dialog based program and write this code at the handler of one button. Here is a code of dialog source code.

  1: 
  2: // WpfVCClientDlg.cpp : implementation file
  3: //
  4: 
  5: #include "stdafx.h"
  6: #include "WpfVCClient.h"
  7: #include "WpfVCClientDlg.h"
  8: #include "CWpfDialog.h"
  9: 
 10: #ifdef _DEBUG
 11: #define new DEBUG_NEW
 12: #endif
 13: 
 14: 
 15: // CAboutDlg dialog used for App About
 16: 
 17: class CAboutDlg : public CDialog
 18: {
 19: public:
 20: 	CAboutDlg();
 21: 
 22: 	// Dialog Data
 23: 	enum { IDD = IDD_ABOUTBOX };
 24: 
 25: protected:
 26: 	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
 27: 
 28: 	// Implementation
 29: protected:
 30: 	DECLARE_MESSAGE_MAP()
 31: };
 32: 
 33: CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
 34: {
 35: }
 36: 
 37: void CAboutDlg::DoDataExchange(CDataExchange* pDX)
 38: {
 39: 	CDialog::DoDataExchange(pDX);
 40: }
 41: 
 42: BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
 43: END_MESSAGE_MAP()
 44: 
 45: 
 46: // CWpfVCClientDlg dialog
 47: 
 48: 
 49: 
 50: 
 51: CWpfVCClientDlg::CWpfVCClientDlg(CWnd* pParent /*=NULL*/)
 52: : CDialog(CWpfVCClientDlg::IDD, pParent)
 53: {
 54: 	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 55: }
 56: 
 57: void CWpfVCClientDlg::DoDataExchange(CDataExchange* pDX)
 58: {
 59: 	CDialog::DoDataExchange(pDX);
 60: }
 61: 
 62: BEGIN_MESSAGE_MAP(CWpfVCClientDlg, CDialog)
 63: 	ON_WM_SYSCOMMAND()
 64: 	ON_WM_PAINT()
 65: 	ON_WM_QUERYDRAGICON()
 66: 	//}}AFX_MSG_MAP
 67: 	ON_BN_CLICKED(IDOK, &CWpfVCClientDlg::OnBnClickedOk)
 68: 	ON_BN_CLICKED(IDC_BTN_MESSAGE, &CWpfVCClientDlg::OnBnClickedBtnMessage)
 69: END_MESSAGE_MAP()
 70: 
 71: 
 72: // CWpfVCClientDlg message handlers
 73: 
 74: BOOL CWpfVCClientDlg::OnInitDialog()
 75: {
 76: 	CDialog::OnInitDialog();
 77: 
 78: 	// Add "About..." menu item to system menu.
 79: 
 80: 	// IDM_ABOUTBOX must be in the system command range.
 81: 	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 82: 	ASSERT(IDM_ABOUTBOX < 0xF000);
 83: 
 84: 	CMenu* pSysMenu = GetSystemMenu(FALSE);
 85: 	if (pSysMenu != NULL)
 86: 	{
 87: 		BOOL bNameValid;
 88: 		CString strAboutMenu;
 89: 		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
 90: 		ASSERT(bNameValid);
 91: 		if (!strAboutMenu.IsEmpty())
 92: 		{
 93: 			pSysMenu->AppendMenu(MF_SEPARATOR);
 94: 			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
 95: 		}
 96: 	}
 97: 
 98: 	// Set the icon for this dialog.  The framework does this automatically
 99: 	//  when the application's main window is not a dialog
100: 	SetIcon(m_hIcon, TRUE);			// Set big icon
101: 	SetIcon(m_hIcon, FALSE);		// Set small icon
102: 
103: 	// TODO: Add extra initialization here
104: 
105: 	return TRUE;  // return TRUE  unless you set the focus to a control
106: }
107: 
108: void CWpfVCClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
109: {
110: 	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
111: 	{
112: 		CAboutDlg dlgAbout;
113: 		dlgAbout.DoModal();
114: 	}
115: 	else
116: 	{
117: 		CDialog::OnSysCommand(nID, lParam);
118: 	}
119: }
120: 
121: // If you add a minimize button to your dialog, you will need the code below
122: //  to draw the icon.  For MFC applications using the document/view model,
123: //  this is automatically done for you by the framework.
124: 
125: void CWpfVCClientDlg::OnPaint()
126: {
127: 	if (IsIconic())
128: 	{
129: 		CPaintDC dc(this); // device context for painting
130: 
131: 		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
132: 
133: 		// Center icon in client rectangle
134: 		int cxIcon = GetSystemMetrics(SM_CXICON);
135: 		int cyIcon = GetSystemMetrics(SM_CYICON);
136: 		CRect rect;
137: 		GetClientRect(&rect);
138: 		int x = (rect.Width() - cxIcon + 1) / 2;
139: 		int y = (rect.Height() - cyIcon + 1) / 2;
140: 
141: 		// Draw the icon
142: 		dc.DrawIcon(x, y, m_hIcon);
143: 	}
144: 	else
145: 	{
146: 		CDialog::OnPaint();
147: 	}
148: }
149: 
150: // The system calls this function to obtain the cursor to display while the user drags
151: //  the minimized window.
152: HCURSOR CWpfVCClientDlg::OnQueryDragIcon()
153: {
154: 	return static_cast<HCURSOR>(m_hIcon);
155: }
156: 
157: 
158: void CWpfVCClientDlg::OnBnClickedOk()
159: {
160: 	// TODO: Add your control notification handler code here
161: 	OnOK();
162: }
163: 
164: void CWpfVCClientDlg::OnBnClickedBtnMessage()
165: {
166: 	CWpfDialog dlg;
167: 
168: 	try
169: 	{
170: 		if (dlg.CreateDispatch(__uuidof(MyDialog)) == FALSE)
171: 		{
172: 			AfxMessageBox(_T("Can not create object"));
173: 			return;
174: 		}
175: 
176: 		dlg.DisplayDialog();
177: 	}
178: 	catch (...)
179: 	{
180: 		MessageBox(_T("ERror"), _T("Unkown Error"));
181: 	}	
182: }
183: 

Important thing is that you have to place the executable in the same location where .net assembly reside. If we want to place .net assembly somewhere else then we have to register that assembly in global cache using “gacutil” utility. But we have to sign that assembly fist with strong name using one more .net assembly “sn”. Here is the output of the program when we click on the button on unmanaged program.

WPFFromUnmanaged

Advertisements

Responses

  1. Thanks for the great post. Where can I get the source code for this example? Thanks again.

    • Hi

      All the required code for the program is already posted in the blog. Do you need help on some specific issue?

      Thanks

  2. Do you not need to call CoInitialize(NULL); prior to using the COM objects in the MFC app?

    • Yes you are right. It seems that i forgot to paste the application class here. I did this in InitInstance method of application class and rest of the code is wizard generated.

  3. Can i get other sources without MFC?

    • Please correct me if I don’t understand your question correctly. You means you want to access WPF dialog without using MFC? Technically you can. You can use ATL, some smart pointer classes, your own C++ classes even with plain C and assembly languagea. But in that case you migh ended up writing lots of code yourself. I still remember when we are trying to do this while reading the Don Box book “Essential COM” (very long chapters 🙂 ) or Inside COM by Dale Rogerson.

      Regards
      Zeeshan Amjad

  4. The source code would be very nice, because when I copy and paste the code I get line numbers and a lot of empty lines!

    • I understand your problem, I am not expert of wordpress blog. Surly will take a look at this and try to solve it as soon as possible. Thanks for visiting.

  5. In addition, it seems that some part is missing, the application class, as per the comments above. So the full source code would be nice!

    • Thanks for visiting. Yes I agree complete source code would be nice. It is just a simple dialog based application created by MFC wizard. I hope it helps.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: