Search code examples
objectcomjava-native-interface

JNI: Sink Object not Firing, Releases freezing


I am trying to create a JNI wrapper for this windows application called 'Personal Communications' through a COM Object that it supplies. PCOM Help

Through lots of googling, I have managed alot on my own. I can suffessfully connect to the COM Object and run getter/setter methods to retrieve various info on the application.

My problem is now with Sink Objects (trying to get the application to send me events). As far as I can tell, everything is returning a good return code, but the Invoke on the Sink Class is never called. Whats worse, if a Event was suppose to be called, Releasing the COM object that told it to start sending events, hangs the application. If I end the application advising it of the sink but not making it fire any events, the application doesn't hang.

I have tried various methods and compilers. They all give the same result. Whats weird though, if I use the same code in an actual application (exe) outside of Java, everything works fine and the Events are fired through the Sink Object. So I am truly at a lost for whats wrong with Java in the picture.

Here is my code I have so far:

PcommControl.h (My Wrapper for controlling the COMs)

initConnectionManagerEvents: creates a new sink object and advises the COM of it. RegisterStartEvent tells the COM to start sending messages to the sink objects.

removeConnectionManagerEvents: unadvises and removes all sink objects. UnregisterStartEvent tells the COM to stop sending messages to the sink objects.

 jobject PcommControl::initConnectionManagerEvents(jobject sinkType) {
    if (!initConnectionManager())
        return NULL;

    if (autConnectionManagerPoint == NULL) {
        autConnectionManagerPoint = getConnectionPoint(autConnectionManager,
                DIID_IStartEvent);
        if (autConnectionManagerPoint == NULL)
            return NULL;
    }

    ConnectionSink *conSinkC = new ConnectionSink(jvm, sinkType);

    if (!conSinkC->isInitialized()) {
        delete conSinkC;
        return NULL;
    }

    if (!conSinkC->Advise(autConnectionManagerPoint)) {
#ifdef DEBUG
        printf("PcommControl: failed to advise ConnectionManagerEvent\n");
#endif
        delete conSinkC;
        return NULL;
    }

#ifdef DEBUG
    printf("PcommControl: ConnectionManagerEvent> %ld\n", conSinkC->getCookie());
#endif

    ConnectionSink *temp = connectionManagerEvents.put(
            (long) conSinkC->getCookie(), conSinkC);
    if (temp) {
        temp->Release();
    }

    if (connectionManagerEvents.getSize() == 1) {
#ifdef DEBUG
        printf("PcommControl: Registering ConnectionManagerEvent\n");
#endif
        HRESULT hresult = autConnectionManager->RegisterStartEvent();
        if (!SUCCEEDED(hresult)) {
#ifdef DEBUG
            printf("Failed to get RegisterStartEvent\n");
#endif
            // TODO
        }
    }

    return conSinkC->getJavaObjectConnection();
}

void PcommControl::removeConnectionManagerEvents() {
    ConnectionSink *connectionSink;

    if ((autConnectionManager) && (!safeUnload)) {
#ifdef DEBUG
        printf("PcommControl: Unregistering ConnectionManagerEvent\n");
#endif
        // TODO: seems to cause hanging issues for Java
        HRESULT hresult = autConnectionManager->UnregisterStartEvent();
        if (!SUCCEEDED(hresult)) {
#ifdef DEBUG
            printf("Failed to UnregisterStartEvent\n");
#endif
        }
    }

    while ((connectionSink = connectionManagerEvents.removeHead()) != NULL) {
        if (!safeUnload) {
#ifdef DEBUG
            printf("PcommControl: releasing a connection manager event\n");
#endif
            connectionSink->Unadvise();
#ifdef DEBUG
            printf("PcommControl: start release\n");
#endif
            connectionSink->Release();
        }
    }

#ifdef DEBUG
    printf("PcommControl: done releasing ConnectionManager events\n");
#endif
}

JNIEventSink.h (My Sink Interface for all JNI Sinks)

#ifndef JNIEVENTSINK_H_
#define JNIEVENTSINK_H_

#include <jni.h>
#include <OCIdl.h>

#define FARFAR  FAR* FAR*

class JNIEventSink: public IDispatch {
protected:

private:
    bool initialized;
    bool deconstructor;
    DWORD referenceCount;

    JavaVM *jvm;
    jobject javaObjectConnection;
    DWORD cookie;

    IConnectionPoint *point;

    static jclass javaLangClass;
    static jmethodID javaLangClassNewInstance;

    void init(JavaVM *javaVM, jobject sinkType) {
        initialized = false;
        deconstructor = false;
        referenceCount = 0;
        jvm = javaVM;
        javaObjectConnection = NULL;
        cookie = 0;

        AddRef();

        // create Java sink class from sinkType
//      if (javaVM) {
//          JNIEnv *env;
//          javaVM->AttachCurrentThread((void **) &env, NULL);
//          if (env == NULL) {
//#ifdef DEBUG
//              printf("JNIEventSink: java environment not found!\n");
//#endif
//              return;
//          }
//
//          if (javaLangClass == NULL) {
//              javaLangClass = NULL;
//              javaLangClassNewInstance = NULL;
//
//              javaLangClass = env->FindClass("java/lang/Class");
//              if (javaLangClass == NULL) {
//#ifdef DEBUG
//                  printf("JNIEventSink: javaLangClass not found!\n");
//#endif
//                  return;
//              }
//              javaLangClassNewInstance = env->GetMethodID(javaLangClass,
//                      "newInstance", "()Ljava/lang/Object;");
//              if (javaLangClassNewInstance == NULL) {
//#ifdef DEBUG
//                  printf(
//                          "JNIEventSink: javaLangClass NewInstance not found!\n");
//#endif
//                  return;
//              }
//          }
//
//          javaObjectConnection = env->CallObjectMethod(sinkType,
//                  javaLangClassNewInstance);
//          if (javaObjectConnection == NULL) {
//#ifdef DEBUG
//              printf(
//                      "JNIEventSink: Failed to create new Connection Object!\n");
//#endif
//              return;
//          }
//      }
        initialized = true;
    }
public:
    bool test;

    JNIEventSink(JavaVM *javaVM, jobject sinkType) {
#ifdef DEBUG
        printf("JNIEventSink: constructor\n");
#endif
        init(javaVM, sinkType);
        test = false;
    }

    virtual ~JNIEventSink() {
#ifdef DEBUG
        printf("JNIEventSink: deconstructor\n");

        if (test)
            printf("YESYESYESYESYESYESYESYES\n");
#endif
        deconstructor = true;
//
//      if (point != NULL)
//          Unadvise();
//
//      if (referenceCount > 0)
//          Release();
    }

    bool isInitialized() {
        return initialized;
    }

    bool Advise(IConnectionPoint *point) {
#ifdef DEBUG
        printf("JNIEventSink: Start Advise\n");
#endif
        this->point = point;
        this->point->AddRef();
        HRESULT hresult = point->Advise(this, &cookie);

        // TODO set cookie to java class

#ifdef DEBUG
        printf("JNIEventSink: Advise End\n");

        if (!SUCCEEDED(hresult))
            printf("JNIEventSink: failed\n");
#endif

        return SUCCEEDED(hresult);
    }

    bool Unadvise() {
#ifdef DEBUG
        printf("JNIEventSink: Start Unadvise\n");
#endif
        if (point == NULL)
            return true;

        IConnectionPoint *point = this->point;
        this->point = NULL;

        HRESULT hresult = point->Unadvise(cookie);
        point->Release();

#ifdef DEBUG
        printf("JNIEventSink: Unadvise End\n");

        if (!SUCCEEDED(hresult))
            printf("JNIEventSink: failed\n");
#endif

        return SUCCEEDED(hresult);
    }

    DWORD getCookie() {
        return cookie;
    }

    jobject getJavaObjectConnection() {
        return javaObjectConnection;
    }

    ULONG
    STDMETHODCALLTYPE AddRef() {
#ifdef DEBUG
        printf("JNIEventSink: Add Ref %ld,%ld,%ld\n", (long) this, cookie,
                referenceCount);
#endif
        referenceCount++;
        return referenceCount;
    }

    ULONG
    STDMETHODCALLTYPE Release() {
#ifdef DEBUG
        printf("JNIEventSink: Start Release %ld,%ld,%ld\n", (long) this,
                cookie, referenceCount);
#endif
        if (referenceCount == 0) {
#ifdef DEBUG
            printf("0 ref\n");
#endif
            return 0;
        }

        referenceCount--;
        long temp = referenceCount;

        if ((temp == 0) && (!deconstructor)) {
#ifdef DEBUG
            printf("JNIEventSink: deleting\n");
#endif
            delete this;
        }

        return temp;
    }

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
            void **ppvObject) {
#ifdef DEBUG
        printf("JNIEventSink: QueryInterface %ld,%ld\n", (long) this, cookie);
#endif
        if (iid == IID_IUnknown) {
            *ppvObject = (IUnknown *) this;
        } else if (iid == IID_IDispatch) {
            *ppvObject = (IDispatch *) this;
        } else {
            *ppvObject = (void *) this;
        }

        ((IUnknown *) (*ppvObject))->AddRef();
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) {
#ifdef DEBUG
        printf("JNIEventSink: GetTypeInfoCount %ld,%ld\n", (long) this, cookie);
#endif
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid,
            LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) {
#ifdef DEBUG
        printf("JNIEventSink: GetIDsOfNames %ld,%ld\n", (long) this, cookie);
#endif
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int iTInfo,
            LCID lcid, ITypeInfo FARFAR ppTInfo) {
#ifdef DEBUG
        printf("JNIEventSink: GetTypeInfo %ld,%ld\n", (long) this, cookie);
#endif
        return E_NOTIMPL;
    }

    //  virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid,
    //          LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams,
    //          VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo,
    //          unsigned int FAR* puArgErr) = 0;
};

jclass JNIEventSink::javaLangClass = NULL;
jmethodID JNIEventSink::javaLangClassNewInstance = NULL;

#endif /* JNIEVENTSINK_H_ */

ConnectionSink.h (My Implementation of the JNI Sink that is suppose to get all Events from autConnectionManager)

#ifndef CONNECTIONSINK_H_
#define CONNECTIONSINK_H_

#include <jni.h>
#include "PcommInterfaces.h"
#include "..\COM\JNIEventSink.h"

class ConnectionSink: public JNIEventSink {
private:

public:
    ConnectionSink(JavaVM *javaVM, jobject sinkType) :
        JNIEventSink(javaVM, sinkType) {
    }

    virtual ~ConnectionSink() {
#ifdef DEBUG
        printf("ConnectionSink: deconstructor\n");
#endif
    }
    // IStartEvent
    // future events I want to call
    // 1 - void NotifyStartEvent(VARIANT ConnHandle, VARIANT_BOOL bStarted);
    // 2 - void NotifyStartError(VARIANT ConnHandle);
    // 3 - void NotifyStartStop(int* Reason);

    virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid,
            LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams,
            VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo,
            unsigned int FAR* puArgErr) {
        // TODO this seems like it is never called

#ifdef DEBUG
        printf("ConnectionSink: Invoke %ld,%ld,%ld\n", (long) this,
                this->getCookie(), dispIdMember);
#endif

        test = true;

        return S_OK;
        //return E_NOTIMPL;
    }
};

#endif /* CONNECTIONSINK_H_ */

Example output when it hangs:

Java_gov_ssa_utils_pcomm_ConnectionManager_registerStartEvents
JNIEventSink: constructor
JNIEventSink: Add Ref 72880304,0,0
JNIEventSink: Start Advise
JNIEventSink: Add Ref 72880304,0,1
JNIEventSink: Advise End
PcommControl: ConnectionManagerEvent> 1
PcommControl: Registering ConnectionManagerEvent
Created and attached sink.
Waiting 10 seconds for user to fire event
Destroying.
Java_gov_ssa_utils_PComm_release
PcommControl: destroying
PcommControl: Unregistering ConnectionManagerEvent

Thanks for any help.


Solution

  • I figured out my problem.

    The thread that created the sink object needs to also pump the thread's message queue using the window's function 'GetMessage'.

    This is why the windows executable was working correctly, it already have a message pump for the window that was created. I figured this out when I put a breakpoint in the sink object when the invoke was being called and looked at the stack trace. 'GetMessage' was the first thing on the trace that came from my code.

    In this instance, I believe why 'GetMessage' is the answer is because I believe the COM's code is calling 'PostThreadMessage' to notify me that a sink event has occurred and 'GetMessage' looks at these messages and somehow knows they are COM related and handle them on its own.

    PS: Notice I said the thread that created the sink. If you create the sink in one thread and have the message pump in another, you will receive the message from the COM about the sink, but it won't know how to handle it automatically since this thread has nothing loaded about the COM in its space. So the sink object's invoke is never called.

    So Java could access the same COM everywhere, I made the 2nd thread control everything about the COM, and any native methods that were called redirect the request to the that thread. So no matter what thread Java is running in, it will always have access to the same COM without having to reload everything.