Trolltech Home | Qt-jambi-interest Home | Recent Threads | All Threads | Author | Date
All threads index page 1

Qt-jambi-interest Archive, February 2008
PDF Viewing Support (poppler-qt4)


Message 1 in thread

I'm looking to be able to view PDFs in a Jambi app that I'm working on. So I thought I'd
try porting poppler (http://poppler.freedesktop.org/) to Jambi!

First I tried playing with the Jambi Generator. I ran in to a bit of trouble there (mostly
the complexity/lack of documentation, and the fact that all of the bits I needed to use
were in the Poppler namespace, and the generator docs claim it doesn't handle namespaces
except for enums).

So I went the hand-coded JNI route. Everything seems to be working up to the point where I
have a native C++ QImage, and I want to stuff that into a Jambi (Java) QImage, and I have
no idea where to go from here.

Ideas? Thanks,

-Adam Batkin


Message 2 in thread

Adam Batkin wrote:
> I'm looking to be able to view PDFs in a Jambi app that I'm working
> on. So I thought I'd try porting poppler
> (http://poppler.freedesktop.org/) to Jambi!


Hi Adam,

Cool stuff!

> First I tried playing with the Jambi Generator. I ran in to a bit of 
> trouble there (mostly the complexity/lack of documentation, and the
> fact that all of the bits I needed to use were in the Poppler
> namespace, and the generator docs claim it doesn't handle namespaces 
> except for enums).

The Qt 4.4 Jambi version of the will have extended support for 
namespaces. We've implemented quite a bit there in order to properly 
support the Phonon API's so that part should not be an issue in the future.

As for the complexity of the generator... Did you start with the 
generator example and try to go incrementally from there? Mapping an API 
is not a trivial task, but if there are concrete missing parts of the 
docs we would like to hear a bit more about them.

For instance, if the resulting API is just one class that displays a pdf 
file, you could write a non-namespace wrapper class which subclasses 
QWidget and has a "setPDFFile(const QString &file)" function and map 
this using the generator.

> So I went the hand-coded JNI route. Everything seems to be working up
> to the point where I have a native C++ QImage, and I want to stuff
> that into a Jambi (Java) QImage, and I have no idea where to go from
> here.

If you're not using the generator you can still "steal" some of the 
logic in it ;-). We have quite a bit of utility functionality that is 
used from the generated code in qtjambi_core.h and friends. If you have 
a look at the generated code of for instance QPixmap::toImage() you see 
that converting a QImage into a proper jobject of class 
com.trolltech.qt.gui.QImage is done something like this:

     jobject jimg =
         qtjambi_from_object(env,
                             &image,
                             "QImage",
                             "com/trolltech/qt/gui/",
                             true);

I hope this helps a little bit.

best regards,
Gunnar


Message 3 in thread

> The Qt 4.4 Jambi version of the will have extended support for 
> namespaces. We've implemented quite a bit there in order to properly 
> support the Phonon API's so that part should not be an issue in the future.

That's good to hear.

> As for the complexity of the generator... Did you start with the 
> generator example and try to go incrementally from there? Mapping an API 
> is not a trivial task, but if there are concrete missing parts of the 
> docs we would like to hear a bit more about them.

Actually, maybe the generator is simpler than I thought, but the 
provided example is a bit complex for "beginners". Many things became 
suddenly clear once I created a few simple classes for myself, just to 
play around with, and ran them through the generator. That was my key to 
enlightenment.

Here's the thing: If you look at the regular Qt and Qt Jambi examples, 
starting with the tutorials, you become spoiled. The examples and 
tutorials are well explained, well documented, and most importantly, 
they generally highlight only a single bit of functionality. If you want 
to verify that everything is installed properly in your development 
environment, paste in the code from Example 1 (it's only 6 lines). If 
you forgot how to deal with the Item View framework, look at the Item 
View examples. Etc...

So what would be nice is concrete examples of how to deal with specific 
issues when using the generator or interfacing with native libraries. In 
particular (but also as an example of what I'm looking for (a little 
voice in my head that I haven't heard since I was in school warns me not 
to let you actually do all of my work for me))...Something like "here's 
an example program with 2 classes, Parent and Child, and you obtain a 
Child by calling parent.getChild() but the parent can't be GC's until 
all of the children also have no references remaining."

> If you're not using the generator you can still "steal" some of the 
> logic in it ;-). We have quite a bit of utility functionality that is 
> used from the generated code in qtjambi_core.h and friends. If you have 

