JVM Hosting
Chabster Wednesday, January 2, 2008 C++ & Java & skeleton

Хостинг усправляемого кода в программных продуктах уже не редкость. Oracle поддерживает выполнение байткода JVM еще с 8-й версии и периодически выполняет апгрейд виртуальной машины (11g поставляется с версией 1.5). Сравнительно недавно добавили поддержку .NET 1.1 и .NET 2.0. Microsoft SQL Server 2005 имеет поддержку CLR 2.0 и, кстати, внутренняя архитектура последнего была усовершенствована (скорее "заточена") под задачи хостинга. Дальше я расскажу, как добавить возможность выполнять JVM-код в приложение, написанное на С++.
Подготовка проекта для хостинга виртуальной машины Java.

Первое, что необходимо сделать, - поставить Java Development Kit версии 6. Далее в настройках проекта прописать папку поиска заголовочных файлов JDK - "C:\Program Files\Java\jdk1.6.0_03\include\", папку с библиотечными файлами - "C:\Program Files\Java\jdk1.6.0_03\lib" и добавить путь поиска jvm.dll - C:\Program Files\Java\jdk1.6.0_03\jre\bin\client\ как написано здесь. Еще нужно закинуть файл "c:\Program Files\Java\jdk1.6.0_03\include\win32\jni_md.h" в папку с проектом и включить его в проект. Традиционно изменяем stdafx.h:

#include <jni.h>
#pragma comment(lib, "jvm.lib")
Инициализация виртуальной машины Java.

Перед использованием, виртуальную машину Java необходимо загрузить и инициализировать. По-сути, она является песочницей (sandbox) для выполняемого байт-кода, другими словами, мини-операционной системой в пределах хост-процесса. Инициализация предполагает создание управляющих структур, выделение пулов памяти и других ресурсов ОС, связанных с конкретным екземпляром JVM (да, их можно создать несколько).

jint jRet;

JavaVMOption options[1];
options[0].optionString = "-verbose:class,gc,jni";

JavaVMInitArgs jvmInitArgs;
jvmInitArgs.version = JNI_VERSION_1_6;
jvmInitArgs.nOptions = _countof(options);
jvmInitArgs.options = options;
jvmInitArgs.ignoreUnrecognized = JNI_TRUE;

JavaVM *pJvm;
JNIEnv *pEnv;
jRet = JNI_CreateJavaVM(&pJvm, reinterpret_cast<LPVOID*>(&pEnv), &jvmInitArgs);
if (jRet != JNI_OK) {
   // Это залет, солдат!
   cout << "JNI_CreateJavaVM returned " << jRet << endl;
   return(jRet);
}

Структура JavaVMInitArgs содержит настройки создания екземпляра JVM - запрашиваемую версию и различные конфигурационные параметры. Функция JNI_CreateJavaVM, как нетрудно догадаться из ее названия, создает виртуальную машину и возвращает указатель на управляющий интерфейс - JNIEnv. Любые дальнейшие манипуляции выполняются посредством него.
Выполнение байт-кода JVM.

Не буду оригинален. Вашему вниманию представляется вариант "Hello World" в исполнении C++/Java:

void printHelloWorld(JNIEnv *pEnv) {
   jclass classSystem = pEnv->FindClass("java/lang/System");
   jfieldID fieldId = pEnv->GetStaticFieldID(classSystem, "out", "Ljava/io/PrintStream;");
   jobject system_out = pEnv->GetStaticObjectField(classSystem, fieldId);

   jclass classPrintStream = pEnv->FindClass("java/io/PrintStream");
   jmethodID methodId = pEnv->GetMethodID(classPrintStream, "println", "(Ljava/lang/String;)V");

   jvalue v;
   v.l = pEnv->NewStringUTF("Hello World!");
   pEnv->CallVoidMethodA(system_out, methodId, &v);
   if (jthrowable ex = pEnv->ExceptionOccurred()) {
      pEnv->ExceptionDescribe();
      pEnv->ExceptionClear();
   }
}
Завершение работы.

