Модульная структура
От: Larsik Россия  
Дата: 09.07.07 09:49
Оценка:
Добрый день, коллеги.

Существует проект написанный на Deplhi, надо перенести на C#, но есть загвозка, одна и очень большая.
Сама система не умеет ничего, все функции реализованы в dll'ках которые подгружаются при запуске или изменении настроек. А dll'ки программа находи в нужном каталоге, причем количество может меняться. Как такое можно реализовать на C#.

Все Dll'ки имеют определенный стандарт, по названию функций и возвращаемым результатам, типа плугинов.

Очень надо, плzzz.
Re: Модульная структура
От: hell citizen Россия  
Дата: 09.07.07 11:00
Оценка:
Здравствуйте, Larsik, Вы писали:

L>Все Dll'ки имеют определенный стандарт, по названию функций и возвращаемым результатам, типа плугинов.

Определённые стандарты описать интерфейсами, для подгрузки dll-ек смотреть в сторону System.Reflection.
Если хочется не переписывать нативные dll-ки, читать про атрибут DllImport всё с ним связанное
Re: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 12:24
Оценка: 1 (1)
Здравствуйте, Larsik, Вы писали:

L>Добрый день, коллеги.


L>Существует проект написанный на Deplhi, надо перенести на C#, но есть загвозка, одна и очень большая.

L>Сама система не умеет ничего, все функции реализованы в dll'ках которые подгружаются при запуске или изменении настроек. А dll'ки программа находи в нужном каталоге, причем количество может меняться. Как такое можно реализовать на C#.

L>Все Dll'ки имеют определенный стандарт, по названию функций и возвращаемым результатам, типа плугинов.


L>Очень надо, плzzz.


