packaging c++ app for browser plugin

It’s not that bad, but every little bit helps.

But I wouldn’t recommend enabling true threading for the purpose of diagnosing this problem; probably better to disable threading altogether instead (instead of the SIMPLE_THREADS model that we offer by default). Better to go simpler than more complex.

David

Ok, with no threading I’m getting the crash in the same places (when adding the ambient light, etc), but here:

@pandaNode.I:

INLINE CPT(PandaNode::Up) PandaNode::CData::
get_up() const {
  return _up.get_read_pointer();
}

call stack:

>	libpanda.dll!PandaNode::CData::get_up()  Line 1080 + 0x27 bytes	C++
 	libpanda.dll!PandaNode::get_component(NodePathComponent * parent=0x0067d1b8, PandaNode * child_node=0x03f30040, int pipeline_stage=0, Thread * current_thread=0x00421364)  Line 3566 + 0xf bytes	C++
 	libpanda.dll!PandaNode::attach(NodePathComponent * parent=0x0067d1b8, PandaNode * child_node=0x03f30040, int sort=0, int pipeline_stage=0, Thread * current_thread=0x00421364)  Line 3323 + 0x1a bytes	C++
 	libpanda.dll!NodePath::attach_new_node(PandaNode * node=0x03f30040, int sort=0, Thread * current_thread=0x00421364)  Line 885 + 0x19 bytes	C++

Due to in-lining the second step of the call stack is missing, but it’s this:

int parent_index = child_node->do_find_parent(parent_node, cdata_child);

I printed parent_node and cdata_child->get_up(), both look like valid pointers.

I also get an assertion:

Assertion failed: _void_ptr == (void *)NULL at line 33 of c:\devel\panda\stable\
panda3d\built\include\pointerToVoid.I

This is an assertion at the destructor of PointerToVoid that checks that the pointer was null before destruction. This is the definition of do_find_parent():

INLINE int PandaNode::
do_find_parent(PandaNode *node, const CData *cdata) const {
  CPT(Up) up = cdata->get_up();
  Up::const_iterator ui = up->find(UpConnection(node));
  if (ui == up->end()) {
    return -1;
  }
  return ui - up->begin();
}

Maybe the up pointer is being destroyed prematurely? Due to an inlining bug or w/e?

The problem is not any python compatibility, for the record. Running the dll with rundll32 I get the same problems.

How does the runtime communicate with the client app in the C++ case, anyway? I mean, I figure the panda app needs a window handler of the browser/runtime at least, right? Does this happen magically, or was I supposed to send my app a window handler through the python interface next?

If so, can’t I just run an exe from python and pass the handler as an argument, seeing as all my problems are dll-related? This assuming running an exe isn’t disallowed by any security measures/antivirus ofc…

I’ve made progress with this, there was a festival of problems in my environment, among them, the usual NDEBUG issue (which appeared back there when I created a new project for the dll) and a config.prc file with funny stuff that was causing a crash. At least now I know I was right to disable prc loading in my custom build, the one were I disabled prc loading was working and it was confusing me. Implicit loading of config files is only a source of problems.

Anyway, I now have a working panda environment in a DLL (a small test where I load a model) and I just have a cleanup crash on exit, that I may be able to fix, just wanted to let you know so that you forget about this.

For the record, this is the call-stack of the crash:

