Monday, October 12, 2009

Хитрый ListView.

Выбрал для отображения структуры каталогов - компонент ListView, который в ближайшем будущем должен удовлетворять всем требованиям для отображения. Привлек компонент именно тем, что предоставляет возможность отображать содержимое в 4х режимах, из который наиболее важны View.LargeIcons и View.Details.

В общем 3 часа на выходных убил на то, чтобы найти баг, из-за которого в режиме List все отображается нормально, а в режиме Details - только серая полоска и пустой компонент. 10 раз перепроверил создание колонок при помощи ColumnHeaders и добавление элементов в список. А все оказалось гениально просто - при обновлении содержимого я вызывал ListView.Clear() по аналогии с компонентом TextBox, хотя, как оказалось, нужно было вызывать ListView.Items.Clear(), так как ListView.Clear() вызывает ListView.Items.Clear() + совсем не очевидный ход -> ListView.Columns.Clear(). А в связи с тем, что я создавал набор колонок один раз, то они при обновлении просто стирались. А так как по логике ListView - если нет заданных колонок, то ничего рисовать не надо - и получал пустой компонент, но при переключении в вид View.List - я видел корректное отображение, что меня вводило в ступор.

Вывод: перед использованием каких-либо API, меньше опираться на логику, а больше на документацию, которая в данном случае(MSDN) доступна чуть более, чем полностью :)

Friday, October 9, 2009

Дружба народов или использование native code библиотек с c#

Так как изобретать велосипед не хотелось, решил организовать поддержку в моем проекте плагинов для Total Commander. Плагины представляют собой динамические библиотеки двоичного кода со списком поддерживаемых функций, определяющих интерфейс плагина. Насчет статического связывания native библиотек с .net кодом написано много (программирование с помощью аттрибутов DllImport), но вот вопрос как загрузить библиотеку во время выполнения и выполнить функцию, которая экспортируется этой библиотекой остается мало освещен. Исправлю этот пробел.

Итак, постановка задачи:
Есть бибилотека someDll.dll, которая является не .net сборкой, а так называемым двоичным кодом(или "родным" (native)). Обычно такие библиотеки написаны на С++ или Delphi. Эта библиотека экспортирует функцию

void libraryFunction(char* text);


Необходимо вызвать эту функцию в проекте .net на этапе выполнения. В данном примере - в проекте на языке C#

Порядок действий.

1. Необходимо описать список win32 api функций, необходимых для работы с библиотеками. Использование этих функций легко организовать при помощи статического связывания при помощи InteropServices(Не забывайте добавлять директиву "using System.Runtime.InteropServices;" вначале файла) и использования аттрибута DllImport. Рекоммендую ресурс PInvoke.net для поиска готовых описаний системных функций, чтобы не ошибится при использовании функций со сложными входными параметрами и возвращаемыми значениями. Если вы используете SharpDevelop, то в контекстном меню редактора есть пункт "Insert PInvoke Signature" и выбрав его и введя в строке поиска необходимую функцию, вы сразу можете вставить готовое описание.


2. Для загрузки библиотеки необходима функция LoadLibrary. Она возвращает дескриптор библиотеки, который необходим для использования ее функций. В качестве параметра используется char[] который вы можете получить, используя метод ToCharArray() у класса string.


3. Необходимо получить адрес функции, которую вы хотите использовать. Используем GetProcAddress, первым параметром указываем дескриптор загруженной библиотеки, который мы получили вызвав LoadLibrary, а вторым параметром указываем название функции, которую хотим вызвать.

Примечание: Название функции содержит только имя функции без скобок, параметров и возвращаемого значения. Иначе функция вернет вам адресс IntPtr.Zero

4. Так как .net считается безопасным кодом, то преобразовать адрес функции в функцию невозможно. Но среда предоставляет возможность безопасно преобразовать адрес в делегат. Нам необходима функция "void libraryFunction(char* text);". Для этого объявим делегат

internal delegate void functionDelegate(char[] text);


Преобразовываем адрес функции в делегат(аналог безопасного варианта указателя на функцию в C++ для среды .net) при помощи метода Marshal.GetDelegateForFunctionPointer

functionDelegate libFunction = (functionDelegate)Marshal.
GetDelegateForFunctionPointer(procID, typeof(
functionDelegate));


5. И теперь мы свободно можем пользоваться нашей функцией при помощи делегата.



6. Не забваем выгрузить библиотеку при помощи FreeLibrary после того, как библиотека стала не нужной.

using System;
using System.Runtime.InteropServices;

class Imports
{
#region external imports

[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr LoadLibrary(string
lpFileName);

[DllImport("kernel32", CharSet = CharSet.Ansi,
ExactSpelling = true, SetLastError = true)]
public internal static extern IntPtr GetProcAddress(
IntPtr hModule, string procName);

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool FreeLibrary(IntPtr hModule);

#endregion
}

class DLL_Loader
{
internal delegate void functionDelegate(char[] text);

public void useFunctionFromDLL()
{
IntPtr libID = Imports.LoadLibrary("someDll.dll");
IntPtr procID = Imports.GetProcAddress(libID,
"libraryFunction");

functionDelegate libFunction = (functionDelegate)
Marshal.GetDelegateForFunctionPointer(procID, typeof(
functionDelegate));

libFunction("my string".ToCharArray());

Imports.FreeLibrary(libID);
}
}