Maybe this is going against the goals of Qt Jambi, but it might be nice 
to have pretty documentation for the C++ JNI functions (all those 
qtjambi_* functions) like there is for the Qt C++ docs, and Jambi javadocs.

I must admit, Jambi overall has a very polished feel for something that 
is so new (in comparison to Qt which we all know/expect to be well 
polished since it's been around forever). I'm guessing that you just 
haven't yet figured out all of the ways that we (developers at large) 
might want to use the generator or other low-level JNI bits, what 
problems commonly arise and what questions are frequently asked. So, 
I'll (also) continue with my original thread of questions and maybe that 
will help highlight areas where developers may often become stumped. Or 
you'll tell me that I'm better off learning Visual Basic ;)

Phew! Too many words! Hope that helps anyway,

-Adam Batkin


Message 4 in thread

Adam Batkin wrote:
> So what would be nice is concrete examples of how to deal with specific 
> issues when using the generator or interfacing with native libraries.

Point taken. I'll see how we can improve on this in the future. We 
already bogged down by tasks for the 4.4 release, but it should be 
possible to get some more examples in future releases.

> Maybe this is going against the goals of Qt Jambi, but it might be nice 
> to have pretty documentation for the C++ JNI functions (all those 
> qtjambi_* functions) like there is for the Qt C++ docs, and Jambi javadocs.

These functions were originally supposed to be used only by the 
generator primarly and the generator doesn't need to read docs ;-)

I'll add a task to document them too.

 > you'll tell me that I'm better off learning Visual Basic ;)
> 
> Phew! Too many words! Hope that helps anyway,

It does indeed help, but I'll never promote visual basic!

best regards,
Gunnar


Message 5 in thread

> For instance, if the resulting API is just one class that displays a pdf 
> file, you could write a non-namespace wrapper class which subclasses 
> QWidget and has a "setPDFFile(const QString &file)" function and map 
> this using the generator.

I did something similar, just wrapping the Document and Page classes 
(with my own PdfDocument and PdfPage). The problem I now have is with 
object ownership and garbage collection:

Pages get constructed on-the-fly by (for example) calling 
Document->page(n). You can then do various useful things, in my case 
just page->renderToImage() is enough, and out pops a nice QImage. But: 
my PdfDocument could get garbage collected (and the underlying Document 
deleted) while there are still outstanding PdfPage (and Page) objects 
lying about.

I'm thinking that I'll solve this by replacing my bare pointer to the 
Document (in my PdfDocument class) with a reference counted proxy, then 
make sure that each PdfPage also keeps a copy of its underlying 
Document. Does this sound reasonable? Are there other common patterns to 
solving patterns like this? I know that in the C++ world this type of 
solution is common, but I was kind-of hoping that the generator would 
have some magic to make that all go away.

What about keeping a Java reference in the Java PdfPage class to the 
Java PdfDocument class instead? Then the VM will know that their two 
lifetimes are linked. But will the VM/Jambi's infrastructure let them 
get GC'd when the PdfDocument and all of its PdfPages go out of scope (I 
know that normally the JVM will detect cycles like that (which is why 
it's called Garbage Collection instead of Reference Counting) but I 
don't know all of the Magic that Jambi might be performing behind the 
scenes that might prevent that)?

>> So I went the hand-coded JNI route. Everything seems to be working up
>> to the point where I have a native C++ QImage, and I want to stuff
>> that into a Jambi (Java) QImage, and I have no idea where to go from
>> here.
> 
> If you're not using the generator you can still "steal" some of the 
> logic in it ;-). We have quite a bit of utility functionality that is 
> used from the generated code in qtjambi_core.h and friends. If you have 
> a look at the generated code of for instance QPixmap::toImage() you see 
> that converting a QImage into a proper jobject of class 
> com.trolltech.qt.gui.QImage is done something like this:
> 
>     jobject jimg =
>         qtjambi_from_object(env,
>                             &image,
>                             "QImage",
>                             "com/trolltech/qt/gui/",
>                             true);
> 
> I hope this helps a little bit.

Yeah, that last bit helps a lot. But now that you have me looking back 
at the generator, using qtjambi_from_object and friends directly seems 
too...easy.