По завершению работы с JVM рантайм нужно удалить:

jRet = pJvm->DestroyJavaVM();
if (jRet != JNI_OK) {
   cout << "pJvm->DestroyJavaVM() returned " << jRet << endl;
   return(jRet);
}
TOKEN_PRIVILEGES oldTokenPrivileges = { 0 };

HANDLE impersonationToken = NULL;
HANDLE userToken = NULL;

LPVOID pEnvironment = NULL;
PROCESS_INFORMATION processInformation = { 0 };

__try {
    bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    // This step might not be necessary because SeTcbPrivilege is enabled by default for Local System
    LUID luid;
    bRet = LookupPrivilegeValue(NULL, _T("SeTcbPrivilege"), &luid);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    TOKEN_PRIVILEGES adjTokenPrivileges = { 0 };
    adjTokenPrivileges.PrivilegeCount = 1;
    adjTokenPrivileges.Privileges[0].Luid = luid;
    adjTokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    DWORD dwOldTPLen;
    bRet = AdjustTokenPrivileges(processToken, FALSE, &adjTokenPrivileges, sizeof(TOKEN_PRIVILEGES), &oldTokenPrivileges, &dwOldTPLen);
    if (bRet) {
        hr = GetLastError();
        if (hr == ERROR_SUCCESS);
        else if (hr == ERROR_NOT_ALL_ASSIGNED) {
            // Enabled by default
        }
    }
    else {
        hr = GetLastError();
        return hr;
    }

    DWORD conSessId = WTSGetActiveConsoleSessionId();
    if (conSessId == 0xFFFFFFFF) {
        // There is no session attached to the console
        return ERROR_SUCCESS;
    }

    bRet = WTSQueryUserToken(conSessId, &impersonationToken);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    bRet = DuplicateTokenEx(impersonationToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &userToken);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    STARTUPINFO si = { 0 };
    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = _T("winsta0\\default");

    bRet = CreateEnvironmentBlock(&pEnvironment, userToken, TRUE);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }

    bRet = CreateProcessAsUser(userToken, _T("C:\\Windows\\notepad.exe"), NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &processInformation);
    if (!bRet) {
        hr = GetLastError();
        return hr;
    }
}
__finally {
    if (processInformation.hThread) {
        CloseHandle(processInformation.hThread);
    }
    if (processInformation.hProcess) {
        CloseHandle(processInformation.hProcess);
    }
    if (pEnvironment) {
        bRet = DestroyEnvironmentBlock(pEnvironment);
    }
    if (userToken) {
        CloseHandle(userToken);
    }
    if (impersonationToken) {
        CloseHandle(impersonationToken);
    }
    if (processToken) {
        bRet = AdjustTokenPrivileges(processToken, FALSE, &oldTokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
        CloseHandle(processToken);
    }
}

Код функций состоит исключительно из ассемблерных инструкций, поэтому они объявлены, как __declspec(naked). Конвенция вызова __fastcall подарит нам единственный аргумент (адрес состояния блокировки) в регистре ecx. xchg - атомарная инструкция, которая в нашем случае меняет местами значение в регистре eax (где всегда хранится единица) и по адресу памяти, который хранится в ecx. В результате выполнения одной неделимой инструкции, мы получим в eax нолик, если блокировка успешно захвачена. Состояние блокировки изменится соответственно. Если блокировка занята, в eax будет находится единица. Оставшиеся инструкции проверяют регистр eax и реагируют на успех или провал захвата блокировки.

Вы, наверное, не заметили, что в коде используется не xchg, а lock xchg. Это потому, что у меня многопроцессорная машина и две инструкции xchg могут выполнятся одновременно. Чтобы их синхронизировать, применяется конструкция lock, которая заставляет блокировать шину памяти на время выполнения инструкции.
Использование spinlock

Спин блокировку следует использовать исключительно:
Для синхронизации действительно маленьких кусков исходного кода.

Поток, ждущий освобождения блокировки, выполняет бесполезную работу, соответственно, расходует процессорное время. Основная цель - потребить меньше ресурсов, чем потребуется для использования примитивов синхронизации операционной системы, тоесть для переключения контекста выполнения.
На многопроцессорной машине.

Поскольку на однопроцессорной машине два потока не могут выполнятся одновременно, поток, который владеет блокировкой, во время попыток ее захвата другим потоком приостановлен и не выполняет никаких действий по освобождению ресурса. Вообще, можно добиться отсутствия переключений контекста потока в момент, когда блокировка захвачена. Но, это очень сложно контролировать.
Proof of concept

Следующим кодом я проверял корректность работы spinlock:

// CppTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>

__declspec(naked) void __fastcall SpinLock(int *cookie) {
    __asm {
        mov eax, 1
spinLoop:
        lock xchg eax, [ecx]
        test eax, eax
        jnz spinLoop
        ret
    }
}

__declspec(naked) void __fastcall SpinUnlock(int *cookie) {
    __asm {
        mov eax, 0
        lock xchg eax, [ecx]
        ret
    }
}

int lockCookie = 0;
int i = 0;
int j = 0;

DWORD WINAPI Thread1(LPVOID lpParameter) {
    while (true) {
        SpinLock(&lockCookie);
        i++;
        j++;
        if (i != j)
            DebugBreak();
        SpinUnlock(&lockCookie);
    }
}

DWORD WINAPI Thread2(LPVOID lpParameter) {
    while (true) {
        SpinLock(&lockCookie);
        i++;
        j++;
        if (i != j)
            DebugBreak();
        SpinUnlock(&lockCookie);
    }
}

int _tmain(int argc, _TCHAR* argv[])
{

    InterlockedIncrement(reinterpret_cast<volatile LONG*>(&argc));

    DWORD thread1Id;
    DWORD thread2Id;

    HANDLE hThread1 = CreateThread(NULL, 100, Thread1, NULL, 0, &thread1Id);
    HANDLE hThread2 = CreateThread(NULL, 100, Thread2, NULL, 0, &thread2Id);

    Sleep(INFINITE);

    return(0);
}
Кстати, глянув на ассемблерный код функции InterlockedIncrement я убедился, что на процессорах Core 2 Duo нужно использовать блокировку шины памяти. Как и на многопроцессорных станциях.
Зачем нужна операционная система, если блокировки так элементарно пишутся вручную?!

А затем, что ассемблерный код не является переносимым даже в пределах одной архитектуры! Скажем, будь у меня однопроцессорная машина, я бы не использовал конструкцию lock и мой код бы не работал корректно на двуядерном процессоре! Кстати, просматривая исходники Windows 2000 я заметил, что реализация всех Interlocked операций завязана на архитектуре и мультипроцессорности. Отсюда и непереносимость утановленной винды на другую платформу. Версий ядра то несколько!
2 comments
Type system covariance and contravariance
Chabster Thursday, January 17, 2008 C# & C++ & Java & programming languages

Полагаю, многие разработчики не знают, что такое ковариантность/контравариарность системы типов. К примеру, в версии 1.5 языка Java появилась возможность использовать тип класса-наследника в объявлении замещающей функции:

 0: /**
 1:  *
 2:  */
 3: package com.chabster.covariance;
 4:
 5: /**
 6:  * @author Chabster
 7:  */
 8: public class Container
 9: {
10:   class A
11:   {
12:      A some() {
13:         return (this);
14:      }
15:   }
16:
17:   class B extends A
18:   {
19:      @Override
20:      B some() {
21:         return (this);
22:      }
23:   }
24:
25: }

В С++ такая возможность существует уже давно, а вот в C# все еще отсутствует. Полагаю, это связано с нестыковкой сигнатур методов в C# и IL. Вот, что говорит по этому поводу Standard ECMA-335 Common Language Infrastructure (CLI) 4th edition (June 2006):
"

A method signatures is composed of
a calling convention,
the number of generic parameters, if the method is generic,
a list of zero or more parameter signatures—one for each parameter of the method—and,
a type signature for the result value, if one is produced.
"

Получается, что IL код различает методы, которые отличаются лишь типом возврата, а C# - нет. Как и любой существующий на данный момент управляемый .NET-язык. С другой стороны этот недостаток можно элементарно восполнить. Вариант реализации предлогаю расценивать, как домашнее задание для читателя :).

В некоторых моментах C#, все же, поддерживает ковариантность и контравариантнось. Но для начала выясним что это такое.
Ковариантность (covariance) и контравариантнось (contravariance)

Ковариантностью называют сохранение формы при преобразовании. Соответсвенно, ковариантным называется преобразование, сохраняющее форму (свойства).

Например, оператор F(x) = x*2 является ковариантным касательно отношения % (делимость). Т.е. из x%y следует, что F(x)%F(y).

Контравариантностью называют обращение формы при преобразовании. Соответсвенно, контравариантным называется преобразование, обращающее форму.

Например, оператор F(x) = -x является контравариантным касательно отношения > (больше). Т.е. из x>y следует, что F(x)<F(y).

Оба этих понятия нельзя правильно обобщить поскольку в каждой науке оно выглядит по-своему. Рассмотрим их в самой интересной для нас области - программирование!
Ковариантность и контравариантнось в системе типов языков программирования

Рассмотрим пример C# кода:

class A {
    A[] aArr = new B[] { };
}

class B : A {
}

Пусть « обозначает отношение "is a" для типов. Поэтому B«A. Оператор Arrize({T}) = {T[]} является ковариантным касательно отношения "is a" для ссылочных типов т.к. из B«A следует B[]«A[].

Возвращаясь к первому Java примеру можно сказать, что оператор Methodize({SuperT virtMethod(T1,T2,...)}) = {SubT virtMethod(T1,T2,...)} является ковариантным касательно отношения override между методами.
Ковариантность и контравариантнось в системе типов C#

В C# есть ковариация, связанная с делегатами:

class A {
}

class B : A {
}

delegate A SomeDelegate();

static B SomeMethod() { return (null); }

static SomeDelegate sd = SomeMethod;

Типы возвращаемых значений метода и делегата различны, но совместимы.

В C# есть и контравариация, связанная с делегатами:

class A {
}

class B : A {
}

delegate void SomeDelegate(B b);

static void SomeMethod(A a) { }

static SomeDelegate sd = SomeMethod;

Сигнатуры метода и делегата различны, но совместимы, правда, уже в обратном направлении.

Это все, конечно, хорошо, вот только...

class A {
}

class B : A {
}

delegate T SomeDelegate<T>();

static B SomeMethod() { return (null); }

static SomeDelegate<B> sdB = SomeMethod;

static SomeDelegate<A> sdA = sdB;

... последняя строчка вызывает ошибку компиляции.

В целом, вариантность - очень полезный механизм, если им правильно пользоваться.

********
Действительно качественные карбоновые чехлы для iPhone, которые будут отлично гармонировать с внешностью телефона от Apple, вы можете обнаружить на сайте Comunicado.ru

Если вы хотите купить определенный товар через интернет в Ижевске, но не знаете какие интернет-магазины занимаются его продажей, вам на выручку придет портал интернет новостей г.Ижевска, располагающийся по адресу Wmizhevsk.ru.

Если вас заботит то, какой будет погода в Турции в ближайшем будущем – рекомендую навестить сайт weather.turmir.com.

google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru
  • Интересная штука- индексируемые свойства С# и Pascal(Delphi)
  • OLTP первые проблемы
  • JSF - сформировать в бине HtmlDataTable
  • Оставить комментарий