Creating a form more than once causes a crash?

PikminDoctor

13-02-2008 14:49:02

Hi, I've been embedding a MOGRE scene in a .NET windows form, as shown in tutorial 6. If I launch a form with MOGRE embedded twice in the same program, this line of code:

ResourceGroupManager.Singleton.AddResourceLocation(archName, typeName, secName);

Will crash the program with an AccessViolationException. Here is an example:

Program.cs:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Mogre;

namespace Tutorial06
{
static class Program
{
[STAThread]

static void Main()
{
BeginForm1();
BeginForm2();
}
static void BeginForm1()
{
OgreForm form = new OgreForm();
form.Init();
form.Go();
}

static void BeginForm2()
{
OgreForm form = new OgreForm();
form.Init();
form.Go();
}
}
}


OgreForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Mogre;

namespace Tutorial06
{
public partial class OgreForm : Form
{
Root mRoot;
RenderWindow mWindow;

public OgreForm()
{
InitializeComponent();

this.Size = new Size(800, 600);
Disposed += new EventHandler(OgreForm_Disposed);
Resize += new EventHandler(OgreForm_Resize);
}

void OgreForm_Resize(object sender, EventArgs e)
{
mWindow.WindowMovedOrResized();
}

void OgreForm_Disposed(object sender, EventArgs e)
{
mRoot.Dispose();
mRoot = null;
}

public void Go()
{
Show();
while (mRoot != null && mRoot.RenderOneFrame())
Application.DoEvents();
}

public void Init()
{
// Create root object
mRoot = new Root();

// Define Resources
ConfigFile cf = new ConfigFile();
cf.Load("resources.cfg", "\t:=", true);
ConfigFile.SectionIterator seci = cf.GetSectionIterator();
String secName, typeName, archName;

while (seci.MoveNext())
{
secName = seci.CurrentKey;
ConfigFile.SettingsMultiMap settings = seci.Current;
foreach (KeyValuePair<string, string> pair in settings)
{
typeName = pair.Key;
archName = pair.Value;
ResourceGroupManager.Singleton.AddResourceLocation(archName, typeName, secName);
}
}

// Setup RenderSystem
RenderSystem rs = mRoot.GetRenderSystemByName("Direct3D9 Rendering Subsystem");
mRoot.RenderSystem = rs;
rs.SetConfigOption("Full Screen", "No");
rs.SetConfigOption("Video Mode", "800 x 600 @ 32-bit colour");

// Create Render Window
mRoot.Initialise(false, "Main Ogre Window");
NameValuePairList misc = new NameValuePairList();
misc["externalWindowHandle"] = Handle.ToString();
mWindow = mRoot.CreateRenderWindow("Main RenderWindow", 800, 600, false, misc);

// Init resources
TextureManager.Singleton.DefaultNumMipmaps = 5;
ResourceGroupManager.Singleton.InitialiseAllResourceGroups();

// Create a Simple Scene
SceneManager mgr = mRoot.CreateSceneManager(SceneType.ST_GENERIC);
Camera cam = mgr.CreateCamera("Camera");
cam.AutoAspectRatio = true;
mWindow.AddViewport(cam);

Entity ent = mgr.CreateEntity("ninja", "ninja.mesh");
mgr.RootSceneNode.CreateChildSceneNode().AttachObject(ent);

cam.Position = new Vector3(0, 200, -400);
cam.LookAt(ent.BoundingBox.Center);
}
}
}

raygeee

22-02-2008 08:52:32

First of all, what does the exception say? Without it, it's hard to tell the source of the problem.