In the end I may go back to hand coding (i.e. hand-written JNI) since it 
looks like I may end up having to do a lot of housekeeping myself to 
ensure that bad things don't happen (like my above concerns about 
premature GCing). Plus JNI code isn't too bad once you get the hang of 
it. If nothing else, the generator will be a good way of 
reverse-engineering different ways of doing things.

Thanks,

-Adam Batkin



Message 6 in thread

Adam Batkin wrote:

> Pages get constructed on-the-fly by (for example) calling 
> Document->page(n). You can then do various useful things, in my case 
> just page->renderToImage() is enough, and out pops a nice QImage. But: 
> my PdfDocument could get garbage collected (and the underlying Document 
> deleted) while there are still outstanding PdfPage (and Page) objects 
> lying about.
> 
> I'm thinking that I'll solve this by replacing my bare pointer to the 
> Document (in my PdfDocument class) with a reference counted proxy, then 
> make sure that each PdfPage also keeps a copy of its underlying 
> Document. Does this sound reasonable? Are there other common patterns to 
> solving patterns like this? I know that in the C++ world this type of 
> solution is common, but I was kind-of hoping that the generator would 
> have some magic to make that all go away.

We cover this exact case because it doesn't appear in Qt. It does, but 
in that case the objects are QObjects and then they are covered by other 
rules ;-)

We have the <reference-count> attribute which is documented here:
http://doc.trolltech.com/qtjambi-4.3.3_01/com/trolltech/qt/qtjambi-typesystem.html#reference-count
which can do a lot with ownership handling. For instance when you have

   lineEdit.setCompleter(completer)

we have a reference-count node where the lineEdit java object gets a 
private completer member and thus keeps a java reference to it. When the 
lineedit is collected this reference goes away and the completer can be 
collected on the next pass. Of course doing:

   lineEdit.setCompleter(null)

will in turn reset the private reference to null allowing the original 
completer to be deleted.


I would suggest that you do a similar approach with your page object, 
namely let the java Page object keep hold of a private member to the 
document assuring you that it will be alive. The way we normally do this 
is by "hiding the original page() function" and replacing it with one 
that does the right thing. In pseudo code:

// Insert a Document member into the page object.
<object-type name="Page">
   <inject-code class="java">
     Document theDocument;
   </inject-code>
</object-type>

// hide the original Document.page() function and replace it
// with one that sets the "theDocument" ref on the returned page
<object-type name="Document">
   <modify-function signature="page()const"
                    access="private"
                    rename="page_helper" />
   <inject-code class="java">
     public Page page() {
         Page page = page_helper();
         page.theDocument = this;
         return page;
     }
   </inject-code>
</object-type>

So, when the page() function gets called it you'll call you own version 
of it which sets up the reference. As long as the Page object is alive 
there will be a reference to the Document object and it cannot be 
collected. I hope this helps you a little bit.

best regards,
Gunnar


Message 7 in thread

I've had a lot of trouble getting everything just right. I put some debugging code in my 
PdfDocument and PdfPage destructors to notify me when they are called, so I can tell when 
the C++ objects are actually destructed.

The interesting bit is the typesystem file for the PdfDocument class:

         <object-type name="PdfDocument">
                 <modify-function signature="page(int)const" access="private" 
rename="page_helper">
                         <modify-argument index="return">
                                 <reference-count action="ignore"/>
                                 <define-ownership class="java" owner="java"/>
                         </modify-argument>
                 </modify-function>
                 <inject-code class="java">
                         public PdfPage page(int num) {
                                 PdfPage page = page_helper(num);
                                 page.theDocument = this;
                                 return page;
                         }
                 </inject-code>
         </object-type>

Does that look reasonable? It seems to work. That's basically letting Java keep ownership 
of everything, which should work since the PdfPage class now has a Java reference back to 
its PdfDocument.

I did some testing with a giant List of every PdfPage ever obtained, and made sure that no 
PdfDocuments were ever garbage collected (without the <modify-argument bits the 
PdfDocuments would often be GCed too early).

Thanks,

-Adam Batkin


Message 8 in thread

Adam Batkin wrote:
> The interesting bit is the typesystem file for the PdfDocument class:
>
>         <object-type name="PdfDocument">
>                 <modify-function signature="page(int)const" 
> access="private" rename="page_helper">
>                         <modify-argument index="return">
>                                 <reference-count action="ignore"/>
>                                 <define-ownership class="java" 
> owner="java"/>

