I am sending an audio stream from my browser (Chrome) to the "peerconnection_client" sample application (trunk\talk\examples\peerconnection) and trying to dump it into a file using StartRTPDump:
int StartRTPDump(int channel, const char fileNameUTF8[1024], RTPDirections direction = kRtpIncoming);
Here is my test code:
void Conductor::OnPeerConnected(int id, const std::string& name) {
LOG(INFO) << __FUNCTION__;
// Refresh the list if we're showing it.
if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
main_wnd_->SwitchToPeerList(client_->peers());
// My test code
{
voe_ = webrtc::VoiceEngine::Create();
webrtc::VoEBase* voeBase = webrtc::VoEBase::GetInterface(voe_);
rtp_rtcp_ = webrtc::VoERTP_RTCP::GetInterface(voe_);
voeBase->Init();
channelId_ = voeBase->CreateChannel();
bool isActive = rtp_rtcp_->RTPDumpIsActive(channelId_); // "isActive" is false
int ret = rtp_rtcp_->StartRTPDump(channelId_, "AudioStreamFromRemotePeer.rtp"); // "ret" is 0
isActive = rtp_rtcp_->RTPDumpIsActive(channelId_); // "isActive" is true
}
}
void Conductor::OnPeerDisconnected(int id) {
LOG(INFO) << __FUNCTION__;
if (id == peer_id_) {
LOG(INFO) << "Our peer disconnected";
main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
} else {
// Refresh the list if we're showing it.
if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
main_wnd_->SwitchToPeerList(client_->peers());
}
// My test code
{
bool isActive = rtp_rtcp_->RTPDumpIsActive(channelId_); // "isActive" is true
int ret = rtp_rtcp_->StopRTPDump(channelId_); // "ret" is 0
isActive = rtp_rtcp_->RTPDumpIsActive(channelId_); // "isActive" is false
rtp_rtcp_->Release();
webrtc::VoiceEngine::Delete(voe_);
}
}
When I set a breakpoint inside the following function after calling OnPeerConnected() but before OnPeerDisconnected():
int32_t Channel::ReceivedRTPPacket(const int8_t* data, int32_t length, const PacketTime& packet_time)
{
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,_channelId), "Channel::ReceivedRTPPacket()"); // "_channelId" here matches to "channelId_" above
....
}
I can see incoming data.
But the generated .rtp file after calling OnPeerDisconnected() looks like:
Why am I not getting any data in my .rtp file?
Related
Hello nano developers,
I'd like to realize the following proto:
message container {
enum MessageType {
TYPE_UNKNOWN = 0;
evt_resultStatus = 1;
}
required MessageType mt = 1;
optional bytes cmd_evt_transfer = 2;
}
message evt_resultStatus {
required int32 operationMode = 1;
}
...
The dots denote, there are more messages with (multiple) primitive containing datatypes to come. The enum will grow likewise, just wanted to keep it short.
The container gets generated as:
typedef struct _container {
container_MessageType mt;
pb_callback_t cmd_evt_transfer;
} container;
evt_resultStatus is:
typedef struct _evt_resultStatus {
int32_t operationMode;
} evt_resultStatus;
The field cmd_evt_transfer should act as a proxy of subsequent messages like evt_resultStatus holding primitive datatypes.
evt_resultStatus shall be encoded into bytes and be placed into the cmd_evt_transfer field.
Then the container shall get encoded and the encoding result will be used for subsequent transfers.
The background why to do so, is to shorten the proto definition and avoid the oneof thing. Unfortunately syntax version 3 is not fully supported, so we can not make use of any fields.
The first question is: will this approach be possible?
What I've got so far is the encoding including the callback which seems to behave fine. But on the other side, decoding somehow skips the callback. I've read issues here, that this happened also when using oneof and bytes fields.
Can someone please clarify on how to proceed with this?
Sample code so far I got:
bool encode_msg_test(pb_byte_t* buffer, int32_t sval, size_t* sz, char* err) {
evt_resultStatus rs = evt_resultStatus_init_zero;
rs.operationMode = sval;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
/*encode container*/
container msg = container_init_zero;
msg.mt = container_MessageType_evt_resultStatus;
msg.cmd_evt_transfer.arg = &rs;
msg.cmd_evt_transfer.funcs.encode = encode_cb;
if(! pb_encode(&stream, container_fields, &msg)) {
const char* local_err = PB_GET_ERROR(&stream);
sprintf(err, "pb_encode error: %s", local_err);
return false;
}
*sz = stream.bytes_written;
return true;
}
bool encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
evt_resultStatus* rs = (evt_resultStatus*)(*arg);
//with the below in place a stream full error rises
// if (! pb_encode_tag_for_field(stream, field)) {
// return false;
// }
if(! pb_encode(stream, evt_resultStatus_fields, rs)) {
return false;
}
return true;
}
//buffer holds previously encoded data
bool decode_msg_test(pb_byte_t* buffer, int32_t* sval, size_t msg_len, char* err) {
container msg = container_init_zero;
evt_resultStatus res = evt_resultStatus_init_zero;
msg.cmd_evt_transfer.arg = &res;
msg.cmd_evt_transfer.funcs.decode = decode_cb;
pb_istream_t stream = pb_istream_from_buffer(buffer, msg_len);
if(! pb_decode(&stream, container_fields, &msg)) {
const char* local_err = PB_GET_ERROR(&stream);
sprintf(err, "pb_encode error: %s", local_err);
return false;
}
*sval = res.operationMode;
return true;
}
bool decode_cb(pb_istream_t *istream, const pb_field_t *field, void **arg) {
evt_resultStatus * rs = (evt_resultStatus*)(*arg);
if(! pb_decode(istream, evt_resultStatus_fields, rs)) {
return false;
}
return true;
}
I feel, I don't have a proper understanding of the encoding / decoding process.
Is it correct to assume:
the first call of pb_encode (in encode_msg_test) takes care of the mt field
the second call of pb_encode (in encode_cb) handles the cmd_evt_transfer field
If I do:
bool encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
evt_resultStatus* rs = (evt_resultStatus*)(*arg);
if (! pb_encode_tag_for_field(stream, field)) {
return false;
}
if(! pb_encode(stream, evt_resultStatus_fields, rs)) {
return false;
}
return true;
}
then I get a stream full error on the call of pb_encode.
Why is that?
Yes, the approach is reasonable. Nanopb callbacks do not care what the actual data read or written by the callback is.
As for why your decode callback is not working, you'll need to post the code you are using for decoding.
(As an aside, Any type does work in nanopb and is covered by this test case. But the type_url included in all Any messages makes them have a quite large overhead.)
I need to decode some H.264 frames into raw format (YUV420).
I receive packets which contains frame by some custom protocol.
How can I pass received H.264 frames into GStreamermm API to decode?
In current time I read them tutorials (unfortunately this is GST - API of C version), but can't find actual GStreamermm API documentation.
Please, point me in any documents or examples of how to do it.
I was able to implement the transfer data via pipeline and retrieve decoding raw video frames using C++ version. Here is raw example:
struct WebPipeline {
Glib::RefPtr<Gst::AppSrc> appsrc;
Glib::RefPtr<Gst::AppSink> appdst;
Glib::RefPtr<Gst::Element> h264parser;
Glib::RefPtr<Gst::Element> avdec_h264;
Glib::RefPtr<Gst::Element> jpegenc;
Glib::RefPtr<Gst::Pipeline> pipe;
bool accepts_data {false};
};
WebPipePtr ExampleClass::CreatePipeline() {
auto web_pipe = std::make_shared<WebPipeline>();
web_pipe->appsrc = Gst::AppSrc::create("web_appsrc");
if (!web_pipe->appsrc) {
throw std::runtime_error("Can't create AppSrc");
}
web_pipe->appdst = Gst::AppSink::create("web_appdst");
if (!web_pipe->appdst) {
throw std::runtime_error("Can't create AppSink");
}
web_pipe->h264parser = Gst::ElementFactory::create_element("h264parse", "h264_parser");
if (!web_pipe->h264parser) {
throw std::runtime_error("Can't create h264parse");
}
web_pipe->avdec_h264 = Gst::ElementFactory::create_element("avdec_h264", "avdec264");
if (!web_pipe->avdec_h264) {
throw std::runtime_error("Can't create avdec_h264");
}
web_pipe->jpegenc = Gst::ElementFactory::create_element("jpegenc");
if (!web_pipe->jpegenc) {
throw std::runtime_error("Can't create jpegenc");
}
web_pipe->pipe = Gst::Pipeline::create("websocket_pipe");
if (!web_pipe->pipe) {
throw std::runtime_error("Can't create pipeline");
}
web_pipe->appdst->property_emit_signals() = true;
web_pipe->appdst->set_sync(false);
web_pipe->appdst->signal_new_sample().connect(sigc::bind(sigc::mem_fun(this, &ExampleClass::PullFromPipe), web_pipe->appdst));
web_pipe->appsrc->property_emit_signals() = true;
web_pipe->appsrc->signal_need_data().connect(sigc::bind(sigc::mem_fun(this, &ExampleClass::EnableAcceptance), web_pipe));
web_pipe->appsrc->signal_enough_data().connect(sigc::bind(sigc::mem_fun(this, &ExampleClass::DisableAcceptance), web_pipe));
web_pipe->pipe->add(web_pipe->appsrc)->add(web_pipe->h264parser)->add(web_pipe->avdec_h264)->add(web_pipe->jpegenc)->add(web_pipe->appdst);
web_pipe->appsrc->link(web_pipe->h264parser)->link(web_pipe->avdec_h264)->link(web_pipe->jpegenc)->link(web_pipe->appdst);
web_pipe->pipe->set_state(Gst::STATE_PLAYING);
return web_pipe;
}
void ExampleClass::EnableAcceptance(guint, WebPipePtr pipe) {
if (!pipe->accepts_data) {
BOOST_LOG_SEV(GetLogger(), log::info) << "Begin push frames";
pipe->accepts_data = true;
}
}
void ExampleClass::DisableAcceptance(WebPipePtr pipe) {
if (pipe->accepts_data) {
BOOST_LOG_SEV(GetLogger(), log::info) << "Begin drop frames";
pipe->accepts_data = false;
}
}
void ExampleClass::PushToPipe(WebPipePtr pipe, std::vector<uint8_t>&& frames) {
if (!pipe->accepts_data) {
return Gst::FLOW_CUSTOM_ERROR;
}
GstBuffer* buffer = gst_buffer_new_wrapped_full(static_cast<GstMemoryFlags>(GST_MEMORY_FLAG_READONLY | GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS),
const_cast<uint8_t*>(frame.data()),
frame.size(),
0,
frame.size(),
reinterpret_cast<gpointer>(frame_ref), // inner implementation of some sort of wrapper
destroy); // lamda-destructor
buffer->set_pts(time);
return pipe->appsrc->push_buffer(buffer);
}
Gst::FlowReturn ExampleClass::PullFromPipe(const Glib::RefPtr<Gst::AppSink>& appsink) {
auto sample = appsink->pull_sample();
if (!sample) {
return Gst::FLOW_ERROR;
}
if (appsink->property_eos()) {
return Gst::FLOW_EOS;
}
Gst::ClockTime timestamp = 0;
{
auto buffer = sample->get_buffer();
if (!buffer) {
throw std::runtime_error("Can't get buffer from sample");
}
timestamp = buffer->get_pts();
}
// process sample...
return Gst::FLOW_OK;
}
I try pull from remote master to local master. In remote master only one not synchronized commit.
Error in method git_annotated_commit_lookup():
Git Error -3 : object not found - no match
for id (08f4a8cc00400100f083caccd755000020299210)
In callback fetchhead_ref_cb never exevute code in "if" block.
int fetchhead_ref_cb(const char *name, const char *url,
const git_oid *oid, unsigned int is_merge, void *payload)
{
qDebug() << "fetchhead_ref_cb";
if (is_merge)
{
qDebug() << "Is merge";
git_oid_cpy((git_oid *)payload, oid);
}
return 0;
}
bool pullBranch()
{
int error;
git_remote *remote;
git_oid branchOidToMerge;
/* lookup the remote */
error = git_remote_lookup(&remote, repo, "origin");
if (!checkForError(error, "Remote lookup")) {
git_fetch_options options = GIT_FETCH_OPTIONS_INIT;
options.callbacks.credentials = cred_acquire_cb;
error = git_remote_fetch(remote,
NULL, /* refspecs, NULL to use the configured ones */
&options, /* options, empty for defaults */
"pull"); /* reflog mesage, usually "fetch" or "pull", you can leave it NULL for "fetch" */
if (!checkForError(error, "Remote fetch")) {
git_repository_fetchhead_foreach(repo, fetchhead_ref_cb, &branchOidToMerge);
git_merge_options merge_options = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
git_annotated_commit *commit;
error = git_annotated_commit_lookup(&commit, repo, &branchOidToMerge);
if (!checkForError(error, "Annotated commit lookup")) {
error = git_merge(repo, (const git_annotated_commit **)commit, 1, &merge_options, &checkout_options);
if (!checkForError(error, "Merge")) {
git_annotated_commit_free(commit);
git_repository_state_cleanup(repo);
git_remote_free(remote);
return true;
}
}
}
}
git_remote_free(remote);
return false;
}
Solution for fast-forward merge:
GitPullStatus GitWizard::pullBranch()
{
git_remote *remote;
int error = git_remote_lookup(&remote, repo, "origin");
if (!checkForError(error, "Remote lookup")) {
git_fetch_options options = GIT_FETCH_OPTIONS_INIT;
options.callbacks.credentials = cred_acquire_cb;
error = git_remote_fetch(remote,
NULL, /* refspecs, NULL to use the configured ones */
&options, /* options, empty for defaults */
"pull"); /* reflog mesage, usually "fetch" or "pull", you can leave it NULL for "fetch" */
if (!checkForError(error, "Remote fetch")) {
git_oid branchOidToMerge;
git_repository_fetchhead_foreach(repo, fetchhead_ref_cb, &branchOidToMerge);
git_annotated_commit *their_heads[1];
error = git_annotated_commit_lookup(&their_heads[0], repo, &branchOidToMerge);
checkForError(error, "Annotated commit lookup");
git_merge_analysis_t anout;
git_merge_preference_t pout;
qDebug() << "Try analysis";
error = git_merge_analysis(&anout, &pout, repo, (const git_annotated_commit **) their_heads, 1);
checkForError(error, "Merge analysis");
if (anout & GIT_MERGE_ANALYSIS_UP_TO_DATE) {
qDebug() << "up to date";
git_annotated_commit_free(their_heads[0]);
git_repository_state_cleanup(repo);
git_remote_free(remote);
return GitPullStatus::GIT_UP_TO_DATE;
} else if (anout & GIT_MERGE_ANALYSIS_FASTFORWARD) {
qDebug() << "fast-forwarding";
git_reference *ref;
git_reference *newref;
const char *name = QString("refs/heads/").append(mCurrentBranch).toLocal8Bit().data();
if (git_reference_lookup(&ref, repo, name) == 0)
git_reference_set_target(&newref, ref, &branchOidToMerge, "pull: Fast-forward");
git_reset_from_annotated(repo, their_heads[0], GIT_RESET_HARD, NULL);
git_reference_free(ref);
git_repository_state_cleanup(repo);
}
git_annotated_commit_free(their_heads[0]);
git_repository_state_cleanup(repo);
git_remote_free(remote);
return GitPullStatus::GIT_PULL_OK;
}
}
git_remote_free(remote);
return GitPullStatus::GIT_PULL_ERROR;
}
I'm trying to store and fetch some data from LMDB. Data seems to be stored, I can see the keys in my database, but it gives me MDB_NOTFOUND when I try to fetch the value with the same ID I have just stored it under.
Database opening
MDB_env* environment;
MDB_dbi main;
MDB_dbi order;
mdb_env_create(&environment);
mdb_env_set_maxdbs(environment, 2);
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
int rc;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
mdb_txn_commit(txn);
Insertion
void Core::Archive::addElement(const Shared::Message& message) {
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
message.serialize(ds);
uint64_t stamp = message.getTime().toMSecsSinceEpoch();
const std::string& id = message.getId().toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (uint8_t*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
if (rc == 0) {
MDB_val orderKey;
orderKey.mv_size = 8;
orderKey.mv_data = (uint8_t*) &stamp;
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
if (rc) {
mdb_txn_abort(txn);
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
}
}
} else {
qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
mdb_txn_abort(txn);
}
}
Fetching
Shared::Message Core::Archive::getElement(const QString& id) {
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.toStdString().size();
lmdbKey.mv_data = (uint8_t*)id.toStdString().c_str();
MDB_txn *txn;
int rc;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
if (rc) {
qDebug() <<"Get error: " << mdb_strerror(rc);
mdb_txn_abort(txn);
throw NotFound(id.toStdString(), jid.toStdString());
} else {
//it never comes here
}
}
Testing code
Core::Archive ar();
ar.open("Test");
Shared::Message msg1;
msg1.generateRandomId();
msg1.setBody("oldest");
msg1.setTime(QDateTime::currentDateTime().addDays(-7));
Shared::Message msg2;
msg2.generateRandomId();
msg2.setBody("Middle");
msg2.setTime(QDateTime::currentDateTime().addDays(-4));
Shared::Message msg3;
msg3.generateRandomId();
msg3.setBody("newest");
msg3.setTime(QDateTime::currentDateTime());
ar.addElement(msg2);
ar.addElement(msg3);
ar.addElement(msg1);
Shared::Message d0 = ar.getElement(msg1.getId());
My logs show stored keys. I can see the required key, I can even compare it with the requested key if I use cursors to scroll over the whole storage, it even shows they are equal, but mdb_cursor_get or mdb_get constantly give me MDB_NOTFOUND. What am I doing wrong?
I got it. No matter what I put into database, I have to read it as a char*
Had to modify fetching code
lmdbKey.mv_data = (uint8_t*)id.toStdString().c_str();
I had to change it to
lmdbKey.mv_data = (char*)id.toStdString().c_str();
and it worked
I have to import a NPAPI DLL in a C++ application for my project.
I follow the excellent tutorial : http://colonelpanic.net/2009/03/building-a-firefox-plugin-part-one/
However I have some trouble to access to the methods of the dll. (Note: in a browser the dll is completly functional). After calling the main functions : NP_GetEntryPoints and NP_Initialize and retrieving the ScriptableNPObject, the invocation of the methods return no value with no error. The property or method names are the same used in javascript in the browser (functional case).
For information, the property and method names and the mime-type have been replaced in this sample.
Anyone has an idea to invoke the methods of the dll by simulating what the browser does?
Here is a part of the main program:
if (hDLL == 0)
{
std::cout << "DLL failed to load!" << std::endl;
}
else
{
std::cout << "DLL loaded!" << std::endl;
//WRAP NP_GETENTRYPOINTS FUNCTION:
_GetEntryPointsFunc = (GetEntryPointsFunc)GetProcAddress(hDLL, "NP_GetEntryPoints");
if (_GetEntryPointsFunc)
{
std::cout << "Get NP_GetEntryPoints Function!" << std::endl;
status = _GetEntryPointsFunc(pFuncs);
}
//WRAP NP_INITIALIZE FUNCTION:
_InitializeFunc = (InitializeFunc)GetProcAddress(hDLL, "NP_Initialize");
if (_InitializeFunc)
{
std::cout << "Get NP_Initialize Function!" << std::endl;
status = _InitializeFunc(&sBrowserFuncs);
}
int32_t mode = NP_EMBED;
int32_t argc = 7;
static const char mimetype[] = "application/x-mime_type_of_my_plugin";
char * argn[] = {"param1", "param2", "param3", "param4", "param5", "param6", "param7"};
char * argv[] = { "value1", "value2", "value3", "value4", "value5", "value6", "value7" };
NPObject np_object;
uint16_t size;
char* descritpionString;
char* nameString;
instance = &(plugin_instance.mNPP);
status = pFuncs->newp((char*)mimetype, instance, (uint16_t)mode, argc, argn, argv, &saved);
status = pFuncs->version; //OK
status = pFuncs->getvalue(instance,NPPVpluginDescriptionString,&descritpionString); //OK
status = pFuncs->getvalue(instance,NPPVpluginNameString,&nameString); //OK
status = pFuncs->getvalue(instance,NPPVpluginScriptableNPObject,&np_object); //ISSUE STARTS HERE
std::cin.get();
}
Here is my create_object function called after getting the scriptable NPObject with the getvalue function:
NPObject* _createobject(NPP npp, NPClass* aClass)
{
if (!npp) {
return nullptr;
}
if (!aClass) {
return nullptr;
}
NPObject *npobj;
if (aClass->allocate) {
npobj = aClass->allocate(npp, aClass);
} else {
npobj = (NPObject *)malloc(sizeof(NPObject));
}
if (npobj) {
npobj->_class = aClass;
npobj->referenceCount = 1;
//TEST:
NPError status;
NPString url;
NPVariant result;
NPVariant variant;
NPIdentifier property = "existing_property";
NPIdentifier *arrayId;
uint32_t count = 2;
const char *str = "https://test_url.com";
url.UTF8Characters = str;//;
url.UTF8Length = 20;
variant.type = NPVariantType_String;
variant.value.stringValue = url;
NPVariant args[] = { variant };
status = 1; //GENERIC ERROR VALUE
status = npobj->_class->structVersion; //OK
status = npobj->_class->hasMethod(npobj,L"existing_set_function"); //STATUS OK
status = npobj->_class->enumerate(npobj, &arrayId, &count); //Privileged instruction ERROR
status = npobj->_class->hasProperty(npobj, property); //STATUS OK
status = npobj->_class->getProperty(npobj, property, &result); //STATUS OK BUT NO RESULT
status = npobj->_class->invoke(npobj,L"existing_set_function",args,1,&result); //STATUS OK
status = npobj->_class->invoke(npobj,L"existing_get_function",args,0,&result); //STATUS OK BUT NO RESULT
status = npobj->_class->invokeDefault(npobj,args,0,&result); //STATUS OK BUT NO RESULT
//END TEST
}
return npobj;
}
Finally, here is the plugin_instance methods to declare ndata and pdata:
nsNPAPIPluginInstance::nsNPAPIPluginInstance()
{
mNPP.pdata = NULL;
mNPP.ndata = this;
}
nsNPAPIPluginInstance::~nsNPAPIPluginInstance()
{
}
Thanks in advance.
While it is unclear what "no result" means, a privileged instruction error suggests the function pointer is off.
I'd start with:
Checking that structVersion >= NP_CLASS_STRUCT_VERSION_ENUM (otherwise NPClass::enumerate is not available)
Using the proper npapi-sdk headers - your function pointer type names suggest you are not doing that
Using a simple test plugin to see what happens on the plugin side
Take care that the NPIdentifiers match up between your host application and the plugin, i.e. use a common string->NPIdentifier mapping for the host code and NPN_GetStringIdentifier() - as posted this can't work
Don't test the NPObject right in the creation function - the plugin may set things up to work properly only after NPN_CreateObject() returned