Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Динамическая загрузка и выгрузка DLL C++ в проекте на C#  (Прочитано 16839 раз)
0 Пользователей и 1 Гость смотрят эту тему.
ezus
Опытный

il
Offline Offline

« : 28-12-2011 09:01 » 

Здравствуйте.

Где я могу почитать что-нубудь по-русски по теме?
Если нет по-русски, то по-английски.

И уже совсем в идеале увидеть примеры кодов.

Гуугл совсем не помог.

Спасибо.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 28-12-2011 12:50 » 

ezus, а причём тут C#? Динамическая загрузка и выгрузка DLL - это WinAPI. Вызовы, соответственно, LoadLibrary и FreeLibrary. После динамической загрузки библиотеки нужно получить адреса её экспортированных функций и глобальных переменных - вызов GetProcAddress.

Google тут ни причём. В MSDN есть все примеры (на C++, конечно). Как оформить WinAPI вызовы в C# - это уже неоднократно разбиралось на форуме, с примерами.

Добавлено через 1 час, 35 минут и 29 секунд:
DLL: dll.dll
Код: (C++)
#include <iostream>
#include <windows.h>

using namespace std;

void initialize() throw()
{
        // Инициализация после подключения DLL к процессу.
        cout << "DLL: loaded." << endl;
}

// API DLL - экспортируемые функции ///////////////////////////////////////////

extern "C" void __declspec(dllexport) Test()
{
        cout << "DLL: called 'Test'" << endl;
}

///////////////////////////////////////////////////////////////////////////////

void finalize() throw()
{
        // Завершающие действия перед отключением DLL от процесса.
        cout << "DLL: is free." << endl;
}

BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpReserved) throw()
{
        switch(dwReason)
        {
        case DLL_PROCESS_ATTACH:
                initialize();
                break;
        case DLL_PROCESS_DETACH:
                finalize();
                break;
        }
        return TRUE;
}