Is the PdfPage a QObject? If not, then you should be careful doing this. 
The reason is that if Java has the ownership of a C++ object, the C++ 
object will be destroyed along with the Java object when the GC sees 
fit. This of course means that you should make sure you have no dangling 
references to the C++ object on the C++ side.

The other caveat is that if the class is not a QObject, then special 
rules apply. Since we do not control the C++ object's destructor, we 
have no control over when C++ object is destroyed. Therefore we cannot 
reuse the Java objects we automatically create to map the C++ objects.

I.e. here's the danger I see:

1. User calls Java version of page()
2. This in turn invokes the generated JNI-based code to convert the 
PdfPage pointer to a Java object.
3. Conversion code creates a Java object bound to the C++ object with 
ownership of the C++ object. (the conversion code *always* creates a new 
Java object, since there is no way of detecting whether the PdfPage 
pointer has been mapped before.)
4. User calls page() again, with the same index as before.
5. C++ returns the same PdfPage pointer because we requested the same page.
6. Conversion code creates a second Java object bound to the same C++ 
object, and this also has ownership of the C++ object.
7. One of the two Java objects goes out of scope and GC kills it.
8. The Java object owns the C++ object, so the finalizer destroys the 
C++ object
9. The second Java object is still alive and tries to access the 
now-invalid data.

If this is the case, you should be able to crash pretty easily by 
calling e.g. page(0) a bunch of times, only retaining the last 
reference, and then trying to call a method on this.

If PdfPage is a subclass of QObject, then other rules apply, because we 
can track the destruction of QObjects, so you should only get a single 
Java object per C++ object, and the code above should work, I think. The 
default ownership in that case is that C++ owns both its own object and 
the Java object and is not garbage collected.

Again, this should be easy to test. You should also be able to check 
that you are in fact getting the same Java object with each call to 
page() for a specific index by checking that the  identity of the two 
objects in Java are equal (if (object1 == object2) ...)

>
> I did some testing with a giant List of every PdfPage ever obtained, 
> and made sure that no PdfDocuments were ever garbage collected 
> (without the <modify-argument bits the PdfDocuments would often be 
> GCed too early).

I'm not sure why this would happen, though. The default behavior is that 
the return value of page() has what we call "split ownership" which 
means that Java is allowed to garbage collect the Java object, but C++ 
owns the C++ object so this will live until it's deleted.

If they are QObjects, then the Java objects of the PdfPage objects 
should not be garbage collected until the corresponding C++ object is 
deleted. The PdfDocument object, granted it's created in Java, should 
live as long as there are references to it, which should be guaranteed 
by the reference set in the PdfPage object.

So, as far as I can see: as long as you have a reference to the PdfPage 
object, this has a reference to the PdfDocument object, the PdfDocument 
object should remain in memory. How did you confirm its deletion?

It might be that something in this analysis is wrong, though, since it's 
all from the top of my head, but you might want to keep alert to this 
having consequences further down the road in case I am right. :-)

Very cool project, by the way. I wish you the best of luck and will help 
as much as I can with any questions you have.

-- Eskil



Message 9 in thread

Eskil Abrahamsen Blomfeldt wrote:
> Adam Batkin wrote:
>> The interesting bit is the typesystem file for the PdfDocument class:
>>
>>         <object-type name="PdfDocument">
>>                 <modify-function signature="page(int)const" 
>> access="private" rename="page_helper">
>>                         <modify-argument index="return">
>>                                 <reference-count action="ignore"/>
>>                                 <define-ownership class="java" 
>> owner="java"/>
> 
> Is the PdfPage a QObject? If not, then you should be careful doing this. 
> The reason is that if Java has the ownership of a C++ object, the C++ 
> object will be destroyed along with the Java object when the GC sees 
> fit. This of course means that you should make sure you have no dangling 
> references to the C++ object on the C++ side.

I think I'm okay, and here's why. Gunnar Sletta previously suggested a 
small addition to the PdfPage class (and the PdfDocument's construction 
of PdfPages): Every PdfPage maintains a Java reference back to the Java 
PdfDocument
This means that:
- PdfPages can be GC'd "at will" once they go out of scope, and that 
includes freeing the underlying Poppler::Page
- Documents can be GC'd (and thus the underlying Poppler::Document 
deleted) ONLY once all of the underlying PdfPage objects are free to be 
GC'd, since they have that Java reference back.

