Как я вставлял Java 7 в работающее приложение, и что пришлось изобрести для С runtime

в 20:39, , рубрики: c++, java, jni, windows, Программирование, метки: , , ,

image

Однажды, в студеную зимнюю пору, в одно старое приложение на С++, успешно работавшее до этого годами, пришлось вставить новый Java runtime 7 вместо отлично работавшего Java 6. Ничто не предвещало, и вдруг.

Код в общем-то очевиден и прост — немного JNI, и вот он — больной зуб. Приведу код только для Windows, т.к. чудеса эквилибристики под маком не потребовались.

Как это было

Когда-то, много лет назад, мы создали простенькую DLL, слинкованную с jvm.dll стандартным способом, экспортировали в ней две с половиной функции, и «дергали» ее динамически. Сей финт ушами очевиден — нам ведь еще надо найти где сама Java расположилась. Вызывали эту простенькую proxy примерно так

bool LoadJavaEngine(HANDLE& engHandle)
{
	bool loadResult = false;
	do
	{

		... // here we have auto variables, nothing interesting

		// first we need to find java
		CRegKey rk;
		if(rk.Open(HKEY_LOCAL_MACHINE, 
			L"SOFTWARE\JavaSoft\Java Runtime Environment\1.6", 
			KEY_QUERY_VALUE) !=
			ERROR_SUCCESS || rk.QueryValue(strBuf, L"RuntimeLib", &strBufSz) != ERROR_SUCCESS) break;

		WCHAR* backslashAddr = wcsrchr(strBuf, L'\');
		if(backslashAddr) BufLen = backslashAddr - strBuf;
		strBuf[BufLen] = L'';

		// now C runtime knows where is jvm.dll located
		retnPWD = SetDllDirectoryW(strBuf);

		// ... nothing intersting - in the same manner we're looking for our proxy path in registry

		if((engHandle = LoadLibrary(strBuf))==NULL)	break;

		// now try to initialize JVM by calling our proxy linked with jvm.dll
		CreateJavaVMFunPtr CreateJavaVM = 
			(CreateJavaVMFunPtr)GetProcAddress(engHandle, "CreateJavaVM");
		if(CreateJavaVM) loadResult = CreateJavaVM(needJNIProxy);
	}
	while(false);
	return loadResult;
}

Неожиданные грабли на ровном месте

Как видно, код чрезвычайно прост и в общем-то занимает аж три строчки

	SetDllDirectory
	LoadLibrary
	GetProcAddress

А теперь — усложняем. С Java 6 переходим на Java 7. Оптимистичный прогноз был таким — поменяем одну строчку, и все работает.

Меняем

		if(rk.Open(HKEY_LOCAL_MACHINE, 
			L"SOFTWARE\JavaSoft\Java Runtime Environment\1.6", 

на

		if(rk.Open(HKEY_LOCAL_MACHINE, 
			L"SOFTWARE\JavaSoft\Java Runtime Environment\1.7", 

и наступает знаменитый литовский праздник «обломайтис» Path not found. Не желает загружать нашу простенькую proxy.

Сеанс телепатии

В ходе полуторачасовых разбирательств, выяснилось:

  1. Предыдущая версия основной программы и наш плагин на java к ней были собраны вижуалстудиями от 2003 до 2008 (MSVCR80.dll, MSVCR90.dll и т. д.), а Java 6 соответственно — раритетом с MSVCR71.dll.
  2. Новая Java собрана с использованием MSVCR100.dll (ага, ага). А версия программы у заказчика — использует MSVCR90.dll. Вот и не загружает.

Гугление вариантов не дало — прописывание и удаление манифестов дало ноль на массу с тем же результатом.

Камлание на бубне

image
И тогда был вытащен из кармана большой напильник, и родили очередное чудовище подпорок. И вместо одной SetDllDirectory получилось вот что:

		wcscat(strBuf, L"\msvcr100.dll");
		test = LoadLibraryW(strBuf);
		strBuf[BufLen] = L'';

		wcscat(strBuf, L"\client\jvm.dll");
		test = LoadLibraryW(strBuf);
		if(test == 0) {
			strBuf[BufLen] = L'';
			wcscat(strBuf, L"\server\jvm.dll");
			test = LoadLibraryW(strBuf);
		}

		strBuf[BufLen] = L'';
		retnPWD = SetDllDirectoryW(strBuf);

То есть если руками подгрузить dependencies начиная от MSVCR100.dll — то находит и работает. А иначе — увы.

Disclaimer: вариант «а вы поставьте заказчику версию поновее» не подходит. Основное приложение — это Adobe InDesign Server CS5, за $7k за лицензию.

Подробности про колючую проволоку можно прочитать в предыдущих публикациях на тему

http://habrahabr.ru/post/122746/
http://habrahabr.ru/post/127574/

Автор: viklequick

* - обязательные к заполнению поля