ringill's Journal
 
[Most Recent Entries] [Calendar View] [Friends]

Below are 20 journal entries, after skipping by the 20 most recent ones recorded in ringill's LiveJournal:

    [ << Previous 20 -- Next 20 >> ]
    Friday, December 8th, 2006
    4:03 pm
    [.NET] Вызов низкоуровневого кода из CLR
    Речь идёт о вызове низкоуровневой функции, принадлежащей динамической библиотеке (DLL), из управляемого кода (на C#, например). Есть несколько способов это сделать:

    1. P/Invoke — наиболее распространённый способ. Прототип вызываемой функции описывается в коде на C# с  указанием директивы DllImport, и вызывается элементарно:
      [DllImport("mylib.dll")]
      static public extern int MyFunc (int param1, int param2);
      
      static void Main ()
      {
        int res = MyFunc(10, 20);
      }
      

      Это работает медленно. Я не знаю, почему. Если такие вызовы редки — ничего
      страшного, но если их миллионы (а сама вызываемая функция проста), то избыточность
      становится заметна.
      UPDATE: На современной .NET уже быстро, поэтому всё последующее можно не читать.



    2. Чтобы вызывать функцию из DLL миллионы раз, достаточно один раз получить её адрес. Процесс вызова прост:
      кладём аргументы на стек, кладём адрес функции на стек, делаем calli и ret.
      Получить адрес можно вызовом GetProcAddress, через DllImport из kernel32.dll. Медленно, но всего один раз на миллион вызовов.

      Для работы со стеком напрямую можно задействовать MSIL, скомпилировав код
      на этом языке в отдельную сборку. Это товарищ Neil Cowburn догадался.
      Вот пример: делаем файл FunctionCaller.il
      .assembly extern mscorlib {}
      .assembly FunctionCaller {}
      
      .class public FunctionCaller
      {
        .method public static int32 IntMethod_Int_Int (native int pfn, int32 param1, int32 param2) 
        { 
          .maxstack 3
          ldarg.1 // для передачи по ссылке следует использовать ldarga
          ldarg.2
          ldarg.0
          calli unmanaged stdcall int32 (int32, int32) 
          ret 
        } 
      }
      

      Компилируем его командой ilasm.exe /dll FunctionCaller.il, и FunctionCaller.dll подключаем
      в проект как Reference. А вот код на C#:
      [DllImport("kernel32.dll")]
      static extern IntPtr LoadLibrary (string file_name);
      
      [DllImport("kernel32.dll")]
      static extern IntPtr GetProcAddress (IntPtr module, string proc_name);
      
      [DllImport("kernel32.dll")]
      static extern bool FreeLibrary (IntPtr module);
      
      static void Main ()
      {
        IntPtr lib = LoadLibrary("mylib.dll");
        IntPtr myfunc_ptr = GetProcAddress(lib, "MyFunc");
        
        int res = FunctionCaller.IntMethod_Int_Int(myfunc_ptr, 10, 20);
        
        FreeLibrary(lib);
      }
      

    3. Указанный код для работы со стеком можно конструировать на этапе выполнения, пользуясь
      механизмами System.Reflection. Полноценный пример приведён в microsoft.public.dotnet.languages.csharp.
      Однако, там используется вызов MethodInfo.Invoke, который хоть и быстрее, чем P/Invoke, но тоже избыточен.

      Для преодоления его избыточности есть приём —
      создание делегата с помощью CreateDelegate. Вот как это делается:
      delegate int MyFuncInvoker (int param1, int param2);
      
      static void Main ()
      {
        IntPtr lib = LoadLibrary("mylib.dll");
        IntPtr myfunc_ptr = GetProcAddress(lib, "MyFunc");
      
        AssemblyName assembly_name = new AssemblyName("Invokable");
        AssemblyBuilder assembly_builder =
           AppDomain.CurrentDomain.DefineDynamicAssembly(assembly_name, AssemblyBuilderAccess.Run);
        ModuleBuilder module_builder = assembly_builder.DefineDynamicModule("CInvoker");
      
        // тип возвращаемого значения и типы параметров нашей функции
        Type return_type = typeof(int);
        Type[] parameter_types = new Type[] { typeof(int), typeof(int) };
      
        DynamicMethod dynamic_method = new DynamicMethod("Invoker",
           MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
           return_type, parameter_types, module_builder, false);
      
        // начинаем конструировать код, аналогичный вышеприведённому на MSIL
        ILGenerator il_generator = dynamic_method.GetILGenerator();
        
        // положить параметры на стек; опять же, для передачи по ссылке следует использовать Ldarga
        il_generator.Emit(OpCodes.Ldarg, 0);
        il_generator.Emit(OpCodes.Ldarg, 1);
      
        // положить адрес функции на стек
        il_generator.Emit(OpCodes.Ldc_I4, myfunc_ptr.ToInt32());
        // (для 64-битных систем следует написать (OpCodes.Ldc_I8, myfunc_ptr.ToInt64())
      
        // сделать calli и ret
        il_generator.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, return_type, parameter_types);
        il_generator.Emit(OpCodes.Ret);
        
        // код сконструировали; теперь создаём делегат
        MyFuncInvoker myfunc_invoker = (MyFuncInvoker)dynamic_method.CreateDelegate(typeof(MyFuncInvoker));
      
        int res = myfunc_invoker(10, 20);
        
        FreeLibrary(lib);
      }


    Сравнительное время миллиона вызовов пустой функции
    P/Invoke

    ~5 сек.

    Код от ILGenerator, вызванный
    через MethodInfo.Invoke()

    ~3.7 сек.

    Код от ILGenerator, вызванный
    через DynamicMethod.CreateDelegate()
    ~0.5 сек.

    Вызов через отдельную сборку на MSIL

    ~0.5 сек.




    Вызовы двумя последними способами происходят быстрее в десять раз.
    Чем они отличаются между собой? Первый лучше тем, что не требует добавлять в проект отдельную сборку.
    Wednesday, November 22nd, 2006
    6:29 pm
    SBCL for Windows
    Был приятно удивлён тем, что SBCL (реализация Common Lisp) для Windows уже выложена на официальном сайте, и даже работает.
    Из пакетов в дистрибутив включены только asdf и пачка специфичных sb-***.

    Поддерживается сохранение образа в исполняемый файл (stand-alone executable):
    C:\>sbcl --noinform
    
    This is experimental prerelease support for the Windows platform: use
    at your own risk.  "Your Kitten of Death awaits!"
    * (defun main () (print 'hello) (sb-ext:quit))
    
    MAIN
    * (sb-ext:save-lisp-and-die "prog.exe" :toplevel (lambda () (main) 0) :executable t)
    [undoing binding stack and other enclosing state... done]
    [saving current Lisp image into C:\\prog.exe:
    ... ...
    done]
    
    C:\>prog
    This is SBCL 0.9.18 [...]
    HELLO
    Wednesday, November 1st, 2006
    8:32 pm
    [Win32] Multi-threaded wrapper for single-threaded DLL
    Если так случилось, что ваша C или C++-библиотека неспособна корректно работать, будучи вызываема параллельно из нескольких процессов или тредов, и переписывать её нет времени, то можно создать для неё специальную обёртку. Идея в том, что в обёртке есть тот же набор процедур, что и в библиотеке, но все они получают один дополнительный параметр -- идентификатор сессии (процесса или треда), из которой делается вызов.

    Как известно, стандартная функция LoadLibrary() загружает библиотеку из DLL-файла и возвращает указатель (handle) на загруженный экземпляр. Этот указатель потом можно передавать в GetProcAddress(), чтобы получать адрес процедур, которые требуется вызвать.

    Всё бы хорошо, но LoadLibrary(), во-первых, требует, чтобы DLL-файл непременно находился на диске, и во-вторых, при попытке загрузить его вторично ничего не сделает и вернёт тот же handle. Если бы не эта неприятность, то LoadLibrary() вполне бы подошла для написания обёртки.

    Товарищ Joachim Bauch героически исследовал, что же происходит внутри LoadLibrary() и написал свой аналог, под названием MemoryModule. В его библиотеке есть вызов MemoryLoadLibrary(), который, во-первых, загружает DLL не из файла, а из оперативной памяти, а во-вторых, может загружать одну и ту же DLL в любом количестве экземпляров, и возвращает каждый раз новый handle. Для получения адресов загруженных процедур есть вызов MemoryGetProcAddress().

    Код обёртки в упрощённом варианте выглядит так:
    #include <hash_map>
    #include "MemoryModule.h"
    
    static stdext::hash_map<int, HMEMORYMODULE> modules;
    static char *dll_buffer = 0;
    
    // Загружает содержимое DLL-файла в память
    extern "C" __declspec(dllexport) int Init (char *file_name)
    {
       FILE *file = fopen(file_name, "rb");
    
       if (file == NULL)
             return 0;
    
       fseek(file, 0, SEEK_END);
       int size = ftell(file);
       dll_buffer = new char[size];
       fseek(file, 0, SEEK_SET);
       fread(dll_buffer, 1, size, file);
       fclose(file);
       return 1;
    }
    
    // Возвращает handle для библиотеки, загруженной для заданной сессии
    static HMEMORYMODULE _getModuleHandle (int id)
    {
       if (modules.find(id) == modules.end())
          // если для этой сессии раньше не загружали, то загрузить
          modules[id] = MemoryLoadLibrary(dll_buffer);
    
       return modules[id];
    }
    
    // Получает идентификатор вызвавшей сессии и параметр для MyFunction
    extern "C" __declspec(dllexport) int MyFunctionWrapper (int id, int param)
    {
       HMEMORYMODULE module = _getModuleHandle(id);
       int (*my_func)(int) = (int (*)(int))MemoryGetProcAddress(module, "MyFunction");
    
       return my_func(param);
    }
    Код совершенно условен; я даже не пытался его скомпилировать.
    Tuesday, October 31st, 2006
    8:20 pm
    Low-level stored procedures in Oracle and SQL Server 2005
    Речь идёт о процедурах, хранящихся в базе данных и написанных не на SQL.
    Они делятся на два класса:

    1) «Внутренние» -- исполняемые непосредственно сервером базы данных.

    2) «Внешние» -- исполняемые операционной системой, на которой установлен сервер.



    Внутренние хранимые процедуры более безопасны и (по крайней мере, в Oracle) быстрее могут обмениваться данными с сервером. Внешние, зато, имеют доступ непосредственно к операционной системе (в обход сервера), и, само собой, их можно писать на C/C++.



    Поддержка хранимых процедур в Oracle и SQL Server


    Oracle

    MS SQL Server
    2000

    MS SQL Server
    2005

    Внутренние PL/SQL, Java

    Transact SQL
    (T-SQL)

    T-SQL, .NET
    assemblies

    Внешние

    Есть
    (на каждую сессию загружаются отдельным процессом под названием extproc)

    Есть. Я не
    пользовался.

    Есть, но только в
    рамках обратной совместимости
    .




    Оказалось, что в SQL Server 2005 можно писать низкоуровневые внешние процедуры на C/C++,
    «оборачивая» их внутренними, работающими в .NET CLR. Для этого нужно
    специальным образом настроить
    сервер, и указать, что обёртка «небезопасная»:



    CREATE ASSEMBLY [name] from [dll file] WITH PERMISSION_SET = UNSAFE;



    Сборкам типа UNSAFE позволено вызывать Win32 API, но не более того: низкоуровневые (unmanaged) вызовы вроде malloc() запрещены, поэтому делать обёртку на C++/CLI не имеет смысла.
    Но пользоваться механизмом P/Invoke разрешено!
    То есть, надо делать обёртку на C# и загружать dll-файл с C или C++-кодом с помощью DLLImport.
    Таким образом, низкоуровневые процедуры будут исполняться в адресном пространстве сервера, и при этом беспрепятственно смогут общаться с ОС. На все пользовательские сессии загружается одна копия библиотеки, т.е. она должна поддерживать multithreaded режим. В Oracle, кстати, наоборот -- на каждую сессию в памяти приходится отдельный экземпляр.



    Кстати, dll-файл, подлежащий импорту, вовсе не обязательно класть в директорию, прописанную в PATH. Здесь написано, как загрузить его из произвольной директории вызовом
    LoadLibrary() из kernel32.dll
    Monday, October 30th, 2006
    5:22 pm
    Работа с Oracle LOB-s в VisualWorks Smalltalk
    Чтобы передать LOB (large object) с клиента в базу данных Oracle, надо сделать три вещи:
    1. Создать временный LOB [OCILobCreateTemporary()].
    2. Записать в него данные [OCILobWrite()].
    3. «Привязать» созданный LOB к запросу INSERT или UPDATE [OCIBindByPos() или OCIBindByName()]

    В стандартном пакете VisualWorks OracleEXDI, в классе OracleSession есть метод bindVariableAsLob. Он выполняет из перечисленных трёх пунктов только последний и, разумеется, не работает.

    Для OCILobCreateTemporary() обёртки нет, но её несложно добавить, по аналогии с существующими, в класс OracleLOBProxy.
    Для записи данных в созданный LOB в классе OracleLOBProxy есть метод writeFrom, но он написан чрезвычайно криво и неэффективно. Я его переписал.
    Соответственно, метод bindVariableAsLob пришлось тоже немного модифицировать.

    Также потребовал правки метод mallocForParameter в классе OracleLargeObjectBuffer. Он выделяет столько дескрипторов для LOB-а, сколько в LOB-е байт, тогда как достаточно одного дескриптора.

    Публикации в виде патча написанное не заслуживает, т.к. содержит только то, что понадобилось мне, и не очень хорошо «вписывается» в структуру OracleEXDI.

    А вот код метода writeFrom:
    writeFrom: startingPosition with: aByteArray
    	| xif conn rtval arraySize amountPointer errorClosure startPos |
    	conn := self session connection.
    	xif := conn class xif.
    	arraySize := aByteArray size.
    	startPos := startingPosition.
    	errorClosure := 
    		[ | errs |
    			errs := conn getErrors.
    			^conn class unableToWriteLobDataSignal raiseWith:
                                    errs errorString: errs first dbmsErrorString.
    		].
    
    	amountPointer := 123 gcCopyToHeap.
    	[arraySize > 0] whileTrue:
    		[
    		amountPointer contents: arraySize.
    		self setBuffer: (aByteArray copyFrom: startPos to: aByteArray size).
    		rtval := self writeLobDataExternalFrom: startPos amount: amountPointer
                                          piece: xif OCI_ONE_PIECE bufferLength: arraySize.
    		(rtval == xif OCI_SUCCESS or:
                       [ (rtval == xif OCI_SUCCESS_WITH_INFO) or:
                         [rtval == xif OCI_NEED_DATA ]] ) ifFalse: [ errorClosure value ].
    		(amountPointer contents == 0) ifTrue: [ errorClosure value ].
    		startPos := startPos + amountPointer contents.
    		arraySize := arraySize - amountPointer contents.
    		].
    
    	^self
    4:52 pm
    COM-Automation в VisualWorks Smalltalk
    Хорошо, но мало: например, не все типы COM VARIANT-ов поддерживаются. Мне, в частности, понадобился тип VT_INT (код 22).
    Насколько я понимаю, обычный для Smalltalk подход при кодировании или раскодировании бинарных данных, представленных в виде блоков с идентификаторами типа -- вместо большого ветвления (switch) делать массив или хэш-таблицу с селекторами (т.е. именами) маленьких методов, каждый из которых обрабатывает блоки своего типа.
    Я добавил кодер и декодер для VT_INT в стандартный класс COMVariantStructure, и прописал их в массивах EncoderSelectors и DecoderSelectors, создаваемых в методе initializeTranslationMaps:
    	DecoderSelectors := #(
    		#xEMPTY #xNULL #xI2 #xI4 #xR4 #xR8 #xCY #xDATE #xBSTR
    		#xDISPATCH #xERROR #xBOOL #xVARIANT #xUNKNOWN
    		#xIllegal #xIllegal #xIllegal #xUI1 #xUI2 #xUI4 #xIllegal #xIllegal #xINT ).
    
    	EncoderSelectors := #(
    		#xEMPTY: #xNULL: #xI2: #xI4: #xR4: #xR8: #xCY: #xDATE: #xBSTR:
    		#xDISPATCH: #xERROR: #xBOOL: #xVARIANT: #xUNKNOWN:
    		#xIllegal: #xIllegal: #xIllegal: #xUI1: #xUI2: #xUI4: #xIllegal: #xIllegal: #xINT: ).
    Tuesday, October 24th, 2006
    9:22 pm
    VisualWorks и HBITMAP
    В библиотеке VisualWorks есть класс Pixmap, являющийся обёрткой системного объекта Windows Bitmap.
    Он не поддерживает одну простую вещь: инициализацию по заданному указателю HBITMAP (bitmap handle),
    полученному, например, из COM-объекта System.Drawing.Bitmap методом GetHbitmap.



    Инструкция даётся здесь:
    надо создать pixmap и подменить вторые 4 байта в массиве handle.



    Вот кусок кода:
       image := comDriver getProperty: 'Image'.
       hbitmap := image invokeMethod: 'GetHbitmap'.
       pixmap handle longAt: 5 put: hbitmap.
       image release.


    Поле handle изначально в классе Pixmap закрыто; одноимённый метод для доступа к нему тоже необходимо создать.
    7:41 pm
    VisualWorks Smalltalk
    Мы с коллегой недавно использовали
    Smalltalk для разработки небольшого приложения.
    Вкратце, Smalltalk -- это (a) язык программирования и (б) среда разработки с
    компилятором и отладчиком, работающая на виртуальной машине. Среда написана на Smalltalk, за исключением небольшого количества
    примитивных вызовов.


    Есть немало реализаций среды Smalltalk. Наиболее известные из них --
    Dolphin,
    Cincom
    (VisualWorks)
    ,
    MT,
    Squeak.
    Мы выбрали VisualWorks, по ряду причин:

    • Быстрая виртуальная машина;

    • Полнофункциональная версия бесплатна для некоммерческого использования (при этом коммерческая лицензия довольно «драконовская»);

    • Есть обёртка для Oracle (нет больше ни в одной реализации);

    • Для Windows поддерживаются механизмы внешних вызовов из динамических библиотек (DLL) на C, объектов COM Automation и .NET-сборок (assemblies);


    VisualWorks поддерживает

    практически все
    современные платформы, однако мы вели разработку только под Windows, с использованием COM.



    Общие впечатления от процесса разработки такие:

    • [+] Очень простой и выразительный язык. Термин «объектно-ориентированный» к нему подходит в значительно большей степени, чем к «раскрученным» C++, Java, C# и даже Python.

    • [+] Среда, в сравнении с теми же Visual Studio и Eclipse работает очень шустро. Все средства обзора и рефакторинга классов в наличии.

    • [+] Очень удобные отладчик и инспектор объектов. Особенно радует то,
      что адресное пространство у разрабатываемой программы общее со
      средой (при необходимости, саму среду можно модифицировать). С изменением
      кода «на лету» в процессе отладки нет никаких проблем.

    • [-] Все окна среды существуют не внутри «главного» окна,
      а сами по себе. В интерфейсе Windows нет виртуальных рабочих столов, и
      когда количество окон VisualWorks превышает 5, становится неудобно; у окон даже
      иконки одинаковые. Update: В репозитории Cincom есть пакет Windows Icons, который
      снабжает окна разных типов разными иконками.

    • [+] Есть удобное средство для проектировния GUI; сам язык как нельзя лучше подходит для программирования интерфейсов.

    • [-] Элементы интерфейса (widgets) -- собственные, а не «родные» системные. На последние
      планируется переход в будущих версиях.

    • [-] Поддержка COM неполна; например, нет возможности вставить в интерфейс OLE control. Есть библиотека SmallCom немецкой фирмы, но её бесплатная версия имеет
      чисто демонстрационную функциональность, и та у нас не заработала. Помимо отсутствия OLE controls, есть другое неприятное упущение.

    • [-] Поддержка Oracle неполна; авторы недоделали интерфейс для временных больших объектов (temporary LOB-s). Нам пришлось писать его самим.

    • [-] Поддержка .NET вообще «ниже плинтуса» -- ей не удалось воспользоваться, хотя мы пытались.

    • [+] Понравилось то, что вся разрабатываемая программа вместе со средой разработки и её настройками содержится в едином файле образа (image); это очень упрощает передачу проекта со всей инфраструктурой между
      машинами. С исходными текстами у VisualWorks своя кухня, которая, впрочем, не очень нарушает принцип. Для параллельной разработки в VisualWorks есть собственная система контроля версий под названием SToRE; нам она не потребовалась.

    • [-] Виртуальная машина не совсем стабильна; при аварийном закрытии теряется всё, что делалось
      со времени последнего сохранения образа. Сама по себе она «падает» редко, но с использованием
      низкоуровневых библиотек типа COM это становится проблемой, т.к. эти библиотеки работают в адресном пространстве среды.
      Update: исходники всё же можно восстановить через Tools->Change List, импортировав файл «.cha».

    • [+] Готовое приложение можно выпустить в виде монолитного исполняемого файла (single executable).
      Для этого сначала надо с помощью утилиты RuntimePackager сделать образ (runtime image), в котором будет только та часть среды, которая нужна для запуска приложения, а затем с помощью сторонней утилиты Resource Hacker запаковать
      образ и виртуальную машину VisualWorks в исполняемый файл.

    Friday, September 22nd, 2006
    11:34 pm
    Common Lisp
    Написал первую осмысленную программу на Common Lisp.

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

    Комментарии от знатоков приветствуются.
    (asdf:operate 'asdf:load-op 'cl-utilities)
    
    (defpackage algebra
      (:use :common-lisp :cl-utilities))
    
    (in-package algebra)
    
    (defun submatrix (matrix row-to-delete)
      (let* ((n (car (array-dimensions matrix)))
    	 (result (make-array (list (1- n) (1- n)))))
        (flet ((fill-row (matrix-row result-row)
     		     (loop for index from 1 below n do
     			   (setf (aref result result-row (1- index))
    				 (aref matrix matrix-row index)))))
          (when (/= row-to-delete 0)
    	(loop for row from 1 below row-to-delete do
    	      (fill-row row (1- row)))
    	(fill-row 0 (1- row-to-delete)))
          (loop for row from (1+ row-to-delete) below n do
    	    (fill-row row (1- row)))
          result)))
    
    (defun determinant-sub (matrix)
      (let ((n (car (array-dimensions matrix))))
        (if (= 1 n)
    	(aref matrix 0 0)
          (let ((rows ()) (prod 1) (selected-row nil) (multiplier nil)
    	    (numerator 1) (denominator 1))
    	(loop for index from 0 below n do
    	      (when (/= 0 (aref matrix index 0))
    		(setq rows (append rows (list index)))
    		(setq prod (* prod (aref matrix index 0)))
    		))
    	(if (= (length rows) 0)
    	    0
    	  (progn
    	  (setq selected-row (car rows))
    	  (setq multiplier (/ prod (aref matrix selected-row 0)))
    	  (loop for index from 1 below n do
    		(setf
    		 (aref matrix selected-row index)
    		 (* (aref matrix selected-row index) multiplier)))
    	  (dolist (row (cdr rows))
    	    (setq multiplier (/ prod (aref matrix row 0)))
    	    (loop for index from 1 below n do
    		  (setf
    		   (aref matrix row index)
    		   (- (* (aref matrix row index) multiplier)
    		      (aref matrix selected-row index)))))
    	  (if (/= 0 selected-row)
    	      (setq numerator -1))
    	  (if (= 1 (length rows))
    	      (setq numerator (* numerator (aref matrix (car rows) 0))))
    	  (if (> (length rows) 2)
    	      (setq denominator (expt prod (- (length rows) 2))))
    	  (/ (* (determinant-sub (submatrix matrix selected-row))
    		numerator
    	      ) denominator)))))))
    
    (defun determinant (matrix)
      (determinant-sub (copy-array matrix)))
    
    (defun submatrix2 (matrix row-to-delete)
      (let* ((n (car (array-dimensions matrix)))
    	 (result (make-array (list (1- n) (1- n)))))
        (flet ((fill-row (matrix-row result-row)
     		     (loop for index from 1 below n do
     			   (setf (aref result result-row (1- index))
    				 (aref matrix matrix-row index)))))
          (when (/= row-to-delete 0)
    	(loop for row from 0 below row-to-delete do
    	      (fill-row row row)))
          (loop for row from (1+ row-to-delete) below n do
    	    (fill-row row (1- row)))
          result)))
    
    (defun hyperplane (points)
      (let* ((first-point (car points))
    	 (n (length first-point))
    	 (matrix (make-array (list n n)))
    	 (row 0) (col 1)
    	 (multiplier 1) (free-term 0)
    	 (result ()))
        (dolist (point (cdr points))
          (setf row 0)
          (mapcar #'(lambda (a b)
    		  (setf (aref matrix row col) (- a b))
    		  (setf row (1+ row)))
    	      point first-point)
          (setf col (1+ col)))
        (loop for row from 0 below n do
    	  (setq result
    		(append result
    			(list (* multiplier
    				 (determinant
    				  (submatrix2 matrix row))))))
    	  (setq multiplier (- multiplier)))
        (mapcar #'(lambda (a b) (setf free-term (- free-term (* a b))))
    	    result first-point)
        (append result (list free-term))))
    
    Friday, August 25th, 2006
    4:37 am
    Steve Yegge
    Товарищ забористо пишет.
    Много про Lisp, Emacs, Ruby и вообще статей на околопрограммистские темы.

    When Polymorphism Fails

    Lisp Wins (I think)

    Math Every Day

    The Emacs Problem

    Effective Emacs

    Lisp is Not an Acceptable Lisp
    Wednesday, June 7th, 2006
    1:10 pm
    Доменное имя третьего уровня, бесплатно и не выходя из дому.
    За подробные инструкции здесь и здесь спасибо товарищу В.Н. Дерибину. Частично они устарели: XName.org даёт и первичный, и вторичный DNS-серверы, а не один на выбор, NetworkSolutions вроде уже не даёт нахаляву nic-handle, ну и многие IP-адреса успели поменяться.


    Ниже -- кратко о том, как получить домен в .spb.ru (net.ru, .msk.ru, ...), имея постоянный IP-адрес.

    1. Проверяем, не занято ли уже это имя в Релкоме. К примеру, poupkine.spb.ru не занято.

    2. Заводим аккаунт на XName.org. Логинимся.

    3. Кликаем на "Create Zone". Вводим там название своего домена poupkine.spb.ru, выбираем PRIMARY и жмём Create

    4. Кликаем на ссылку "modification interface". Там в разделе "Address (A) records" вписываем в колонку 'Name' свой домен poupkine.spb.ru, а в колонку IP -- соответственно свой IP-адрес.

    5. Кликаем на 'Generate zone configuration' внизу. Всё, primary и secondary DNS у нас есть, и будут действовать не позже чем через час. Делаем logout с сайта XName.

    6. Далее. Для получения nic-handle идём по ссылке на сайт РосНИИРОС. Там выбираем "Web-формы для регистрации физических лиц". Придумываем себе незанятый идентификатор вида POUPKINE-RIPN и жмём "Продолжить".

    7. Заполняем длинную форму с персональными данными и снова жмём "Продолжить"

    8. В запросе об указании технической поддержки выбираем "Зарегистрировать". Снова вводим идентификатор вида POUPKINE-MNT-RIPN и жмём "Продолжить".

    9. Вводим свой инентификатор POUPKINE-RIPN в качестве контактного лица техподдержки и жмём "Продолжить".

    10. На последнем этапе вводим свой e-mail адрес и жмём "зарегистрировать объект".

    11. Ждём писем от XName и РосНИИРОС о том, что DNS обновились и nic-handle завёлся.

    12. Пишем письмо на адрес noc-dns@relcom.net:
      	domain:  poupkine.spb.ru
      	descr:   Personal page
      	descr:   50, 9 line, Vasil'evskiy island.
      	descr:   Saint-Petersburg
      	descr:   Russia
      	admin-c: POUPKINE-RIPN 
      	zone-c:  POUPKINE-RIPN
      	tech-c:  POUPKINE-RIPN
      	nserver: ns0.xname.org 195.234.42.1
      	nserver: ns1.xname.org 193.218.105.149
      	changed: poupkine@mail.ru 20060531
      	source:  RIPN

    13. Ждём не более 4-х дней (реально пару часов) до регистрации домена, потом ждём обновления всех DNS-серверов, не более двух дней, у меня опять же всё было готово через несколько часов.
    Tuesday, May 30th, 2006
    12:58 am
    Oracle: работа с LOB-ами через OCI.
    В СУБД Oracle есть встроенный тип данных под названием LOB (Large Object), что означает набор неструктурированной информации, текстовой (CLOB) или бинарной (BLOB) произвольного объёма. Речь пойдёт о том, как обеспечить приемлемое быстродействие операций чтения/записи при работе через интерфейс OCI.

    Создание таблицы с колонкой из LOB

    На этом этапе следует определить, потребуется ли встроенный кэш Oracle для чтения данных из LOB. Если собираетесь читать одни и теже данные несколько раз, то кэш имеет смысл использовать; скорость чтения некэшированного LOB-а очень мала. Ключевые слова -- CACHE/NOCACHE/CACHE READS. CACHE влияет и на чтение, и на запись, а CACHE READS -- только на чтение.
    Есть ещё ряд параметров, в меньшеё степени влияющих на быстродействие: LOGGING/NOLOGGING, PCTVERSION, ENABLE/DISABLE STORAGE IN ROW, о которых подробно рассказано в документации. Размер кэша определяется параметром базы db_cache_size.

    Запись (OCILobWrite())

    Для многократных разбросанных записей следует включить CACHE, NOLOGGING и PCTVERSION 0; видимо, это всё, что можно сделать.
    Для многократной записи маленькими кусочками в конец LOB-а можно и нужно использовать буферизацию. С ней процесс становится быстрее, но и обработка ошибок гораздо «интереснее». Режим буферизации LOB-а включается вызовом OCILobEnableBuffering(). Интересно, что этого вызова нет ни в PL/SQL, ни в JDBC, ни в OCCI-обёртке. Есть только в C и (?) в Visual Basic.

    О недостатках режима буферизации: во-первых, запрещён ряд стандартных операций, например, нельзя узнать размер LOB-а или сделать «добавление в конец». Приходится сохранять размер до буферизации, после чего заниматься ручным подсчётом.
    Во-вторых, OCILobWrite() может возвращать ошибки:
    1. ORA-22280: no more buffers available for operation
    2. Рецепт следующий: получив эту ошибку, надо выключить буферизацию (OCILobDisableBuffering()) и повторить запись. После успеха включить буферизацию снова.
    3. ORA-22282: non-contiguous append to a buffering enabled LOB not allowed
    4. Это редкая ошибка, т.е. появляется даже когда не должна, но в этом случае редко. Она перестала появляться совсем, только когда я усвоил простое правило: надо записывать в LOB только чётное количество байт. Похоже, что на нечётном Oracle просто сбивается со счёта. Это касается и 9-й, и 10-й версий.
    Что же касается режима без буферизации, то в Oracle 9 продедура записи явно работает по алгоритму маляра Шлемиэля: чем длинее LOB, тем больше времени тратится на позиционирование. В Oracle 10, где это исправили, скорость записи от размера LOB-а не зависит.
    И ещё один момент: OCILobWrite не обязан записывать сразу все данные, которые ему дали на вход. Количество записанных байт он возвращает, и надо дозаписывать снова, пока не кончится.

    Чтение (OCILobRead())

    При многократном чтении следует включить CACHE либо CACHE READS. При последовательном чтении маленькими кусками можно включить буферизацию, но она работает менее эффективно, чем собственный рукодельный буфер. При использовании оного штатную буферизацию лучше выключить, она будет избыточна.
    То же самое, что и с записью: не стоит рассчитывать на то, что OCILobRead() прочтёт все данные за один раз.
    Wednesday, March 29th, 2006
    12:30 pm
    MPI
    Совершенно утомили различия в реализациях MPI, а именно кривизна LAM/MPI по сравнению с MPICH 1.2.5.
    1) В LAM/MPI возникают значительные (до 0.5 с) задержки во времени при отсылке нескольких асинхронных сообщений подряд (MPI_Isend()) вперемешку к MPI_Irecv(). Приходится изворачиваться, запаковывать их в структуры и т.п.
    2) Установка сообщениям различных тегов, с целью создания "независимых" каналов обмена, не даёт в LAM/MPI ожидаемого результата: неполучение (MPI_Recv()) асинхронного сообщения из одного канала блокирует другой канал.
    3) Бесконечный цикл вызовов MPI_Test() вместо одного вызова MPI_Wait() тормозит передачу сообщений от "проверяемому" узлу от другим узлов. Это, конечно не проблема, т.к. MPI_Wait() правильнее, но в MPICH и с MPI_Test() трудностей нет.
    4) При частой отсылке асинхронных сообщений с нескольких узлов может возникнуть ситуация, когда некоторые сообщения ДУБЛИРУЮТСЯ (при этом соответствующее количество теряется). Очень неприятно, но этого можно избежать, если не нагружать очередь, т.е. например не посылать
    больше 5 сообщений подряд, пока не получено подтверждение о получении первого.

    Вдобавок, есть общая для всех реализаций MPI проблема: environment.
    Во-первых, системные переменные окружения не передаются автоматически от того узла, на котором программа запущена, к остальным (это связано с безопасностью и/или с использованием rsh вместо ssh для запуска программ на других узлах). Люди пишут workaround для передачи этих переменных посредством MPI_Bcast() в самом начале исполнения программы.
    Во-вторых, даже для корневого процесса не гарантируется наличие окружения с той системы, на которой он запущен.
    Вывод: environment лучше вовсе не использовать, если требуется переносимость.
    Tuesday, February 14th, 2006
    3:09 am
    Установка и настройка шрифтов CM-Super для Plain TeX на Debian.
    (О самих шрифтах написал ранее)

    Установка
    1) Установить последние версии пакетов tetex-base, tetex-bin, tetex-extra и cm-super.
    2) В файле /etc/texmf/fmt.d/01tetex.cnf раскомментировать строку с cyrtex; запустить update-texmf.
    3) В файле /usr/share/texmf-tetex/tex/cyrplain/config/cyrtex.cfg закомментировать строку с cyrcmfnt и раскомментировать с cyrecfnt.
    4) Добыть из архива CTAN пакет ec-plain; *.tfm файлы положить в /usr/share/texmf-tetex/fonts/tfm/public/, а *.mf -- в /usr/share/texmf-tetex/fonts/source/public/.
    5) Запустить mktexlsr, затем mktexfmt cyrtex.

    Запуск
    1) В tex-файле не забыть поставить \Russian для русских переносов.
    2) Для получения dvi-файла cyrtex file.tex или pdfetex -fmt cyrtex file.tex.
    3) Для получения pdf-файла pdfetex -fmt cyrtex -output-format pdf file.tex.
    4) Посмотреть на используемые шрифты можно в /usr/share/texmf-tetex/tex/cyrplain/base/cyrecfnt.tex. Кириллические шрифты имеют префикс la: например labx1200 -- жирный кириллический шрифт 12-го размера.
    Monday, December 26th, 2005
    3:25 am
    Среда для статистических расчётов под названием S-plus
    S-plus -- коммерческий кроссплатформенный программный комплекс для интерактивных статистических расчётов. Есть desktop и enterprise версии. Подробная документация в свободном доступе отсутствует.
    Среда имеет развитый GUI, половина функций которого присутствует только в Windows-версии.
    В качестве IDE используется Eclipse (с плагинами, вероятно).

    Databases
    SQL Server, Oracle, Sybase, DB2, ODBC (последнее только под Windows)

    Extensions
    Писать расширения на S конечно можно.
    Про другие языки не нашёл, но думаю что тоже всё в порядке.

    Interfaces
    Поддерживаются локальные C, C++, Java и Fortran API.
    Поддерживаются интерфейсы COM, OLE и DDE (под Windows).
    Поддерживается Remote Client Access (вычисления на сервере, результаты и графики -- на клиенте).
    Поддерживается Remote Java API (вызовы с клиента).

    Кроссплатформенность
    Windows on Intel, Solaris on SPARC, RedHat (only) Linux on Intel, HP-UX, IBM AIX.
    Windows-версия значительно "навороченнее" остальных.

    Лицензия
    Коммерческая, есть на сайте. Trial или студенческих вариантов не обнаружено.
    3:15 am
    Среда для статистических расчётов под названием R
    R -- свободно распространяемая кроссплатформенная среда для интерактивных статистических расчётов и не только. Есть хорошая общедоступная документация и приличное community.
    Основной язык -- R, это S с улучшениями, в частности lexical (static) scoping. Сама среда написана на R + C.
    Есть сторонние средства для интеграции поддержки R в Emacs, jEdit, Eclipse.
    Стандарнтая среда R имеет интерфейс command-line + graphics plotter (в одной программе много консольных и графических окон). Есть несколько сторонних GUI-обвязок (RKWard, SciViews-R, Rcmdr).
    Сама по себе среда R не является сервером.

    Databases
    Есть модули для R: RMySQL, RODBC, ROracle, RSQLite, pgUtils (postgreSQL).

    Extensions
    Есть возможность писать свои процедуры на R (т.е. и на S), C, C++. Есть хорошая документация.
    Для вызова из R процедур на Java есть небольшой дополнительный
    пакет rJava. Ещё один пакет SJava (ниже), обладает более широкими возможностями.

    О threads в R никакой информации нет, вероятно R не поддерживает
    треды.

    Interfaces (local)
    SJava -- Открытая библиотека, написанная на Java+C. Она позволяет вызывать Java из R и R из Java (последнее только на UNIX-платформах).

    При желании вызывать R из Java, с управляющей программой на Java, необходимо создавать экземпляр R. Нельзя подключиться к уже запущенному экземпляру R. Однако, есть возможность задать для одного экземпляра (ROmegahatInterpreter) несколько независимых пользовательских контекстов (REvaluator). К сожалению, хотя сам по себе REvaluator является thread-safe, интерфейс R таковым не является и одновременная работа с REvaluator в нескольких тредах невозможна.
    Должно быть, запросы к REvaluator-у из разных тредов ставятся в очередь.

    Есть немаленький открытый Java-проект CDK (Chemistry Development Kit), который успешно использует SJava.

    Interfaces (client-server)
    Есть простой движок RWeb (на cgi), вызывающий R из command line. Его исходники открыты для изучения. Ничего особенного.

    Есть свободно распространяемый кроссплатформенный пакет RServe, который позволяет организовать клиентский интерфейс (C, C++, Java) к R на сервере. Сам RServe запускается изнутри R, чем выгодно отличается от SJava и RWeb.

    RServe использует бинарный, а не текстовый интерфейс к R, что обеспечивает хорошую скорость передачи данных.

    RServe может работать с многими клиентами одновременно. К сожалению, это только на UNIX-серверах (по причине отсутствия в Windows API поддержки fork()). Windows-версия RServe не держит более одного клиента единовременно.

    Каждый клиент получает персональный контекст (RConnection), но не может использовать его одновременно в параллельных тредах (что понятно), и не имеет права на более чем один RConnection (что непонятно).

    Кроссплатформенность
    Язык R, разумеется, аппаратно-независим.
    Как было сказано, R -- кроссплатформенная среда, но SJava и RServe имеют ограниченную функциональность под Windows.

    Лицензии
    Цитата с сайта RServe:

    "All files and sources are released under GPL.
    Please contact us if you need a different license."

    R и SJava также имеют GPL лицензию.

    Любые программы, делающие (даже динамическую) линковку с GPL-кодом, обязаны распространяться под GPL
    Sunday, December 11th, 2005
    4:08 am
    Plain TeX, DVI, PDF, шрифты, cm-super.
    TeX производит DVI-файлы. В DVI-файлах не содержится информации о глифах (начертаниях символов). Глифы (т.е. собственно шрифты) привязаны к конкретному дистрибутиву TeX на конкретной машине. Аббревиатура DVI -- DeVice Independent -- означает независимость именно от устройства вывода (экран/принтер/другой принтер), но не от системы.

    Формат PDF не зависит от устройства вывода и переносим. Для конвертации DVI в PDF есть утилиты dvipdf, dvipdfm и dvipdfmx. В первой по сравнению с двумя другими мало опций, а чем dvipdfmx лучше dvipdfm, хорошо объяснил автор скриншота на LOR.
    Кроме утилит dvipdf*, есть ещё программа pdfTeX, которая получает PDF из исходного tex-файла сразу, без промежуточного DVI. К тому же, pdftex позволяет использовать "особые" возможности формата PDF.

    Вот какая трудность возникает при подготовке русскоязычных текстов в TeX/LaTeX: стандартные кириллические шрифты lh представлены в дистрибутивах на языке METAFONT. При необходимости просмотра или печати DVI-файла автоматически генерируются растровые глифы необходимых шрифтов.

    Конвертеры DVI->PDF и pdfTeX также вставляют в PDF-файл растровые шрифты, в разрешении для принтера. К сожалению, многие программы плохо сглаживают их при выводе на монитор, и читать такие тексты с экрана становится затруднительно.

    Видимо, создание векторного шрифта (например Type1, Type3 или TrueType) из описания на METAFONT -- сложная задача. Она была решена (довольно хорошо, но не идеально) В. Воловичем. Его пакет cm-super содержит все кириллические (и не только) стандартные TeX-овские шрифты в формате Type1. Собственно, при должной настройке они и вставляются в PDF.

    Инструкции по установке и настройке в Debian:
    Для Plain TeX.
    Для LaTeX.
    Wednesday, November 30th, 2005
    8:27 pm
    C, forward declarations
    [все наверное уже это знают]

    Механизм т.н. forward declarations (объявление типа без определения его) в C не очень удобен.
    Предположим, есть структуры A и B, описанные в разных файлах. Задача -- не пользоваться #include одного файла в другой, т.к. это, строго говоря, не требуется.
    --- файл a.h
    struct tag_a
    {
      int i;
    };
    typedef struct tag_a A;
    
    --- файл b.h
    struct tag_a;
    typedef struct tag_a A;
    typedef struct
    {
      A *a;
    } B;

    Так не выйдет: включать a.h и b.h вместе некорректно, т.к. повторный typedef в языке C запрещён. В C++, кстати можно и повторять typedef, и вообще объявлять "struct A;".

    Приходится использовать трюки типа
    #ifndef _A_DEFINED
    #define _A_DEFINED
    typedef struct tag_a A;
    #endif
    Tuesday, November 8th, 2005
    2:34 pm
    .NET Clipboard change notification
    Стандартный класс Clipboard в .NET API предоставляет возможность лишь управлять содержимым системного буфера обмена. Event-а, на который можно было бы подписаться, чтобы получать извещение о том, что содержимое буфера изменилось (другой программой, например), увы, не предусмотрено.

    Обнаружился очень полезный сайт http://www.vbaccelerator.com/. Среди прочего, на нём есть описание способа получить желаемый event, с исходниками класса ClipboardChangeNotifier.

    Реализовано через перехват сообщения WM_DRAWCLIPBOARD, которое посылается окну. То есть, необходимо иметь окно, чтобы получать это сообщение. Работает.
    Friday, November 4th, 2005
    4:05 pm
    Впечатления от System.Windows.Forms
    Немного соприкоснулся с относительно новым API для программирования интерфейса (писал на C#).
    Несмотря на огромные усилия, затраченные на разработку System.Windows.Forms и платформы .NET в целом, продуманностью оно явно не отличается.

    1) Все эти "красивые" кнопочки и меню a la Office 2003 и VS.NET в стандартном API недоступны! По этой причине желающим писать под .NET "XP-style" интерфейсы приходится пользоваться third-party библиотеками: Syncfusion, Infragistics, DotNetWidgets...

    2) Та же история с тулбарами (ToolBars). Сделать XP-style dockable тулбар в стандартном .NET -- нетривиальная задача. Поместить на тулбар что-то, кроме кнопок (например ComboBox) -- тоже нельзя, хотя в VS.NET и Office это обычное явление. Народу остаётся или придумывать хитроумные трюки, или опять же пользоваться всемогущими Syncfusion или Infragistics.
    На Panel (в отличие от ToolBar) можно поместить любой Control, но это не всегда удобно; в частности, только на ToolBar-е работает автоматическое "перестраивание" кнопок в два и более рядов, когда ширины не хватает, чтобы выстроить в один ряд.

    3) Смешная история с "плоскими" (flat) кнопками (т.е. у которых свойство FlatStyle выставлено в Flat): они обводятся рамкой, которую не убрать.
    Краткий ответ на этот вопрос даётся в дискуссии:
    -- How can I ommit the border of a button or change its width?
    -- You can't.

    Более обнадёживающий ответ даётся в другой дискуссии: рисовать кнопку вручную или поместить её на Flat ToolBar, в котором рамочек вокруг кнопок нет.

    Тоска, одним словом. Не знаком с SWT, AWT/Swing, GTK, wxWindows и пр., но надеюсь, что все они лучше чем Windows.Forms.
[ << Previous 20 -- Next 20 >> ]
About LJ.Rossia.org