I working on an MFC class which derives from CWnd and creates a hidden window in its constructor. The object itself is constructed inside a derived CWinApp::InitInstance function.
if (
this->CWnd::Create(
nullptr,
nullptr,
WS_DISABLED, // Even disabled it will receive broadcast messages.
{0, 0, 0, 0},
CWnd::GetDesktopWindow(),
fakeWindowId
) == FALSE
)
throw runtime_error{"failed to create window"};
When I run this code in a debug build it triggers the following assertion:
Debug Assertion Failed!
Program: C:\WINDOWS\SYSTEM32\mfc140ud.dll File:
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp Line: 571
For information on how your program can cause an assertion failure,
see the Visual C++ documentation on asserts.
(Press Retry to debug the application)
If I ignore the assertion, the code works fine and no ill effect is observable. How do I address this assertion?
I'm registering the window as follows as well:
BOOL HiddenWindow::PreCreateWindow(CREATESTRUCTW& cs)
{
if ( ! CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
WNDCLASSEXW wc;
ZeroMemory(&wc, sizeof(WNDCLASSEXW));
wc.cbSize = sizeof(WNDCLASSEXW);
const auto instance{AfxGetInstanceHandle()};
if (GetClassInfoExW(instance, this->className_.c_str(), &wc) == FALSE)
{
wc.lpszClassName = this->className_.c_str();
if ( ! RegisterClassExW(&wc))
{
Logger::Fatal(
"Registering the window for copy data message failed! Messages will not be "
"copied, error code {}.",
GetLastError()
);
return FALSE;
}
}
else
Logger::Debug(
"There is already a window registered under the class name '{}'.",
toString(this->className_)
);
cs.lpszClass = _wcsdup(this->className_.c_str());
return TRUE;
}
So, I was unable to ever figure out what caused the MFC assertion. The solution was to remove the MFC window entirely and replace it with a Win32 window underneath the class, i.e. CreateWindowExW, GetClassinfoExW, and RegisterClassExW.
Related
How can I know that for a particular ComboBox which Dialog Style is being used? Is there any Win32 API which can give me that information?
I am using CDialog for a few ComboBox, CDialogEx for some, and an in-house Dialog class, let's say Ctl3dDialogEx, for others. GetClassName() will return the Class name of the ComboBox (if I am passing a ComboBox Handler) which can be "CComboBox".
Is there any Win32 API where I will pass the ComboBox Handler and it will return back to me the Dialog class name, for eg : "CDialog", "CDialogEx", "Ctl3dDialogEx", etc?
Below code will help to understand maybe:
void ComboBox_DoSomeManipulation( HWND hldg , int n )
{
/*"hldg" is the handler of the Current ComBobox */
LPTSTR lpClassName;
int nMaxCount = 256;
/*This will return "CComboBox" as value in lpClassName */
GetClassName(hldg , lpClassName , _count_of(nMaxCount));
/*Is there any WIN API like above which can give */
/* Dialog class information like CDialog, CDialogEx */
/* which the ComboBox is using ? */
}
If your combo-box can somehow get hold of a genuine pointer to its parent window, then you can use dynamic_cast<CDialogEx*>(pParent) to see if it's CDialogEx (returns nullptr if not). You will need several separate checks, starting from the most derived class! So, if your Ctl3dDialogEx is derived from CDialogEx, then:
. . .
CWnd *pParent = pCombo->GetParent(); // This, as is, is not valid code!
if (dynamic_cast<Ctl3dDialogEx*>(pParent) != nullptr) {
// It's a Ctl3dDialogEx parent
}
else if (dynamic_cast<CDialogEx*>(pParent) != nullptr) {
// It's a CDialogEx
}
else { // Assuming no other options …
// It's a CDialog
}
I would recommend making an accessible (static?) copy of the parent window's this pointer during initialisation, if you can. But there are other ways …
For example, assuming you have control over the definition of ComboBox_DoSomeManipulation and when it's called, change the first argument from an HWND to a CWnd* and, when you call it, use this rather than this->m_hwnd. (But this depends on the structure of your code!)
There is no Windows API help since all those dialogs will be subclassing the Windows DIALOG class. If this is all in process, and you are using the same MFC instance, you might be able to do this:
CWnd* pWnd = CWnd::FromHandlePermanent(hdlg);
if (pWnd != NULL)
{
if (pWnd->GetRuntimeClass() == RUNTIME_CLASS(CDialog))
{
}
else if (pWnd->GetRuntimeClass() == RUNTIME_CLASS(CDialogEx))
{
}
else if (pWnd->GetRuntimeClass() == RUNTIME_CLASS(CDialogxyz))
{
}
}
Back in the old days, MS compilers used with MFC didn't play well with dynamic_cast<>, so generally, when using MFC, I don't use it. I probably should have more trust in it, but I was stuck using Visual C++ 6 until 2008, so I am probably a little jaded. The more "standard" "MFC way" is to use the MFC macros...
Another possible ways is something like:
if (CDialogxyz* pDlgxyz = DYNAMIC_DOWNCAST(CDialogxyz, pWnd))
{
}
else if (CDialogEx* pDlgEx = DYNAMIC_DOWNCAST(CDialogEx, pWnd))
{
}
else if (CDialog* pDlg = DYNAMIC_DOWNCAST(CDialog, pWnd))
{
}
I'm doing maintenance on a legacy MFC application. When the user selects Help → Help from the main menu along the top of the app, I need my custom OnHelp() to be called. From my research, I've learned that MFC normally intercepts this command automatically and brings up your help file itself. But you can override this command and intercept this message yourself. I have this in my message map:
BEGIN_MESSAGE_MAP(MyApp, MyBaseApp)
//{{AFX_MSG_MAP(MyApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_HELP, OnHelp)
//}}AFX_MSG_MAP
// Standard file based document commands
END_MESSAGE_MAP()
The "About" OnAppAbout() gets called, but not my OnHelp() (MFC still intercepts it and brings up the help by itself). My implementation is pretty straightforward:
void MyApp::OnHelp()
{
// This never gets called
MessageBox( NULL, "HtmlHelp: MyApp", "Hey", MB_OK );
CString csHelpFile;
csHelpFile.Format( "%s/MyHelp.chm", MyDoc::GetHelpPath() );
::HtmlHelp(
NULL,
csHelpFile,
HELP_WM_HELP,
NULL );
}
I know it's not getting called because my MessageBox never appears (I can't use Visual Studio to debug this; message boxes only). I also tried to wedge it into the CMDIFrameWnd, with the message map and similar implementation, with no success either (a different item from the Help menu item is implemented here and works fine). Any idea what I need to do to hook into my own custom help function?
You need to add ON_WM_HELPINFO() into your CMainFrame's message map. Define afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo); in your header file for the Main Frame, and implement it in the cpp file:
BOOL CMainFrame::OnHelpInfo(HELPINFO* pHelpInfo)
{
#ifdef _DEBUG
MessageBox( NULL, "HtmlHelp: MyApp", "Hey", MB_OK );
#endif
CString csHelpFile;
csHelpFile.Format( "%s/MyHelp.chm", MyDoc::GetHelpPath() );
::HtmlHelp(
NULL,
csHelpFile,
HELP_WM_HELP,
NULL );
return CFrameWnd::OnHelpInfo(pHelpInfo);
}
I have written an MFC dialog based application which is launched by some another application. For now, I have not added any code. It is just the default files that I got. The other application can successfully launch my application.
I am trying to hide the window of my application when the other application launches it.
BOOL CMyApp::InitInstance()
{
CMyAppDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
}
else if (nResponse == IDCANCEL)
{
}
return FALSE;
}
I tried to use:
dlg.ShowWindow(SW_HIDE)
but it still does not hide the window.
How can I accomplish this task?
I'd suggest you have another problem someplace.
If you create a totally new, blank MFC app (Visual Studio 2010) then in App::InitInstance, setting SW_HIDE rather than SW_SHOW does cause the resultant window to be hidden.
BOOL CProj1App::InitInstance()
{
// boilerplate code
. . .
// The one and only window has been initialized, so show and update it
m_pMainWnd->ShowWindow(SW_HIDE); // WORKS!
m_pMainWnd->UpdateWindow();
return TRUE;
}
As soon as you call DoModal your dialog is doomed to be shown. There is only one workaround that successfully avoids focus/flicker problems. See my answer here: Hiding an MFC dialog box
Hence, your code should look like this:
BOOL CMyApp::InitInstance()
{
CMyAppDlg dlg;
dlg.SetVisible(FALSE); // Sets m_visible flag to FALSE.
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
}
else if (nResponse == IDCANCEL)
{
}
return FALSE;
}
Solution to the above issue. The InitInstance code should be as follows:
BOOL CMyApp::InitInstance()
{
CWinApp::InitInstance();
AfxEnableControlContainer();
CMyAppDlg dlg;
dlg.Create(IDD_MyAppUI_DIALOG,NULL);
dlg.ShowWindow(SW_HIDE);
dlg.UpdateWindow();
m_pMainWnd = &dlg;
return TRUE;
}
First of all let me address some issues with previous solutions.
chintan s:
Indeed dialog will be killed when function goes out of scope. It would be a valid solution if dialog was declared as a member variable of the app class.
Vikky:
No need to call Windows API, since dialog is derived from CWnd and it inherits ShowWindow member that take only one parameter: show command.
ixe013:
This solution will work, however, before dialog hides, it will flash, since ShowWindow is called before OnInitDialog is called.
Pete:
This won’t work, since, modal dialog starts before m_pMainWnd has any value assigned to it.
The solution is pointed by ixe013.
This is so far the only solution that works but you will have to declare member variable in you dialog class, as described in the article.
You must hide the dialog from the inside.
Overload OnInitDialog
Call CDialogEx::OnInitDialog()
Hide your window and return
Here is the code
BOOL CMyAppDlg::OnInitDialog()
{
BOOL result = CDialogEx::OnInitDialog();
this->ShowWindow(SW_HIDE);
return result; // return TRUE unless you set the focus to a control
}
There is another method with a sentinel value, YMMV.
The showWindow method has 2 variable.
handle of window
nCmdShow(Controls how the window is to be shown)
BOOL WINAPI ShowWindow(
In HWND hWnd,
In int nCmdShow
);
HWND hWnd = GetSafeHwnd();
ShowWindow(hWnd,SW_HIDE);
See HERE
I have an application that sometimes causes exceptions. And I need to restart it if it crashed. But the problem is, that I have Windows 7 here and when application crashes, Windows shows me nice dialog box with a suggestion to close the application. But the application itself is still running until I click "Close". How to get rid of this Window and make application terminate immediately without any dialog boxes?
Ideally you would catch all exceptions in the most outer scope of your program. However, sometimes you don't have the luxury of making such changes and the crash dialogs prevent you from recovering from a crash. In these cases you can consider disabling Windows Error Reporting, either entirely or for a specific program.
On Windows 7: Start Orb -> Control Panel -> Action Center -> Maintenance -> Check for solutions to problem reports -> Settings
Update: to completely disable the error reporting UI change the DontShowUI registry setting:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\DontShowUI
You can use the concept of the Dr Watson for detecting the crash and restarting the application. Use the following:
Set a Timer like:
SetTimer( eIsDwwin, 30000, NULL);
This will check the Dr Watson process after every 30 seconds in the system process using the following:
void CMainFrame::OnTimer(UINT nIDEvent) {
case eIsDwwin:
HANDLE hndl = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
DWORD dwsma = GetLastError();
DWORD dwExitCode = 0;
PROCESSENTRY32 procEntry={0};
procEntry.dwSize = sizeof( PROCESSENTRY32 );
Process32First(hndl,&procEntry);
do {
if(((!strcmpi(procEntry.szExeFile,"dwwin.exe"))||
(!strcmpi(procEntry.szExeFile,"drwatson.exe"))||
(!strcmpi(procEntry.szExeFile,"drwtsn32.exe"))||
(!strcmpi(procEntry.szExeFile,"WerFault.exe"))) &&
(procEntry.th32ParentProcessID == GetCurrentProcessId())) {
WSACleanup();
PostMessage( WM_CLOSE);
Sleep(500);
if(0==strcmpi(procEntry.szExeFile,"dwwin.exe")) {
system("TASKKILL /IM dwwin.exe /F");
} else if(0==strcmpi(procEntry.szExeFile,"drwatson.exe")) {
system("TASKKILL /IM drwatson.exe /F");
} else if(0==strcmpi(procEntry.szExeFile,"drwtsn32.exe")) {
system("TASKKILL /IM drwtsn32.exe /F");
} else if(0==strcmpi(procEntry.szExeFile,"WerFault.exe")) {
system("TASKKILL /IM WerFault.exe /F");
} else {
system("TASKKILL /IM WerFault.exe /F");
}
break;
}
} while(Process32Next(hndl,&procEntry));
CloseHandle(hndl);
break;
}
}
This will close the application which has crashed, gracefully.
Anothe way would be to use the Signal API in Windows for handling the exceptions and crashes.
Not knowing the language of your application makes it a little tough, but you can typically handle that by running your application within the equivalent of a try / catch block (as in C#, C++, or Python). In pseudo-Csharp-ish-code:
bool done = false;
main()
{
while( !done )
{
AppClass aclass = new AppClass();
try
{
aclass->Run();
}
catch( Exception x )
{
LogErrorSomehow(x);
delete aclass;
continue;
}
}
}
Essentially, the best way to get around an "unhandled exception" error is to handle the exception by catching it and taking appropriate action (even if that action is doing nothing).
In this example, if someone wanted to close your app you'd set the "done" value to true and return from Run().
I'm implementing an application which uses COM in AutoCAD's ObjectARX interface to automate drawing actions, such as open and save as.
According to the documentation, I should be able to call AcadDocument.SaveAs() and pass in a filename, a "save as type" and a security parameter. The documentation explicitly statses that if security is NULL, no security related operation is attempted. It doesn't, however, give any indication of the correct object type to pass as the "save as type" parameter.
I've tried calling SaveAs with a filename and null for the remaining arguments, but my application hangs on that method call and AutoCAD appears to crash - you can still use the ribbon but can't do anything with the toolbar and can't close AutoCAD.
I've got a feeling that it's my NULL parameters causing grief here, but the documentation is severely lacking in the COM/VBA department. In fact it says the AcadDocument class doesn't even have a SaveAs method, which it clearly does.
Has anyone here implemented the same thing? Any guidance?
The alternative is I use the SendCommand() method to send a _SAVEAS command, but my application is managing a batch of drawing and needs to know a) if the save fails, and b) when the save completes (which I'm doing by listening to the EndSave event.)
EDIT
Here's the code as requested - all it's doing is launching AutoCAD (or connecting to the running instance if it's already running), opening an existing drawing, then saving the document to a new location (C:\Scratch\Document01B.dwg.)
using (AutoCad cad = AutoCad.Instance)
{
// Launch AutoCAD
cad.Launch();
// Open drawing
cad.OpenDrawing(#"C:\Scratch\Drawing01.dwg");
// Save it
cad.SaveAs(#"C:\Scratch\Drawing01B.dwg");
}
Then in my AutoCad class (this._acadDocument is an instance of the AcadDocument class.)
public void Launch()
{
this._acadApplication = null;
const string ProgramId = "AutoCAD.Application.18";
try
{
// Connect to a running instance
this._acadApplication = (AcadApplication)Marshal.GetActiveObject(ProgramId);
}
catch (COMException)
{
/* No instance running, launch one */
try
{
this._acadApplication = (AcadApplication)Activator.CreateInstance(
Type.GetTypeFromProgID(ProgramId),
true);
}
catch (COMException exception)
{
// Failed - is AutoCAD installed?
throw new AutoCadNotFoundException(exception);
}
}
/* Listen for the events we need and make the application visible */
this._acadApplication.BeginOpen += this.OnAcadBeginOpen;
this._acadApplication.BeginSave += this.OnAcadBeginSave;
this._acadApplication.EndOpen += this.OnAcadEndOpen;
this._acadApplication.EndSave += this.OnAcadEndSave;
#if DEBUG
this._acadApplication.Visible = true;
#else
this._acadApplication.Visible = false;
#endif
// Get the active document
this._acadDocument = this._acadApplication.ActiveDocument;
}
public void OpenDrawing(string path)
{
// Request AutoCAD to open the document
this._acadApplication.Documents.Open(path, false, null);
// Update our reference to the new document
this._acadDocument = this._acadApplication.ActiveDocument;
}
public void SaveAs(string fullPath)
{
this._acadDocument.SaveAs(fullPath, null, null);
}
From the Autodesk discussion groups, it looks like the second parameter is the type to save as, and may be required:
app = new AcadApplicationClass();
AcadDocument doc = app.ActiveDocument;
doc.SaveAs("d:\Sam.dwg",AcSaveAsType.acR15_dwg,new Autodesk.AutoCAD.DatabaseServices.SecurityParameters());
Since you are in AutoCAD 2010, the type should be incremented to acR17_dwg or acR18_dwg.
Judging by the link to AutoDesk's forum on this topic, it sounds like as you need to close the object after saving...and remove the null's...If I were you, I'd wrap up the code into try/catch blocks to check and make sure there's no exception being thrown!
I have to question the usage of the using clause, as you're Launching another copy aren't you? i.e. within the OpenDrawing and Save functions you are using this._acadApplication or have I misunderstood?
using (AutoCad cad = AutoCad.Instance)
{
try{
// Launch AutoCAD
cad.Launch();
// Open drawing
cad.OpenDrawing(#"C:\Scratch\Drawing01.dwg");
// Save it
cad.SaveAs(#"C:\Scratch\Drawing01B.dwg");
}catch(COMException ex){
// Handle the exception here
}
}
public void Launch()
{
this._acadApplication = null;
const string ProgramId = "AutoCAD.Application.18";
try
{
// Connect to a running instance
this._acadApplication = (AcadApplication)Marshal.GetActiveObject(ProgramId);
}
catch (COMException)
{
/* No instance running, launch one */
try
{
this._acadApplication = (AcadApplication)Activator.CreateInstance(
Type.GetTypeFromProgID(ProgramId),
true);
}
catch (COMException exception)
{
// Failed - is AutoCAD installed?
throw new AutoCadNotFoundException(exception);
}
}
/* Listen for the events we need and make the application visible */
this._acadApplication.BeginOpen += this.OnAcadBeginOpen;
this._acadApplication.BeginSave += this.OnAcadBeginSave;
this._acadApplication.EndOpen += this.OnAcadEndOpen;
this._acadApplication.EndSave += this.OnAcadEndSave;
#if DEBUG
this._acadApplication.Visible = true;
#else
this._acadApplication.Visible = false;
#endif
// Get the active document
// this._acadDocument = this._acadApplication.ActiveDocument;
// Comment ^^^ out? as you're instantiating an ActiveDocument below when opening the drawing?
}
public void OpenDrawing(string path)
{
try{
// Request AutoCAD to open the document
this._acadApplication.Documents.Open(path, false, null);
// Update our reference to the new document
this._acadDocument = this._acadApplication.ActiveDocument;
}catch(COMException ex){
// Handle the exception here
}
}
public void SaveAs(string fullPath)
{
try{
this._acadDocument.SaveAs(fullPath, null, null);
}catch(COMException ex){
// Handle the exception here
}finally{
this._acadDocument.Close();
}
}
Thought I'd include some links for your information.
'Closing Autocad gracefully'.
'Migrating AutoCAD COM to AutoCAD 2010'.
'Saving AutoCAD to another format'
Hope this helps,
Best regards,
Tom.
I've managed to solve this in a non-optimal, very imperfect way so I'd still be interested to hear if anyone knows why the SaveAs method crashes AutoCAD and hangs my application.
Here's how I did it:
When opening a document or creating a new one, turn off the open/save dialog boxes:
this._acadDocument.SetVariable("FILEDIA", 0);
When saving a document, issue the _SAVEAS command passing in "2010" as the format and the filename (fullPath):
string commandString = string.Format(
"(command \"_SAVEAS\" \"{0}\" \"{1}\") ",
"2010",
fullPath.Replace('\\', '/'));
this._acadDocument.SendCommand(commandString);
When exiting AutoCAD turn file dialog prompting back on (probably isn't necessary but just makes sure):
this._acadDocument.SetVariable("FILEDIA", 1);
With C# and COM, when there are optional arguments, you need to use Type.Missing instead of null:
this._acadDocument.SaveAs(fullPath, Type.Missing, Type.Missing);
But since Visual Studio 2010, you can simply omit the optional arguments:
this._acadDocument.SaveAs(fullPath);