I also see your concern about double-freeing the Poppler::Page object, 
but I think I'm safe there, as the Poppler docs make clear (and the code 
confirms this) that a new Poppler::Page object is allocated with each 
call to page().

...snip...

>> I did some testing with a giant List of every PdfPage ever obtained, 
>> and made sure that no PdfDocuments were ever garbage collected 
>> (without the <modify-argument bits the PdfDocuments would often be 
>> GCed too early).
> 
> I'm not sure why this would happen, though. The default behavior is that 
> the return value of page() has what we call "split ownership" which 
> means that Java is allowed to garbage collect the Java object, but C++ 
> owns the C++ object so this will live until it's deleted.
> 
> If they are QObjects, then the Java objects of the PdfPage objects 
> should not be garbage collected until the corresponding C++ object is 
> deleted. The PdfDocument object, granted it's created in Java, should 
> live as long as there are references to it, which should be guaranteed 
> by the reference set in the PdfPage object.
> 
> So, as far as I can see: as long as you have a reference to the PdfPage 
> object, this has a reference to the PdfDocument object, the PdfDocument 
> object should remain in memory. How did you confirm its deletion?

I put 'std::cout << "deleting PdfPage" << std::endl;' in PdfPage's 
destructor (and something similar in PdfDocument's constructor). It's 
terrible, it's ugly, but at least it lets me see what's going on. I wish 
there was a way to force the garbage collector to REALLY garbage collect 
EVERYTHING that it possibly can. Now. As opposed to System.gc() which is 
more like "suggesting" that it "could" collect "whatever it feels like".

> It might be that something in this analysis is wrong, though, since it's 
> all from the top of my head, but you might want to keep alert to this 
> having consequences further down the road in case I am right. :-)
> 
> Very cool project, by the way. I wish you the best of luck and will help 
> as much as I can with any questions you have.

I appreciate that. I'm actually planning on putting this up as a project 
on Google Code so that other people can contribute their ideas and 
everyone can benefit from this work. If I find time, I'll put a writeup 
of my experiences with the Jambi Generator on one of the Google Code Wikis.

Thanks,

-Adam Batkin


Message 10 in thread

>
> If you're not using the generator you can still "steal" some of the 
> logic in it ;-). We have quite a bit of utility functionality that is 
> used from the generated code in qtjambi_core.h and friends. If you 
> have a look at the generated code of for instance QPixmap::toImage() 
> you see that converting a QImage into a proper jobject of class 
> com.trolltech.qt.gui.QImage is done something like this:
>
>     jobject jimg =
>         qtjambi_from_object(env,
>                             &image,
>                             "QImage",
>                             "com/trolltech/qt/gui/",
>                             true);
>
> I hope this helps a little bit.

That would be really interesting to know more about, since we're facing 
some of the same problems. We have a Win32 native image pointer that 
refreshes every with 50Hz or so which we want to visualize in a Jambi 
Widget. Maybe this is more a question to the Qt list, since I probably 
have to get it working in Qt first, or?

Regards,
Helge


Message 11 in thread

Helge Fredriksen wrote:

> That would be really interesting to know more about, since we're facing 
> some of the same problems. We have a Win32 native image pointer that 
> refreshes every with 50Hz or so which we want to visualize in a Jambi 
> Widget. Maybe this is more a question to the Qt list, since I probably 
> have to get it working in Qt first, or?

Perhaps more suited for the qt-interest mailing list, yes.

In Qt / C++ QPixmap has a function to/fromWinHBITMAP which converts 
between HBITMAP and QPixmap. It is however a software / pixel copy 
approach and will most likely be too slow for 20 updates pr second. What 
you should do instead is for this case is to implement a Qt / C++ widget 
that can render HBITMAP's directly. This is done more or less like this:

class NativeBitmapRenderer : public QWidget {
public:
     NativeBitmapRenderer(QWidget *parent) {
         setAttribute(Qt::WA_PaintOnScreen);
         // start 50mz timer and call update()
     }

     QPaintEngine *paintEngine() const { return 0; }

     void paintEvent(QPaintEvent *e) {
         HDC dc = getDC();

         BitBlt(dc, ..., bitmap_dc, ..., SRCCOPY);

         releaseDC(dc);
     }
}

The paintEngine() -> return 0 and PaintOnScreen attributes bypasses the 
Qt painting subsystem and lets you use GDI drawing calls.

best regards,
Gunnar