>	libpanda.dll!PointerToBase<GraphicsPipe>::~PointerToBase<GraphicsPipe>()  Line 46 + 0x11 bytes	C++
 	libpanda.dll!AsyncTaskChain::do_cleanup()  Line 665	C++
 	libpanda.dll!AsyncTaskManager::cleanup()  Line 79	C++
 	libpanda.dll!AsyncTaskManager::~AsyncTaskManager()  Line 55	C++
 	libpanda.dll!AsyncTaskManager::`vector deleting destructor'()  + 0x41 bytes	C++
 	libpanda.dll!PointerToBase<GraphicsPipe>::~PointerToBase<GraphicsPipe>()  Line 47	C++
 	libpanda.dll!Loader::~Loader()  + 0x51 bytes	C++
 	libpanda.dll!Loader::`vector deleting destructor'()  + 0x3e bytes	C++
 	libpanda.dll!PointerToBase<GraphicsPipe>::~PointerToBase<GraphicsPipe>()  Line 47	C++
 	libpanda.dll!`dynamic atexit destructor for 'Loader::_global_ptr''()  + 0x32 bytes	C++
 	libpanda.dll!_CRT_INIT(void * hDllHandle=0x00a93174, unsigned long dwReason=11086200, void * lpreserved=0x00a92978)  Line 449	C
 	libpanda.dll!__DllMainCRTStartup(void * hDllHandle=0x021c0000, unsigned long dwReason=0, void * lpreserved=0x00000000)  Line 560 + 0x8 bytes	C
 	libpanda.dll!_DllMainCRTStartup(void * hDllHandle=0x021c0000, unsigned long dwReason=0, void * lpreserved=0x00000001)  Line 510 + 0xe bytes	C

violation in pointerToBase.I here:

template<class T>
INLINE PointerToBase<T>::
~PointerToBase() {
  reassign((To *)NULL);
}

Looks like a double deallocation of the active tasks vector. Do you happen to know any reason why it could be happening only in dll form and not in exe?

Hmm. Are you setting a custom AsyncTaskManager on the Loader, for any reason? (I doubt it, but I thought it worth asking.)

Maybe there’s an ordering issue in static destructor calling? Seems unlikely too. Should be handled properly by reference counts.

No, no ideas up front.

David

No, it’s just this atm:

#include "pandaFramework.h" 
#include "pandaSystem.h" 
#include "load_prc_file.h"

PandaFramework *framework; 
WindowFramework *window;

#ifdef _USRDLL
extern "C" __declspec(dllexport) void method(){ 
  int argc = 0; 
  char**argv = 0; 
#else
#pragma comment(linker, "/SUBSYSTEM:console /ENTRY:mainCRTStartup")
void main(int argc, char *argv[]) {
#endif

  load_prc_file_data("", "load-display pandagl");

  framework = new PandaFramework();
  framework->open_framework(argc, argv); 
  framework->set_window_title("a window"); 
  window = framework->open_window(); 

  NodePath environ = window->load_model(framework->get_models(), "models/smiley");
  environ.reparent_to(window->get_render());

  window->setup_trackball();

  framework->main_loop(); 
  window = NULL;
  //framework->close_framework(); 
  //delete framework;
}

I’ve tried with framework and framework* in case it mattered but no difference.

Turns out this last crash only happens with rundll. But the old crashes (when loading a model for example) keep happening with ctypes. So it’s something else and I’m back where I started.

Ok, it seems it finally works. The key was simply building with true threads (neither simple-threads or no-threads would do it). Maybe you need them with ctypes for a similar reason why you need them for boost.threads? It still crashes on exit with rundll32.exe, though.

Anyway, finally got an app with models that is loadable from ctypes, what now? Because I don’t see how my panda app is gonna get the hWnd it needs to attach to the plugin process (disclaimer: I don’t understand the p3d architecture). I’m assuming this step is just missing from the documentation, so how do you make it work, is this unexplored terrain and should I tinker, or am I missing something?

Hmm, the runtime should load the appropriate value into the config file automatically, so that WindowProperties.getDefault() contains the appropriate parent_window definition, and when you open a default window it should automatically go to the right place. Does this not work?

David

I didn’t even try since I couldn’t see how it would work, and I considered that maybe it was done that way, but I disable prc loading. I will just enable it and try.

OT: However, this approach seems a little limited, this means that when the size of the plugin size changes the app stays the same size, right? In most browsers when you zoom it and out on a page, plug-ins get scaled accordingly. But I guess that adding support for this would be easy so no biggie.

Another question. Since I need real threads (apparently) I need to build my own rtdist, but what about the runtime? Does HAVE_THREADS make a different for it?

Full support for resizing windows through the browser will require integration with the appropriate Python-based callbacks. (Python is the mediator between your application and commands sent from the browser.)

Glancing at the Python code now, though, I see that it currently attempts to apply changes in the window size directly to base.win, which will fail in a C++ application that has its own window instead of storing it in base.win. Of course, one option is for you to write your C++ application in such a way that it stores a copy of its window pointer in Python-accessible base.win.

There might be other stupid Python-only assumptions that the AppRunner code makes. It hasn’t yet been tested for C++ development, obviously. :confused:

No, I don’t think so. Still, I don’t really understand why you need real threads in order to avoid crashing. Same with prc files. Seems like something is still wrong. You should be able to use the standard rtdist, and you should be able to read prc files at startup.

Perhaps it’s just a matter of linking with the wrong runtime DLL? I think we always link with the multithreaded runtime DLL’s, even in the Panda builds that don’t have threading enabled.

David

Yeah, I’m using the exact same configuration than Panda, multi-threaded DLL, I’m also setting VC to ignore the same default libraries as Panda.

Have you tried to do this, perchance? Just try to load a model and show it, in C++, and build that in a dll and use it from ctypes or with the PyObject method, with the 1.7.0 shipped binaries, for example. If this works for you then I have something wrong. I have win7-64, btw.

I haven’t had a chance to try this yet, but I promise to try it soon. :slight_smile:

David

I have tried this, and I find that I am able to successfully invoke a C++ DLL that invokes Framework to load and display a model, exactly as you describe, from Python. Using ctypes works perfectly.

This is running with the same build of Panda that I produced locally: this is the Panda DLL’s that Python loads, and it was also used to produce my DLL at the same time, ensuring that my compilation settings were exactly the same in both cases. I used the ppremake build system, not makepanda.

That said, I find the following difficulties in embedding my DLL in a p3d file:

(1) Our development build of Panda3D is built using OPTIMIZE 3, which is to say, a “Release” build with NDEBUG not defined.

(2) The production build of Panda3D that is used to power the runtime, however, is built using OPTIMIZE 4: a “Release” build with NDEBUG defined. This makes it fundamentally incompatible with (1), since the presence of NDEBUG shortens a few low-level structures (removing some debug-only constructs).

Thus, in order to produce a DLL that is compatible with a p3d file, I will need to build it with an appropriate dtool_config.h that matches (2). But then I won’t be able to run this DLL, unless I also obtain a libpanda.dll etc. that matches (2).

I don’t think this is related to your problems–it doesn’t seem like you got to this point yet. If I’m not misunderstanding, you were having difficulty getting (1) to work–you were unable to produce a DLL that was compatible with the development build of Panda3D and which could be run from Python interactively.

Overcoming this (1)/(2) build dichotomy will be a bother, but it is not insurmountable. It means a developer will need to compile in mode (1) during development, then rebuild in mode (2) for packaging into a p3d file. I don’t think this is unreasonable, especially if we make it easy to switch between the two modes.

There is another problem I ran into:

(3) A DLL embedded into a p3d file is not automatically extracted to disk when running. Thus, the application DLL cannot be invoked when packaged this way. This is clearly just a bug in the Panda3D runtime system. (For the record, it does unpack a DLL from a .mf package, just not from a p3d file.)

I can easily fix (3) for the future.

As to (1), maybe the next thing I need to do is see if I can build a DLL that is compatible with the development libpanda.dll that we distribute.

David

Addendum: when I attempt to compile the same DLL against the Panda3D distributed here, and then invoke it from Python, I do indeed get exactly the crash you describe. Fascinating.

The next step, I guess, will be to build Panda from source, using makepanda, and see where that gets me.

David

Ah, thanks a lot. Just to make it clear, you are right in your understanding of what I can and cannot do. Indeed, just running the dll like (1) is success I haven’t achieved (last time I was getting farther but it still crashed on exit).

So, since you said you wanted to try makepanda, I take it that the fundamental thing you are doing differently is using ppremake instead of makepanda, I’ll try ppremake soon and see if I have more luck, though I’m interested in seeing if you experience the same with makepanda.

Guys, I hope you are going to actually solve this. I will have to try web publishing with bullet and thus wanted to build with C++ instead of using Python. Any new intel on the topic?

I do plan to revisit this in the near future, but for now I’ve unfortunately been distracted by other things, and haven’t had a lot of time to devote to Panda development. My apologies.

David

What I’m doing now is using Firebreath with Panda3D:

code.google.com/p/firebreath/

You can create a firebreath wrapper that calls your game passing it the window handler, then set the window handler in WindowProperties in your game, then you get a working plugin. It is of course a little more of work but it has the advantage over the P3D platform that python isn’t required so the download is smaller for the user.

Firebreath doesn’t support Linux yet though, but I don’t care about that platform, and Mac support is experimental. I do need mac support but the project seems very active, the devs are sponsored.

For now I’ve only done tests with Firebreath though, I’ve yet to write the code that autodownloads the game and unpackages it, so if you want to work on a generic firebreath wrapper for Panda I can help.

Maybe we could even add some kind of Firebreath support to cvs if David agrees. I don’t see the viability of the P3D platform for C++ games, having to download python to run a C++ game is not good at all, specially when you are competing with 300KB flash games and the attention span of the users is really short.

EDIT: Well, It’s not so much Python as all the things that are installed, when you first load a website with a P3D plugin, explorer freezes for 30 seconds, then it starts a 60 seconds download (and I have 20mbit at home) where 100 MB! of runtime are downloaded.

My C++ game fits in 10MB (including Panda) and the firebreath plugin wrapper weights 500kb. Using the P3D platform really adds a lot of unnecessary MB (and it doesn’t freeze the browser, why does that happen?). We need something lighter for casual games. Just mentioning this cause I think it’s an important point, in case you consider a redesign in a distant future.