Можно сделать так:
    public class SafeModule : SafeHandle
    {
        public SafeModule(string path) : base(IntPtr.Zero, true)
        {
            handle = LoadLibrary(path);
        }

        protected override bool ReleaseHandle()
        {
            return FreeLibrary(handle);
        }

        public override bool  IsInvalid { get { return handle==IntPtr.Zero; } }

        public IntPtr GetProcAddress(string lpProcName)
        {
            return GetProcAddress(handle, lpProcName);
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
        private static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
        private static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
    }
    
    public class DynamicLibrary : IDisposable
    {
        private SafeModule _hModule = null;
        public DynamicLibrary(string path)
        {
            // Загружаем DLL
            _hModule = new SafeModule(path);

            try
            {
                initFunctions();
            }
            finally
            {
                _hModule.Dispose();
            }
        }

        public void Dispose()
        {
            // Если DLL не выгружена, выгружаем ее
            if (!_hModule.IsClosed)
                _hModule.Dispose();
        }

        private void initFunctions()
        {
            // инициализируем делегаты адресами функций из DLL
            MessageBox = (MessageBoxDelegate)getAddrOfProc("MessageBoxA", typeof(MessageBoxDelegate));
        }

        private Delegate getAddrOfProc(string procName, Type type)
        {
            IntPtr procAddr = _hModule.GetProcAddress(procName);
            if (procAddr == IntPtr.Zero)
                throw new MethodAccessException("Function "+procName+" not found!");
            else
                return Marshal.GetDelegateForFunctionPointer(procAddr, type);
        }

        // Описываем параметры всех нужных функций в виде делегатов
        public delegate int MessageBoxDelegate(IntPtr hWnd, string lpText, string lpCaption, int uType);
        public MessageBoxDelegate MessageBox = null;
    }



    class Program
    {
        static void Main()
        {
            using(DynamicLibrary dll = new DynamicLibrary("user32.dll"))    // не забываем диспозить DLL'ку после использования, чтобы она не жрала зря память
                dll.MessageBox(IntPtr.Zero, "test", "capt", 0);
        }
    }


в DynamicLibrary описываем параметры всех нужных функций в виде делегатов (я так понял что они во всех DLL'ях одинаковые). При создании экземпляра DynamicLibrary передаем параметром путь к требуемой DLL'ке и вызываем у DynamicLibrary нужные функции как делегаты...

... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[2]: Модульная структура
От: AlexZu Россия  
Дата: 09.07.07 13:00
Оценка: +1
Здравствуйте, _Morpheus_, Вы писали:

_M_>
_M_>...
_M_>        public DynamicLibrary(string path)
_M_>        {
_M_>            // Загружаем DLL
_M_>            _hModule = new SafeModule(path);

_M_>            try
_M_>            {
_M_>                initFunctions();
_M_>            }
_M_>            finally
_M_>            {
_M_>                _hModule.Dispose();
_M_>            }
_M_>        }
_M_>...
_M_>


Лихо ты выгрузил dll после получения адресов ф-й
Re[3]: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 13:07
Оценка:
Здравствуйте, AlexZu, Вы писали:

AZ>Здравствуйте, _Morpheus_, Вы писали:


_M_>>
_M_>>...
_M_>>        public DynamicLibrary(string path)
_M_>>        {
_M_>>            // Загружаем DLL
_M_>>            _hModule = new SafeModule(path);

_M_>>            try
_M_>>            {
_M_>>                initFunctions();
_M_>>            }
AZ>_M_>            finally
_M_>>            {
_M_>>                _hModule.Dispose();
_M_>>            }
_M_>>        }
_M_>>...
_M_>>


AZ>Лихо ты выгрузил dll после получения адресов ф-й


блин, точно!

finally надо на catch заменить
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[4]: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 13:13
Оценка:
вот так:
    catch
    {
        _hModule.Dispose();
        throw;
    }
... << RSDN@Home 1.2.0 alpha rev. 676>>
Вот вариант с доп.проверками и чисткой после выгрузки
От: _Morpheus_  
Дата: 09.07.07 15:17
Оценка: 3 (1)
Вот вариант с дополнительными проверками и чисткой делегатов после выгрузки DLL:

using System;
using System.Runtime.InteropServices;
using System.Security;
using System.IO;


public class SafeModule : SafeHandle
{
    public SafeModule(string path)
        : base(IntPtr.Zero, true)
    {
        handle = LoadLibrary(path);
    }

    protected override bool ReleaseHandle()
    {
        return FreeLibrary(handle);
    }

    public override bool IsInvalid { get { return handle == IntPtr.Zero; } }

    public IntPtr GetProcAddress(string lpProcName)
    {
        return GetProcAddress(handle, lpProcName);
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
    private static extern bool FreeLibrary(IntPtr hModule);

    [DllImport("kernel32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
}

public class DynamicLibrary : IDisposable
{
    private SafeModule _hModule = null;
    public DynamicLibrary(string path)
    {
        // Загружаем DLL
        _hModule = new SafeModule(path);

        if (_hModule.IsInvalid)
            throw new FileNotFoundException("Can't open library " + path);

        try
        {
            initFunctions();
        }
        catch
        {
            Dispose();
            throw;
        }
    }

    public void Dispose()
    {
        clearFunctions();

        // Если DLL не выгружена, выгружаем ее
        if (!_hModule.IsClosed)
            _hModule.Dispose();
    }

    private Delegate getAddrOfProc(string procName, Type type)
    {
        IntPtr procAddr = _hModule.GetProcAddress(procName);
        if (procAddr == IntPtr.Zero)
            throw new MethodAccessException("Function " + procName + " not found!");
        else
            return Marshal.GetDelegateForFunctionPointer(procAddr, type);
    }

    // Описываем параметры всех нужных функций в виде делегатов
    public delegate void testDelegate(int x, int y);
    public testDelegate test = null;

    private void initFunctions()
    {
        // инициализируем делегаты адресами функций из DLL
        test = (testDelegate)getAddrOfProc("test", typeof(testDelegate));
    }

    private void clearFunctions()
    {
        // чистим делегаты (дабы не вызвать AccessViolation)
        test = null;
    }
}


public class Program
{
    static void Main()
    {
        using (DynamicLibrary dll = new DynamicLibrary("lib.dll"))
            dll.test(123, 456);
    }
}
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 16:06
Оценка:
Здравствуйте, Larsik, Вы писали:

L>Существует проект написанный на Deplhi, надо перенести на C#, но есть загвозка, одна и очень большая.

L>Сама система не умеет ничего, все функции реализованы в dll'ках которые подгружаются при запуске или изменении настроек. А dll'ки программа находи в нужном каталоге, причем количество может меняться. Как такое можно реализовать на C#.

L>Все Dll'ки имеют определенный стандарт, по названию функций и возвращаемым результатам, типа плугинов.


что касается поиска dll, это можно сделать так:
        public static void Run()
        {
            foreach (string dllPath in GetFileList(".", "*.dll"))
                using (DynamicLibrary dll = new DynamicLibrary(dllPath))
                    dll.test(123, 456);
        }

        public static string[] GetFileList(string startDir, string mask)
        {
            List<string> dllPathList = new List<string>();

            foreach (string dir in Directory.GetDirectories(startDir))
                dllPathList.AddRange(GetFileList(dir, mask));
            
            foreach (string dllPath in Directory.GetFiles(startDir, mask))
                dllPathList.Add(dllPath);
            
            return dllPathList.ToArray();
        }


в данном примере используется ранее приведенный мной класс DynamicLibrary с описанием интерфейса с функцией test. Пример ищет все DLL файлы расположенные начиная с текущей папки приложения и по всем подпапкам, загружает каждую найденую DLL и вызывает функцию test, после чего выгружает DLL и переходит к следующей.
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re: Модульная структура
От: Larsik Россия  
Дата: 09.07.07 17:22
Оценка:
Спасибо всем за ответы, но из всех ответов подходит больше всего ответ hell citizen мне нужно переписать полностью под .Net без unsafe. Если есть пример просьба показать.
Re[2]: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 17:38
Оценка:
Здравствуйте, Larsik, Вы писали:

L>Спасибо всем за ответы, но из всех ответов подходит больше всего ответ hell citizen мне нужно переписать полностью под .Net без unsafe. Если есть пример просьба показать.


я тебе привел пример DynamicLibrary, который не использует unsafe. На мой взгляд самое удачное решение, просто мне самому интересно стало и я сделал на будующее для себя этот класс, вот как он выглядит в последнем варианте:
public class DynamicLibrary : IDisposable
{
    private string _dllPath = string.Empty;
    private SafeModule _hModule = null;
    public DynamicLibrary(string dllPath)
    {
        // Загружаем DLL
        _dllPath = dllPath;
        _hModule = new SafeModule(dllPath);

        if (_hModule.IsInvalid)
            throw new FileNotFoundException("Can't open library " + dllPath);

        initFunctions();
    }

    public void Dispose()
    {
        clearFunctions();

        // Если DLL не выгружена, выгружаем ее
        if (!_hModule.IsClosed)
            _hModule.Dispose();
    }

    private Delegate getAddrOfProc(string procName, Type type)
    {
        IntPtr procAddr = _hModule.GetProcAddress(procName);
        if (procAddr == IntPtr.Zero)
            return null;
        else
            return Marshal.GetDelegateForFunctionPointer(procAddr, type);
    }

    public bool IsLoaded { get { return !_hModule.IsInvalid && !_hModule.IsClosed; } }

    public string DllPath { get { return _dllPath; } }




    // Описываем параметры всех нужных функций в виде делегатов
    public delegate void testDelegate(int x, int y);
    public testDelegate test = null;
    public delegate void test2Delegate(int x);
    public testDelegate test2 = null;

    private void initFunctions()
    {
        // инициализируем делегаты адресами функций из DLL
        test = (testDelegate)getAddrOfProc("test", typeof(testDelegate));
        test2 = (test2Delegate)getAddrOfProc("test2", typeof(test2Delegate));
    }

    private void clearFunctions()
    {
        // чистим делегаты (дабы не вызвать AccessViolation)
        test = null;
        test2 = null;
    }
}



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

Т.е. все что тебе нужно сделать это написать делегаты для всех функций использующихся в твоих DLL'ках и экземпляры этих делегатов, а в методы initFunctions и clearFunctions добавить инициализацию и очистку этих делегатов.

в приведенном варианте подразумевается что используются DLL'ки которые экспортируют две функции:
#include <windows.h>


__declspec(dllexport) void __cdecl test(int x, int y)
{
    char *buf = new char[256];
    wsprintf(buf, "TEST: %i, %i", x, y);

    MessageBox(NULL, buf, "CAPTION", 0);
    delete buf;
}

__declspec(dllexport) void __cdecl test2(int x)
{
    MessageBox(NULL, "TEST2", "CAPTION", 0);
}



Т.е. в твоем случае программа по поиску всех DLL'ей в заданной папке "D:\TEMP" и всех ее подпапках, загрузке этих DLL'ей и вызову функций test и test2 из каждой из них будет выглядеть таким образом:
class Program
{
    static void Main()
    {
        foreach (string dllPath in GetFileList(@"D:\TEMP", "*.dll"))
            using (DynamicLibrary dll = new DynamicLibrary(dllPath))
            {
                if (dll.test != null)            // если DLL содержит функцию test, то вызвать ее
                    dll.test(123, 456);
                if (dll.test2 != null)        // если DLL содержит функцию test2, то вызвать ее
                    dll.test2(0);
            }
    }

    public static string[] GetFileList(string startDir, string mask)
    {
        List<string> dllPathList = new List<string>();

        foreach (string dir in Directory.GetDirectories(startDir))
            dllPathList.AddRange(GetFileList(dir, mask));

        foreach (string dllPath in Directory.GetFiles(startDir, mask))
            dllPathList.Add(dllPath);

        return dllPathList.ToArray();
    }
}


никакого unsafe этот код не использует
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[3]: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 17:44
Оценка:
там ошибочка, при определении делегата test2, нужно так:
public test2Delegate test2 = null;



лучше врядли сделаешь, в любом случае, если тебе нужно выбирать с какой именно DLL иметь дело, то нужно использовать LoadLibrary/FreeLibrary, другого способа нет.
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[2]: Модульная структура
От: _Morpheus_  
Дата: 09.07.07 18:32
Оценка: 2 (1)
Здравствуйте, Larsik, Вы писали:

L>Спасибо всем за ответы, но из всех ответов подходит больше всего ответ hell citizen мне нужно переписать полностью под .Net без unsafe. Если есть пример просьба показать.



что касается полного переписывания всего кода на C#, то я думаю это не лучший вариант, в этом случае не получится выгружать неиспользуемые модули. В C# можно только загрузить новую сборку...

А делается это таким образом:

Три сборки, iface.dll, plugin1.dll, main.exe...

iface.cs:
using System;

public interface IPluginInterface        // некий интерфейс общий для всех объектов-плагинов, лежит в сборке, которая используется всеми частями приложения и плугинами
{
    void DoWork(string value);
}



main.cs, ссылается на iface.dll:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;

public class MainApp    // класс основного приложения, лежит в сборке основного приложения, которое тоже ссылается на сборку с интерфейсом IPluginInterface
{
    static void Main()
    {
        foreach (string asmPath in GetFileList(Environment.CurrentDirectory, "*.dll"))
        {
            Console.WriteLine("Loaded: {0}", asmPath);
            IPluginInterface iface = LoadPlugin(asmPath);
            if(iface!=null)    // если удалось успешно загрузить плагин, то вызываем его метод
                iface.DoWork("test");
        }
    }

    private static IPluginInterface LoadPlugin(string asmPath)
    {
        try
        {
            Assembly asm = Assembly.LoadFile(asmPath);    // загрузка сборки
        
            IPluginInterface iface = null;
            foreach (Type t in asm.GetTypes())                    // поиск типа который наследован от интерфейса IPluginInterface
                if (t.IsClass && typeof(IPluginInterface).IsAssignableFrom(t))
                {
                    iface = (IPluginInterface)Activator.CreateInstance(t);
                    break;
                }
            return iface;
        }
        catch(Exception ex)
        {
            Console.WriteLine("EXEPTION: "+ex.Message);
        }
        return null;    // не удалось загрузить сборку или найти подходящий тип - игнорируем
    }

    public static string[] GetFileList(string startDir, string mask)
    {
        List<string> dllPathList = new List<string>();

        foreach (string dir in Directory.GetDirectories(startDir))
            dllPathList.AddRange(GetFileList(dir, mask));
            
        foreach (string dllPath in Directory.GetFiles(startDir, mask))
            dllPathList.Add(dllPath);
            
        return dllPathList.ToArray();
    }
}



Собственно плагин, таких сборок может быть несколько, с разным кодом. plugin1.cs, ссылается на iface.dll:
using System;
using System.Windows.Forms;

public class Plugin1 : IPluginInterface   // класс плагина который лежит в своей отдельной сборке и ссылается на сборку с интерфейсом IPluginInterface
{
    public void DoWork(string value)
    {
        MessageBox.Show("Plugin1: "+value);
    }
}


в данном примере класс основного приложения (MainApp) ищет все сборки по указанному пути и во всех вложенных папках, загружает сборку, пытается найти в ней плагин и если находит и удается создать его экземпляр, то вызывает у плагина метод DoWork

Проблема только в том что в C# сборки можно загружать, а вот выгружать нельзя...
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[3]: Модульная структура
От: Larsik Россия  
Дата: 10.07.07 09:57
Оценка:
Здравствуйте, _Morpheus_, Вы писали:

_M_>Проблема только в том что в C# сборки можно загружать, а вот выгружать нельзя...


Для меня это как раз то и не проблема, в этих модулях лежат функции, которые переодически вызываюся, каждая со своим промежутком.
Почему модульная структура, потому что есть возможность написать доп. модуль и на тебе уже новый фукционал у системы))

Спасибо. Попробую, поозже отпишусь, что получилось. Скоровыложу проект на RSDN и все посмотрите, что да как))
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.