First thing is, you are creating two Mogre.Root objects which is generally not a good approach since it's supposed that there is only one root, although Mogre.Root is not a singleton (correct me if I'm wrong).
The second thing might be your problem: The ResourceGroupManager uses the singleton pattern which defines that there can only be one instance of the ResourceGroupManager class (see http://en.wikipedia.org/wiki/Singleton_pattern). In your project you are trying to add a resource location to the same ResourceGroupManager twice. I guess this results in an exception saying the resource location has already been added.

PikminDoctor

02-03-2008 16:12:47

I know what I've done and kinda understand why it doesn't work.
This isn't actually my project, it's just a slight modification of the forms tutorial to show up the issue.

I guess because, unlike you, I don't understand a lot about what Ogre does in the background I don't know the reason why some things cause memory access violation exceptions, but I'm surprised that after leaving the BeginForm1() function the memory used wasn't freed up.

The only way I got my program to run Ogre again was to actually make the entire program restart, which seems like a really hackey solution. Does anyone else have a more elegant way of solving this problem?

Bontakun

05-03-2008 08:05:09

Dont make two roots. Take that part out of OgreForm, and pass it in to each.

PikminDoctor

07-03-2008 18:04:20

Unsurprisingly, I still get the same problem when passing the same root to both in this way.

static void Main()
{
Root mRoot = null;
for (int i = 0; i < 2; i++)
{
OgreForm form = new OgreForm(mRoot);
form.Init();
form.Go();
}
}


This may not be what you had in mind, what exactly do you mean?
(note it is nessesary for me to load different video settings and scenes etc. so the engine needs to be reloaded)

Bontakun

09-03-2008 09:36:11

Yes that it what I meant. Also everything before 'create render window' plus 'init all resource groups' only do once. For each form you will need a render window, a scenemanager... etc.

PikminDoctor

21-03-2008 14:32:45

You can't create a render window more than once, it seems. I followed your instructions and it doesn't seem to work for me. All I get is another invalid memory exception.

Thanks for trying to help, but all I want to do is completely wipe the memory that Ogre takes up, so that when I dispose of it it's as if it never run before. Basically achive the same effect of closing the program down and starting it up again without actually doing that.

What does Windows do when the program shuts down to stop this happening when the program is started up again, because thats what I need.

Clay

21-03-2008 20:07:56

Ogre wasn't designed with this scenario in mind, but what you are doing here should have worked. I took a look at this myself, and it looks like a bug in Ogre.

I modified an Mogre program I've been working on to do this:

static void Main()
{
RunOnce();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
RunOnce();
}


Where RunOnce is a function which looks like the Main functions in the tutorials (creates an OgreWindow, etc). The Collect/WaitForPendingFinalizers/Collect will fully cleanup the Ogre run. Then if we look at the state of the program, you can see that indeed, the ResourceGroupManager (and the _native instance it points to) are different. The AV itself takes place in Ogre itself, with CLRStack showing this:

0:000> !clrstack
OS Thread Id: 0x1744 (0)
ESP EIP
002cf170 04857e14 [NDirectMethodFrameStandalone: 002cf170] <Module>.Ogre.ResourceGroupManager.addResourceLocation(Ogre.ResourceGroupManager*, std.basic_string<char,std::char_traits<char>,std::allocator<char> >*, std.basic_string<char,std::char_traits<char>,std::allocator<char> >*, std.basic_string<char,std::char_traits<char>,std::allocator<char> >*, Boolean)
002cf18c 055512b3 Mogre.ResourceGroupManager.AddResourceLocation(System.String, System.String, System.String)
002cf21c 05550aab MogrePython.OgreWindow.InitResources()
002cf254 05550228 MogrePython.OgreWindow.InitializeOgre()
002cf280 05550153 MogrePython.OgreWindow.Go()
002cf288 006e8355 MogrePython.Program.RunOnce()
002cf2b4 006e0095 MogrePython.Program.Main()
002cf4d4 79e7c74b [GCFrame: 002cf4d4]


Looking at the native callstack we see that the AV is in Ogre itself. Note the function name is wrong, I do not have symbols loaded for release mode in Ogre:

0:000> kn3
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 002cf158 055512b3 OgreMain!Ogre::StringConverter::parseAngle+0x1ff4
01 002cf238 7b069289 0x55512b3
02 002cf2b0 79e7c74b System_Windows_Forms_ni+0x99289

What this tells us is that we are only one frame deep in OgreMain. So, this isn't likely to be an Mogre issue. I can see the problem though. It looks like Ogre!ResourceGroupManager::AddResourceLocation has called a function which returned an invalid object. AddResourceLocation then tries to dereference a field of the object and this hits an AV (remember rax is the return value in x86 calling conventions):

0:000> r
eax=baadf00d ebx=002cf1c4 ecx=00f48e5c edx=002cf18c esi=00f48e5c edi=00f48e30
eip=04857e14 esp=002cf014 ebp=002cf158 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
OgreMain!Ogre::StringConverter::parseAngle+0x1ff4:
04857e14 8b7004 mov esi,dword ptr [eax+4] ds:0023:baadf011=????????

This really does look like a bug in Ogre, but I can't find the problem here without getting the Mogre/Ogre sources and symbols for the Release version. You should do one of the following:

  1. Repro the bug under C++ Ogre and report it to the Ogre developers.[/*:m]
  2. If you have the full Visual Studio, you could use the Debug version of Mogre (I can't because I'm using C# express and don't have the Debug CRT) and find the problem using mixed-mode debugging.[/*:m]
  3. You could ask the maintainer of Mogre very nicely to take a look at it, because he probably has the knowhow and setup to do this, and it's a very simple repro.[/*:m][/list:o]

PikminDoctor

26-03-2008 12:21:02

thanks for your help!

I had a suspicion there was something wierd going on here, but I didn't know enough about how things work in the background and the CLRstack thing you were talking about (where can I learn about that stuff btw?)

I'll reproduce the bug in C++ but I only have the express version of Visual C# as well.

Clay

26-03-2008 15:52:38

!CLRStack is a command from a debugger extension called SOS, which can be used in WinDbg (a free debugger). It's not easy to learn this stuff. Most of it you either have to learn for yourself using the !help function in SOS or you have to pick it up from reading blogs of people "in the know". Here are the ones that will teach you most of the things you need to know:
http://blogs.msdn.com/johan/archive/200 ... -dump.aspx
http://blogs.msdn.com/johan/archive/200 ... art-i.aspx
http://blogs.msdn.com/johan/archive/200 ... rt-ii.aspx
http://blogs.msdn.com/johan/archive/200 ... mands.aspx

You can find lots of information on CLR debugging on these two blogs:
http://blogs.msdn.com/tess/default.aspx
http://blogs.msdn.com/johan/default.aspx

Usually you are better off just using Visual Studio (if you own it) in Mixed Mode Debugging though. This will allow you to debug both managed code (Mogre) and native code (Ogre) at the same time. If you need this functionality and don't own Visual Studio, you have to use WinDbg+SOS.

Though me personally, I prefer using SOS anyway. Maybe that's because I work for Microsoft (on the CLR itself) and because I own the SOS tool. =)

Zool

27-03-2008 10:03:00

Though me personally, I prefer using SOS anyway. Maybe that's because I work for Microsoft (on the CLR itself) and because I own the SOS tool. =)
Clay, this is freaking awesome! Can you share some details on what exactly you are working on (is it mainly the tool you mentioned) ?

Do you know whether there will be a new CLR version in the near future, or we'll be using 2.0 for a long time ?

Clay

28-03-2008 06:35:09

I work on a team primarily devoted to fixing stress bugs in the CLR. Stress bugs include things like deadlocks, races, and mostly threading issues. We are also responsible for handling support requests. Any time someone within Microsoft thinks they have run into a bug in CLR, we get the first crack at it. This is why we are called the Quick Response Team. We're sort of like the debugging ninjas of CLR. :)

I do own SOS, but that's only a small part of what I do. That tool is fairly stable, so most of the work on it is fixing regressions or minor feature improvements. For the most part, we work on a little bit of everything. We fix bugs in CLR and in Silverlight. We work on almost every portion of the code base except for the JIT.

I can't say much about the future release dates of products. Though to be honest, I haven't been keeping up with them. It'll be a while before you are using something other than 2.0, but keep in mind that we just shipped a service pack for v2.0 which included a huge number of improvements and fixes, but CLR is very "behind the scenes". Aside from performance, there's nothing I can point at and say "hey! look at this!" Most of that goes to the C# team or the DLR team who take advantage of the features we implement. They get all the credit. =)

Though I must say, the new C# is quite sexy. LINQ, Extension Methods, Type Inference, and Lambda Expressions all freaking rock. I'm a long time C++ coder (and I do 99.8% of my work on CLR in C++), but I love these new C# features. It looks like they are going to add dynamic typing to the language for C# 4 that may look something like this (from here):
static void Main(string[] args)
{
dynamic
{
object myDynamicObject = GetDynamicObject();
myDynamicObject.SomeMethod(); // call a method
myDynamicObject.someString = "value"; // Set a field
myDynamicObject[0] = 25; // Access an indexer
}
}

Where all of the methods, variables, and such are accessed dynamically at runtime. Cool stuff.

PikminDoctor

28-03-2008 12:41:36

Well, it seems we were both wrong... (unless I reproduced the bug in C++ wrong)

http://www.ogre3d.org/phpBB2/viewtopic.php?t=40155

I guess if this is the way Ogre is supposed to work, I'm gonna have to find some sort of work around - though I already have the technique of restarting the entire program again when it finishes. The only other thing I can think of is to make the Ogre part a seperate program that the other part runs through Windows.

Clay

28-03-2008 17:01:24

Your repro isn't quite correct. What about this instead?

include "Test.h"

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

//running ogre first time...

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
try
{
// Create application object
TestApp app1;
app1.go();
}
{
//That one has run and closed now. Let's run Ogre again...
TestApp app2;
app2.go();
}

catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() << std::endl;
#endif
}

return 0;
}

#ifdef __cplusplus
}
#endif

Sinbad is right, you did create two root objects. You need to tear them down with the destructor of TestApplication (which I assume deletes Root).

PikminDoctor

28-03-2008 18:25:37

That wouldn't be able to compile, look at the try statement.

You were right though, my repro was wrong after all. I striped back all the nonsence and reproduced the problem so that it's just like the mogre example earlier, and it works with no memory problems (or so it seems on my Visual C++ compiller) So the bug must be in mogre instead...

#include "Test.h"

void RunOgre()
{
try
{
// Create application object
TestApp app1;
app1.go();
}
catch( Ogre::Exception& e )
{
MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
}
}


#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
{
RunOgre();
RunOgre();
return 0;
}