Приложение
Код: (C#)
using System;
using System.Runtime.InteropServices;

namespace App
{
    class Program
    {
        [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
        static extern IntPtr LoadLibrary(string lpFileName);

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

        [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
        static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

        [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
        static extern uint GetLastError();


        delegate void TestDelegate();


        static void Main(string[] args)
        {
            Console.WriteLine("Application: begin.");
            IntPtr hModule = LoadLibrary("dll.dll");
            if (hModule == IntPtr.Zero)
            {
                Console.WriteLine("Application: Can't load DLL. Error # {0}.", GetLastError());
            }
            else
            {
                IntPtr ptrTest = GetProcAddress(hModule, "Test");
                if (ptrTest == IntPtr.Zero)
                {
                    Console.WriteLine("Application: Function 'Test' not found in DLL.");
                }
                else
                {
                    TestDelegate Test = Marshal.GetDelegateForFunctionPointer(ptrTest, typeof(TestDelegate)) as TestDelegate;

                    /// Работа с DLL //////////////////////////////////////////

                    Test();

                    ///////////////////////////////////////////////////////////
                }
                FreeLibrary(hModule);
            }
            Console.WriteLine("Application: end.");
            Console.ReadKey();
        }
    }
}
« Последнее редактирование: 28-12-2011 14:28 от dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
alexdrew
Новенький

ru
Offline Offline

« Ответ #2 : 24-03-2012 16:33 » 

Здравствуйте.
А как быть в случае, если функция Test должна принимать аргумент?
Например так:

Код: (C++)
extern "C" void __declspec(dllexport) Test(int i)
{
        cout << "DLL: called 'Test'" <<i<< endl;
}

При попытке оформить это так:

Код: (C#)
delegate void TestDelegate(int i);
//.....
Test(7);

Возникает эксепшен:
PInvokeStackImbalance  was detected
A call to PInvoke function 'ConsoleApplication1!ConsoleApplication1.Program+TestDelegate1::Invoke' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

Можно конечно сделать так:
Код: (C#)
[DllImport("new22.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
static extern void Test1(int i);
//........
Test(7);
И обойтись без использования делегатов.
Тогда эксепшен пропадает.
Но в этом случае будет отсутствовать проверка на загрузку dll. Что не очень хорошо.

Как можно добавить проверку на загрузку dll во втором случае, или исправить возникновение эксепшена в первом?
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 24-03-2012 20:12 » 

alexdrew, такой проблемы не существует. Опробовано.

По всей видимости та копия DLL, которая лежит рядом с EXE, не соответствует сборке с приведённым тобой делегатом. Будь внимательнее.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
ezus
Опытный

il
Offline Offline

« Ответ #4 : 10-04-2012 06:30 » 

Мы принимали решение об использовании динамической загрузки DLL  в надежде ( в часности ) избежать проблем с остаточной памятью DLL. Но, похоже, мечтать не вредно или мы опять чего-то не понимаем или не знаем.
Код:
         [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string lpFileName);

       [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate int DllDoReqCalcTree(ref ZIInitReqRes sReq, CallBack cbff, ref ZIDllRequestCalcTree sTree);
        DllDoReqCalcTree _fDoReqCalcTree;
...
Код:
            hModule = LoadLibrary(path);                                       
            IntPtr ProcAddr = GetProcAddress(hModule, "FtaDllDo");

            _fDoReqCalcTree = Marshal.GetDelegateForFunctionPointer(ProcAddr, typeof(DllDoReqCalcTree)) as DllDoReqCalcTree;


   rc = _fDoReqCalcTree(ref sInitReqRes, CBfunc, ref sTree);
   FreeLibrary(hModule);

Если следить за памятью через Task Manager, то после FreeLibrary память не возвращается к состоянию до LoadLibrary.
Причем на размер потерь влияет внутренняя работа DLL.

Как это можно объяснить? И есть ли пути борьбы с этим на стороне C#?

Спасибо.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 10-04-2012 07:05 » 

ezus, а причём тут C#? .NET не управляет unmanaged-памятью процесса, это находится целиком на совести WinAPI и DLL (и, конечно, программиста, который её писал).

С тем же успехом ты можешь проделать эти все действия на C++. .NET - не панацея от кривых внешних DLL.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
ezus
Опытный

il
Offline Offline

« Ответ #6 : 10-04-2012 08:00 » 

Это я прекрасно понимаю. Я имел ввиде, что можно ли решить проблему потери памяти в DLL извне?

Мы просто надеялись, что удаление библиотеки должно привести к освобождению ВСЕЙ использованной ей памяти.
В чем похоже мы ошибались.

Тогда придется задать вопрос: "Где можно по-подробнее познакомиться с распределением памяти при использовании DLL?"

Возможно ты сможешь просветить нас в этом вопросе?
Будем ОЧЕНЬ ОЧЕНЬ признательны.
Спасибо.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 10-04-2012 10:35 » 

ezus, память выделяется не в DLL, а в процессе. DLL - это лишь динамически загружаемая часть процесса, отображаемая в определённые места адресного пространства процесса (обычно в верхние адреса после 1,5 Гб для 32-хбитных процессов). Причём динамически загружается только сама DLL (т.е. код и статические данные), но если DLL работает с кучей, то это общая куча процесса (точнее, с точки зрения WinAPI, это страницы физической памяти, отображаемые в адресное пространство процесса в соответствии с работой менеджера памяти процессов).

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

Как вариант, написание собственного менеджера памяти, который бы предоставлял память для обслуживания DLL, а потом бы принудительно освобождал все выделенные для DLL блоки. Но это нетривиальное решение, потребующее переработки DLL: по меньшей мере, перехват всех способов аллокации памяти (C++: new, C: malloc/calloc/realloc, WinAPI: HeapAlloc/HeapReAlloc) и соответствующих им способов её освобождения.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
zubr
Команда клуба

by
Offline Offline
Пол: Мужской

« Ответ #8 : 10-04-2012 12:34 » 

Для очистки страниц памяти, можно заюзать функцию EmptyWorkingSet или SetProcessWorkingSetSize
Записан
ezus
Опытный

il
Offline Offline

« Ответ #9 : 11-04-2012 06:45 » 

Спасибо.
Проблема решена.

Меня подвела старость в виде древней информации в купе с доисторическим(времен MS DOS) недоверием к DLL того периода.
Как мне помнится, тогда DLL загружалась в свою память и реентерабельно использовалась различными процессами. Но возможно это также фантазии. Вот что значит перерыв в 20 лет, когда я работал в других системах.

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

Еще раз спасибо.

Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 11-04-2012 08:55 » 

ezus, так и сейчас DLL, грубо говоря, загружается 1 раз в страницу физической памяти, но эта страница отображается в адресные пространства всех процессов, которые используют DLL. Эта страница, разумеется, только для чтения: там код и статические данные; стек вызовов, куча и статически выделяемая память под переменные в каждом процессе свои.

А если говорить точнее, то ни DLL, ни EXE не загружаются в память с диска, а происходит отображение дискового файла в адресное пространство процесса. В частности, это позволяет запустить EXE, не дожидаясь его полного прочтения с диска. Оперативная память рассматривается как кэш дискового файла. Отдалённо это напоминает своп и оверлеи.

И так это работает, начиная с Win95/WinNT 4.0.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
ezus
Опытный

il
Offline Offline

« Ответ #11 : 11-04-2012 09:38 » 

Да, в общем-то, так и должно быть.
Спасибо за консультацию.
Записан
resource
Молодой специалист

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 11-04-2012 17:42 » 

Эта страница, разумеется, только для чтения
Это, разумеется, не так. Атрибуты страницы выставляются в соответствие с атрибутами секций DLL.

стек вызовов, куча и статически выделяемая память под переменные в каждом процессе свои.
Стек даётся не процессу, а потоку. Поэтому у каждого потока он свой.
Записан
ezus
Опытный

il
Offline Offline

« Ответ #13 : 02-05-2012 07:07 » 

Стек даётся не процессу, а потоку. Поэтому у каждого потока он свой.
А где выделяется память под стеки разных потоков, и как обеспечивается их НЕперекрываемость?
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #14 : 02-05-2012 10:23 » 

ezus, в процессе, конечно. Стеки имеют фиксированный размер. Обычно в Windows он составляет 2 Мб на поток. Под это в адресном пространстве процесса выделяются области. Но это не значит, что под это сразу же выделяется физическая память - она выделяется по потребности по страницам.
« Последнее редактирование: 02-05-2012 10:25 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
ezus
Опытный

il
Offline Offline

« Ответ #15 : 02-05-2012 12:30 » 

в процессе, конечно. Стеки имеют фиксированный размер. Обычно в Windows он составляет 2 Мб на поток.
Это надо понимать так. что стек не может превышать 2 Мб на поток? А это не маловато?
Записан
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #16 : 02-05-2012 13:48 » 

ezus, если надо, руками можно задавать размер стека для потока. Может, и маловато, но, в конечном итоге, стек-то резиновым быть не должен. Не куча, всё-таки.
Записан
ezus
Опытный

il
Offline Offline

« Ответ #17 : 02-05-2012 15:05 » 

Вад, к сожалению, рекурсия только через стек, а деревья бывают ужасть какой глубины
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #18 : 02-05-2012 16:43 » 

ezus, рекурсивный алгоритм можно развернуть в циклический с собственным стеком на базе массива или списка. И тогда можно ужасть сколько памяти под это извести - хоть гигабайт.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
ezus
Опытный

il
Offline Offline

« Ответ #19 : 03-05-2012 07:03 » 

Ладно, это уже разговор не о том.
Еще раз спасибо.
Записан
resource
Молодой специалист

ru
Offline Offline
Пол: Мужской

« Ответ #20 : 21-05-2012 21:28 » 

Значится так (Глеб Жеглов ©). Стек бывает ядерный и юзермодный. Юзермодный стек резервируется исходя из параметров бинаря. Т.е. линкер рулит этим делом (ну или тот, кто рулит линкером). Майкрософтовский (он же студийный) линкер устанавливает по умолчанию 1 мегабайт памяти зарезервированной под стек. Из них 4 килобайта выделяются сходу.

Мало или немало? 1 мегабайт стека - это очень много. Для сравнения, в ядре размер стека равен 12 килобайт, и ничего, живут люди. Если 1 мегабайт стека оказалось недостаточно для вашего софта, то скорее всего вы набыдлокодили.
Записан
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines