I'm trying to call a DLL compiled from VB6 source code that I do not have access to. The VB6 code simply retrieves data from a DB2 database using ADO and my client code grabs that data and marshals it to my Java code. I'm attempting to achieve this using JNI and COM (without a third-party bridge). It works 75% of the time, but the other 25% of the time, the JVM crashes with the usual Hotspot crash log containing an access violation exception. However, I don't know what in my C++ code (VC++ 8) could be causing this except for passing a "wild" pointer to the code lying underneath the COM object interface. If that is the case, I don't know how I am doing that.
The Java code that is calling my native method is running on Tomcat 5.5.25 and just to be safe, I am not allowing multiple threads to concurrently call the method in my JNI DLL (though I realize that this will kill performance). Once I can get past this problem, I'll do the COM interfacing on a worker thread in my native code so I don't screw up CoInitialize and CoUninitialize calls in the case the same thread is concurrently executing multiple calls to my native code.
I've noticed that in most cases, the JVM crashes during my call to the pClsAccount->OpenConnection method. However, my DLL isn't what is listed on the top of the call stack, which is why I suspect the passing of a wild pointer, though I'm just taking a guess at that. Does anyone have an idea as to what's going on ? Also, is there any way to glean anything useful from the stack trace in the crash log ? The only thing I've been able to find is the offset of the entry points for the called functions in the DLLs. Below is my code and at the bottom is a portion of the JVM crash log. My DLL that uses JNI is CustomerInfoProxy.dll and the VB6 DLL is MIGI.dll
JNIEXPORT jobject JNICALL Java_CustomerInfo_nGetCustomerAccountInfo(JNIEnv env, jobject customerInfo, jstring accountNumber, jstring iniFileName)
{
jboolean isCopy;
// Account info class and instance to be instantiated
jclass accountInfoCls = NULL;
jobject accountInfoObj = NULL;
// The constructor ID of the accountInfoCls
jmethodID ctorID = NULL;
// Pointer to the interface for the ClsAccount COM object
_clsAccount *pClsAccount = NULL;
HRESULT hr;
BSTR bstrIniFileName(L"");
try
{
const char *nativeAccountNumber = NULL;
if (accountNumber != NULL)
{
nativeAccountNumber = env->GetStringUTFChars(accountNumber, &isCopy);
}
else
{
jclass newExcCls;
env->ExceptionDescribe();
env->ExceptionClear();
newExcCls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(newExcCls, "accountNumber passed in was null !");
return NULL;
}
// Initialization
_variant_t varConnectionSucceeded = _variant_t(false);
_variant_t varGetAccountInfoSucceeded = _variant_t(false);
_variant_t varAccountNumber = _variant_t(nativeAccountNumber);
_bstr_t bstrLastPaymentDate = _bstr_t();
_bstr_t bstrLastErrorMessage = _bstr_t();
_bstr_t bstrLastErrorNumber = _bstr_t();
jlong jTotalDue = NULL;
jlong jEstablishedDueDay = NULL;
jlong jLastPaymentAmount = NULL;
jstring jLastPaymentDate = NULL;
jstring jLastErrorMessage = NULL;
jstring jLastErrorNumber = NULL;
jthrowable jException = NULL;
const char *chLastPaymentDate = NULL;
const char *chLastErrorMessage = NULL;
const char *chLastErrorNumber = NULL;
long long totalDue;
long long lastPaymentAmount;
long establishedDueDateDay;
//Convert string from Java string to C string to VB string
const char *nativeIniFileName = NULL;
if (iniFileName != NULL)
{
nativeIniFileName = env->GetStringUTFChars(iniFileName, &isCopy);
}
else
{
jclass newExcCls;
env->ExceptionDescribe();
env->ExceptionClear();
newExcCls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(newExcCls, "iniFileName passed in was null");
return NULL;
}
bstrIniFileName = _com_util::ConvertStringToBSTR(nativeIniFileName);
CoInitialize(NULL);
// Create an instance of the COClass with the interface over it
hr = CoCreateInstance(__uuidof(clsAccount), NULL, CLSCTX_INPROC_SERVER, __uuidof(_clsAccount), (void *)&pClsAccount);
if (hr == S_OK)
{
varConnectionSucceeded.boolVal = pClsAccount->OpenConnection(&bstrIniFileName);
if (varConnectionSucceeded.boolVal == -1)
{
varGetAccountInfoSucceeded.boolVal = pClsAccount->GetAccountPaymentInformation(&(varAccountNumber.GetVARIANT()));
env->ReleaseStringUTFChars(accountNumber, nativeAccountNumber);
// Extract all available account information from the ClsAccount object
if (varGetAccountInfoSucceeded.boolVal == -1)
{
totalDue = pClsAccount->TotalDue.int64;
establishedDueDateDay = pClsAccount->EstablishedDueDateDay;
lastPaymentAmount = pClsAccount->LastPaymentAmount.int64;
bstrLastPaymentDate = pClsAccount->LastPaymentDate;
chLastPaymentDate = _com_util::ConvertBSTRToString(bstrLastPaymentDate.GetBSTR());
jTotalDue = (jlong)totalDue;
jEstablishedDueDay = (jlong)establishedDueDateDay;
jLastPaymentAmount = (jlong)lastPaymentAmount;
jLastPaymentDate = env->NewStringUTF(chLastPaymentDate);
delete[] chLastPaymentDate;
}
pClsAccount->CloseConnection();
}
// Populate error fields if any errors occur
bstrLastErrorMessage = pClsAccount->LastErrMessage;
chLastErrorMessage = _com_util::ConvertBSTRToString(bstrLastErrorMessage.GetBSTR());
bstrLastErrorNumber = pClsAccount->LastErrNumber;
chLastErrorNumber = _com_util::ConvertBSTRToString(bstrLastErrorNumber.GetBSTR());
jLastErrorMessage = env->NewStringUTF(chLastErrorMessage);
jLastErrorNumber = env->NewStringUTF(chLastErrorNumber);
delete[] chLastErrorMessage;
delete[] chLastErrorNumber;
const char* clsName = "com/abcco/CustomerAccountInfo";
// Find the Java class and the ID of its constructor
accountInfoCls = env->FindClass(clsName);
ctorID = env->GetMethodID(accountInfoCls, "<init>", "(JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jException = env->ExceptionOccurred();
if (jException != NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
}
//Release all resources associated with the ClsAccount instance
pClsAccount->Release();
//Instantiate the class with the given parameters
accountInfoObj = env->NewObject(accountInfoCls, ctorID, jTotalDue, jEstablishedDueDay, jLastPaymentAmount, jLastPaymentDate, jLastErrorMessage, jLastErrorNumber);
jException = env->ExceptionOccurred();
if (jException != NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
}
}
else if (hr == REGDB_E_CLASSNOTREG)
{
cout << "COM class not registered" << endl;
}
else if ( hr == CLASS_E_NOAGGREGATION)
{
cout << "COM class can't be aggregated" << endl;
}
else if (hr == E_NOINTERFACE)
{
cout << "No interface for COM class clsAccount" << endl;
}
else if (hr == E_POINTER)
{
cout << "*ppv pointer was NULL !" << endl;
}
else
{
cout << "Error occurred while creating COM object. HR is [" << hr << "]" << endl;
}
// Free the BSTR because a new one was returned with a call to _com_util::ConvertStringToBSTR
SysFreeString(bstrIniFileName);
env->ReleaseStringUTFChars(iniFileName, nativeIniFileName);
CoUninitialize();
}
catch (_com_error &e)
{
cout << "Encountered an exception in GetCustomerAccountInfo: Error was " << e.ErrorMessage();
pClsAccount->Release();
}
catch (...)
{
pClsAccount->Release();
}
return accountInfoObj;
}
JVM Crash Excerpt:
--------------- T H R E A D ---------------
Current thread (0x49215000): JavaThread "http-8080-Processor22" daemon [_thread_in_native, id=1536, stack(0x4a2d0000,0x4a320000)]
siginfo: ExceptionCode=0xc0000005, reading address 0x4af9984d
Registers:
EAX=0x000901a0, EBX=0x00090000, ECX=0x7ff97000, EDX=0x00090608
ESP=0x4a31dc80, EBP=0x4a31de9c, ESI=0x4af99848, EDI=0x00000005
EIP=0x7c82bc18, EFLAGS=0x00010216
Top of Stack: (sp=0x4a31dc80)
0x4a31dc80: 4a9f3324 00000000 00000000 4a9f0000
0x4a31dc90: 7c82a0fc 4a9f0650 4a9f0660 00000000
0x4a31dca0: 777964d0 0009c97c 776a928c 00000000
0x4a31dcb0: 4a9f3348 00000000 00001000 4a31defc
0x4a31dcc0: 00000008 777964d0 4a31dfb8 000ae9c4
0x4a31dcd0: 0009c97c 00000000 4a9f0178 00000000
0x4a31dce0: 00000000 4a9f0608 006d0061 00460020
0x4a31dcf0: 4a9f3310 4a9f0178 0043005c 4a9f32f8
Instructions: (pc=0x7c82bc18)
0x7c82bc08: ff ff e9 83 ea ff ff 8b 70 04 83 ee 08 89 75 cc
0x7c82bc18: 8a 46 05 88 45 e3 8d 4e 08 8b 39 89 bd 4c fe ff
Stack: [0x4a2d0000,0x4a320000], sp=0x4a31dc80, free space=311k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [ntdll.dll+0x2bc18]
C [ntdll.dll+0x339e1]
C [kernel32.dll+0x23eee]
C [MSDART.DLL+0xb723]
C [MSDART.DLL+0x8607]
C [msado15.dll+0x1757]
C [msado15.dll+0x68de6]
C [msado15.dll+0x655a3]
C [ole32.dll+0x3d8ac]
C [ole32.dll+0x3af7e]
C [ole32.dll+0x3d9b6]
C [ole32.dll+0x3d92d]
C [ole32.dll+0x3cb27]
C [ole32.dll+0x3cad8]
C [ole32.dll+0x3da17]
C [ole32.dll+0x3af7e]
C [ole32.dll+0x3af19]
C [ole32.dll+0x3af7e]
C [ole32.dll+0x3b10f]
C [ole32.dll+0x3679a]
C [ole32.dll+0x36762]
C [ole32.dll+0x36963]
C [oledb32.dll+0x2b440]
C [oledb32.dll+0x17875]
C [msado15.dll+0x1fd6c]
C [msado15.dll+0x1fc91]
C [msado15.dll+0x1ff13]
C [msado15.dll+0x20270]
C [msado15.dll+0x4dff]
C [MIGI.DLL+0x3813]
C [CustomerInfoProxy.dll+0x3099]