Выбрал для отображения структуры каталогов - компонент 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) доступна чуть более, чем полностью :)
Monday, October 12, 2009
Friday, October 9, 2009
Дружба народов или использование native code библиотек с c#
Так как изобретать велосипед не хотелось, решил организовать поддержку в моем проекте плагинов для Total Commander. Плагины представляют собой динамические библиотеки двоичного кода со списком поддерживаемых функций, определяющих интерфейс плагина. Насчет статического связывания native библиотек с .net кодом написано много (программирование с помощью аттрибутов DllImport), но вот вопрос как загрузить библиотеку во время выполнения и выполнить функцию, которая экспортируется этой библиотекой остается мало освещен. Исправлю этот пробел.
Итак, постановка задачи:
Есть бибилотека someDll.dll, которая является не .net сборкой, а так называемым двоичным кодом(или "родным" (native)). Обычно такие библиотеки написаны на С++ или Delphi. Эта библиотека экспортирует функцию
Необходимо вызвать эту функцию в проекте .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);". Для этого объявим делегат
Преобразовываем адрес функции в делегат(аналог безопасного варианта указателя на функцию в C++ для среды .net) при помощи метода Marshal.GetDelegateForFunctionPointer
5. И теперь мы свободно можем пользоваться нашей функцией при помощи делегата.
6. Не забваем выгрузить библиотеку при помощи FreeLibrary после того, как библиотека стала не нужной.
Итак, постановка задачи:
Есть бибилотека 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);
}
}
Labels:
.net,
c#,
Marshaling,
plugin,
using native code in .net
Subscribe to:
Posts (Atom)