Как добавить dll в проект visual studio c

Обновлено: 07.07.2024

Как же подключить/загрузить внешнюю динамическую библиотеку в свою программу/проект, написанную на языке программирования C++ в IDE Microsoft Visual Studio, в ОС Microsoft Windows?

Состав

Типы файлов

Для начала, давайте разберёмся, что обычно представляет собой любая динамическая библиотека, написанная на, и созданная для программ С++? Это от 1 до 4 типов файлов:

Из всего списка главным являются только файлы с расширением .dll, т.е. остальных файлов может и не быть.

Количество

У одной библиотеки может быть множество файлов таких типов. Все они, обычно, рассортированы по соответствующим папкам, чтобы было легче подключать в IDE.

Вариации

Файлы dll, lib и exp различаются по платформе (ОС), архитектуре, конфигурации, etc. В одной версии библиотеки может предоставляться сразу несколько альтернативных вариаций одинаковых по функционалу файлов. E.g.:

Всё это крайне важно, всё надо учитывать при подключении библиотеки к проекту. Свойства проекта в Visual C++ могут устанавливаться отдельно для каждой конфигурации (Release, Debug) и архитектуры/платформы (x32 (x86), x64).

Подключение

Перед тем, как подключать файлы библиотеки к своей программе, надо сперва открыть необходимый проект в IDE Visual Studio: Главное меню > Файл > Открыть проект.

Заголовочные файлы (.h)

Файлы таблиц импорта и экспорта (.lib, .exp)

Файл таблицы импорта (.lib)

Помимо указания IDE директории расположения файлов таблиц импорта (.lib), их также для линковки надо дополнительно явно указать. Сделать это можно двумя способами:

      Прописать полные названия (путь, название и расширение) .lib файлов сюда: Главное меню > Проект > Свойства проекта > Свойства конфигурации > Компоновщик > Ввод > Дополнительные зависимости. Каждая библиотека в отдельной строке. Кавычки необязательны.
      В итоге они будут вписаны автоматически в строку в двойных кавычках через точку с запятой (;). E.g.:

    Можно использовать сразу два способа.

    Я рекомендую использовать второй вариант, когда все подключения пишутся непосредственно в коде. Так нагляднее и переносимость кода увеличивается, когда всё, что нужно написано в самом коде, а не где-то там в настройках IDE.

    Файлы библиотек (.dll)

    Параметры конфигурации препроцессора

    Также не забывайте указывать параметры конфигурации препроцессора тут: Главное меню > Проект > Свойства проекта > Свойства конфигурации > C/C++ > Препроцессор > Определения препроцессора. Параметры являются комбинированием в определённой последовательности определённых литералов. Вот некоторые из них:

    Некоторые возможные варианты параметров:

    • MT_StaticRelease
    • MTd_StaticDebug
    • MD_DynamicRelease

    Это далеко не все возможные параметры.

    Примечания

    Пути директории указывать без конечного слеша.

    Опция Компоновщик
    Библиотеки

    Заключение

    На самом деле, подключать сразу столько всего зачастую не нужно. Как правило, предоставляется только исходный код и всё. Приходится самостоятельно из исходников компилировать библиотеки (файлы .dll и .lib). Т.е. нужно будет указать только заголовочные файлы .h, которые являются частью исходников.

    Использование DLL в программе на Visual C++

    Многие знают, что существует два основных способа подключить DLL к программе - явный и неявный.

    При неявном подключении (implicit linking) линкеру передается библиотека импорта (обычно имеет расширение lib), содержащая список переменных и функций DLL, которые могут использовать приложения. Обнаружив, что программа обращается хотя бы к одной из них, линкер добавляет в целевой exe-файл таблицу импорта . Таблица импорта содержит список всех DLL, которые использует программа, с указанием конкретных переменных и функций, к которым она обращается. Позже, когда exe-файл будет запущен, загрузчик проецирует все DLL, перечисленные в таблице импорта, на адресное пространство процесса; в случае неудачи весь процесс немедленно завершается.

    При явном подключении (explicit linking) приложение вызывает функцию LoadLibrary, чтобы загрузить DLL, затем использует функцию GetProcAddress, чтобы получить указатели на требуемые функции (или переменные), а по окончании работы с ними вызывает FreeLibrary, чтобы выгрузить библиотеку и освободить занимаемые ею ресурсы.

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

    Теперь рассмотрим, как каждый из перечисленных методов используется на практике. Для этого будем считать, что у нас есть библиотека MyDll.dll, которая экспортирует переменную Var, функцию Function и класс Class. Их объявления содержатся в заголовочном файле MyDll.h, который выглядит следующим образом:

    Кроме того, будем считать, что библиотека импорта содержится в файле MyDll.lib.

    Неявное подключение

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

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

    Явное подключение

    Загрузка DLL

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

    В нашем примере загрузка DLL выглядит так.

    Вызов функций

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

    Обратите внимание на приведение указателя к ссылке на тип FARPROC. FARPROC - это указатель на функцию, которая не принимает параметров и возвращает int. Именно такой указатель возвращает функция GetProcAddress. Приведение типа необходимо, чтобы умиротворить компилятор, который строго следит за соответствием типов параметров оператора присваивания. Альтернативный подход заключается в использовании оператора typedef с последующим приведением значения, возвращаемого GetProcAddress, к указателю на функцию с нужным прототипом.

    Доступ к переменным

    Хотя это не всегда очевидно из документации, получить указатель на переменную из DLL можно, используя все ту же функцию GetProcAddress. В нашем примере это выглядит так.

    Использование классов

    Сразу замечу, что в общем случае не рекомендуется размещать классы в библиотеках, подключаемых явно. Приемлемым можно считать только подход, который исповедует COM, при котором объекты класса создаются и разрушаются внутри DLL (для этого используются экспортируемые глобальные функции), а сам класс содержит исключительно виртуальные методы.

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

    Сначала задумаемся, почему объекты класса из явно подключаемой библиотеки нельзя использовать, как обычно. Дело в том, что при создании объекта класса компилятор генерирует вызов его конструктора. Но линкер не может разрешить этот вызов, поскольку адрес конструктора будет известен только в процессе выполнения программы. В результате сборка программы закончится неудачно. Такая же проблема возникает при вызове невиртуальных методов класса. С другой стороны, вызов виртуальных методов возможен, так как он осуществляется через таблицу виртуальных функций (vtable). Так, следующий фрагмент откомпилируется и слинкуется нормально (хотя, конечно, вызовет ошибку в процессе выполнения):

    Приведенные выше рассуждения подсказывают решение проблемы. Коль скоро неявный вызов конструктра невозможен, мы можем вызвать его вручную, предварительно получив его адрес и выделив память под объект. Затем можно вызывать невиртуальные методы, получая их адреса с помощью GetProcAddress. Виртуальные методы можно вызывать, как обычно. Кроме того, необходимо не забыть вручную вызвать деструктор объекта, прежде чем выделенная для него память будет освобождена.

    Продемонстрирую все сказанное на примере. Сначала мы выделяем память для объекта и вызываем для него конструктор. Память можно выделить как на стеке, так и в куче (с помощью оператора new). Рассмотрим оба варианта.

    Обратите внимание на использование операторов .* и ->* для вызова функции-члена класса по указателю на нее. Этими операторами мы будем пользоваться и дальше.

    ПРИМЕЧАНИЕ

    Как правило, имена функций, экспортируемых из DLL, искажаются линкером. Поэтому вместо понятного имени, такого как "Constructor", получается совершенно нечитабельное имя вида "??0Class@@QAE@XZ". В рассматриваемом примере я назначил переменным и функциям нормальные имена при помощи def-файла следующего содержания:

    Невиртуальные методы класса вызываются так же, как и конструктор, например:

    Виртуальные методы вызываются непосредственно (как это делается для обычных классов). Хотя DLL и экспортирует их, явно получать их адреса с помощью GetProcAddress не требуется. Отсюда следует вывод: если все методы класса являются виртуальными, использование объектов класса из явно подключаемой библиотеки практически ничем не отличается от использования объектов любого другого класса. Разница только в том, что конструктор и деструктор для таких объектов придется вызывать вручную.

    В нашем примере виртуальная функция вызывается так.

    После того, как работа с объектом завершена, его нужно уничтожить, вызвав для него деструктор. Если объект был создан на стеке, деструктор необходимо вызвать до его выхода из области видимости, иначе возможны неприятные последствия (например, утечки памяти). Если объект был распределен при помощи new, его необходимо уничтожить перед вызовом delete. В нашем примере это выглядит так.

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

    Выгрузка библиотеки

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

    Отложенная загрузка

    Использование отложенной загрузки

    Вот и все. Теперь можно использовать функции и классы DLL прозрачно, как и в случае с неявным подключением. Единственная проблема возникает с переменными: их невозможно использовать напрямую. Дело в том, что при обращении к одной из функций в DLL мы на самом деле вызываем функцию __delayLoadHelper, которая и выполняет загрузку DLL (если она еще не загружена), затем получает адрес функции с помощью GetProcAddress и перенаправляет все последующие вызовы функции по этому адресу. Но при обращении к переменной вызова функции не происходит, а значит использовать __delayLoadHelper не удается.

    Проблема решается путем явного использования GetProcAddress при работе с переменными. Если DLL еще не загружена, ее придется загрузить явно с помощью LoadLibrary. Но если мы уже обращались к ее функциям и точно знаем, что она находится в памяти, мы можем получить ее дескриптор с помощью функции GetModuleHandle, которой необходимо передать имя DLL. В нашем примере это выглядит так.

    Выгрузка библиотеки

    Итак, мы установили, что при использовании отложенной загрузки DLL грузится в память, когда происходит обращение к одной из ее функций. Но в последствии нам может потребоваться выгрузить ее, чтобы не занимать зря системные ресурсы. Специально для этого предназначена функция __FUnloadDelayLoadedDLL, объявленная в файле Delayimp.h. Если вы планируете использовать ее, вам нужно задать еще один ключ линкера - /DELAY:UNLOAD. Например:

    Имя, которое вы передаете функции __FUnloadDelayLoadedDLL, должно в точности соответствовать имени, заданному в ключе /DELAYLOAD. Если, к примеру, передать ей "MYLIB.DLL" или "mylib.dll", библиотека останется в памяти.

    ПРЕДУПРЕЖДЕНИЕ

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

    Обработка исключений

    Как я уже говорил, в случае ошибки функция __delayLoadHelper возбуждает исключение. Если нужная DLL не обнаружена, возбуждается исключение с кодом VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND). Если в DLL не обнаружена требуемая функция, исключение будет иметь код VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND).

    ПРИМЕЧАНИЕ

    VcppException - это макрос, который используется для формирования кода ошибки в подсистеме Visual C++. Первый параметр задает "степень серьезности" ошибки, а второй - код исключения.

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

    Написанный вами фильтр исключений может также получить дополнительную информацию с помощью функции GetExceptionInformation. Эта функция возвращает указатель на структуру EXCEPTION_POINTERS. В ней содержится поле ExceptionRecord - указатель на структуру EXCEPTION_RECORD. А структура EXCEPTION_RECORD в свою очередь содержит поле ExceptionInformation[0], в которое __delayLoadHelper помещает указатель на структуру DelayLoadInfo, содержащую дополнительную информацию. Эта структура объявлена следующим образом (файл Delayimp.h).

    В частности, вы можете извлечь из нее имя DLL (поле szDll), а также имя или порядковый номер функции, вызов которой привел к исключению (поле dlp).

    Функции-ловушки

    Функции-ловушки должны иметь следующий прототип:

    Первый параметр функции содержит код уведомления или ошибки, второй - указатель на уже знакомую нам структуру DelayLoadInfo. Все возможные коды уведомления описаны в файле Delayimp.h при помощи следующего перечисления:

    В качестве примера приведу текст функции-ловушки, которая подменяет вызов функции SomeFunc на вызов функции YetAnotherFunc.

    папка references

    3. Если вы хотите добавить в проект свою или стороннюю библиотеку, то перейдите к пункту №7, если же вы хотите подключить сборку, входящую в состав FCL, тогда в меню Reference Manager выберите пункт Assemblies

    пункт меню Assemblies

    4. Выберите подпункт Framework

    пункт меню Framework

    5. В появившемся по центу списке выберите нужную вам сборку и нажмите кнопку OK.

    список сборок

    6. В папке References должна появиться ссылка на добавленный вами файл.

    ссылка на сборку

    7. Если вам требуется подключить в проект свою или чью-то скаченную библиотеку, то тогда, сначала, нажмите кнопку Browse

    кнопка Browse

    8. Откроется диалоговое окно. Перейдите в папку, которая содержит нужный вам dll файл, выберите его и нажмите кнопку Add

    диалоговое окно

    9. Выберите пункт Browse

    пункт меню Browse

    10. Убедитесь, что добавленный файл выбран и нажмите кнопку OK

    Reference Manager

    11. В папке References должна отобразиться ссылка на добавленный вами файл

    Рассмотрен по шагам процесс создания в Visual Studio файла динамически загружаемой библиотеки *.dll и методы вызова функций из неё. Все описанное далее делается на примере среды разработки Visual Studio 2003 Version 7.1.3088, но также подходит и к Visual Studio 2005. Для простоты папки создаваемых проектов будут находиться в директории C:\VSPROJ\.

    [Создание библиотеки DLL]

    1. File -> New -> Project, в дереве Project Types: выбираем Visual C++ Projects -> Win32, в окошке Templates: выбираем Win32 Console Project. В поле Name: вводим имя проекта для DLL, например MyDLL, в поле ввода Location: выбираем путь C:\VSPROJ (можно воспользоваться кнопкой Browse. ). Жмем ОК.

    DLL-VisualStudio01.PNG

    2. Появится окошко мастера настройки свойств проекта Win32 Application Wizard - MyDLL. Щелкаем на Application Settings, Application type: выбираем DLL, в Additional options: ставим галочку Empty project, жмем Finish.

    DLL-VisualStudio02.PNG

    3. Создадим заголовочный файл для модуля наших функций в создаваемой DLL. В дереве браузера проекта выбираем Header Files -> Add -> Add New Item. в дереве Categories: выбираем Visual C++ -> Code, в шаблонах Templates: выбираем Header File (.h). В поле Name: вводим любое имя файла, например mydllmodule, жмем Open.

    DLL-VisualStudio03.PNG

    namespace dllfuncs
    class DummyClass
    public :
    // Делаем бип
    static __declspec(dllexport) void ShortBeep ( void );

    Вариант без использования классов:

    __declspec(dllexport) void ShortBeep ( void );
    __declspec(dllexport) void Msg ( char * msgstr);

    4. Создадим файл для модуля наших функций в создаваемой DLL, в котором будет сам код функций. В дереве браузера проекта выбираем Source Files -> Add -> Add New Item. в дереве Categories: выбираем Visual C++ -> Code, в шаблонах Templates: выбираем C++ File (.cpp). В поле Name: вводим то же самое имя файла, которое вводили на шаге 3 - mydllmodule, жмем Open.

    DLL-VisualStudio04.PNG

    Создастся новый файл, к котором будет код функций, добавляемых в файл DLL. Вводим в него следующий текст:

    using namespace std;

    namespace dllfuncs
    void DummyClass::ShortBeep( void )
    Beep(1000,100); //частота 1000 Гц, длительность 100 мс
    >

    void DummyClass::Msg( char * msgstr)
    MessageBox(NULL, msgstr, "Message from DLL" , MB_OK);
    >
    >

    Вариант без использования классов:

    using namespace std;

    void ShortBeep( void )
    Beep(1000,100); //частота 1000 Гц, длительность 100 мс
    >

    void Msg( char * msgstr)
    MessageBox(NULL, msgstr, "Message from DLL" , MB_OK);
    >

    После всех этих действий появится папка C:\VSPROJ\MyDLL\, в которой будут находиться файлы mydllmodule.cpp и mydllmodule.h, а также конфигурационные файлы проекта MyDLL.

    5. Чтобы наш проект скомпилировался в DLL, это должно быть настроено в свойствах проекта. Проверим настройки: MyDLL -> Properties. -> Configuration Properties -> General -> Configuration Type должно быть установлено в Dynamic Library (.dll). Жмем OK.

    DLL-VisualStudio05.PNG

    6. Теперь скомпилируем нашу библиотеку Build -> Build MyDLL. В папке C:\VSPROJ\MyDLL\Debug появятся два файла MyDLL.dll и MyDLL.lib. Первый файл MyDLL.dll - динамически загружаемая библиотека наших функций, она должна находится в папке исполняемого файла, который использует эти функции (см. [Создание приложения, которое использует функции из DLL]). Второй файл MyDLL.lib - статическая библиотека, которая может быть присоединена на этапе компиляции к приложению, использующему функции проекта MyDLL (см. [Создание приложения, которое использует функции из статической библиотеки lib]).

    [Создание приложения, которое использует функции из загружаемой DLL]

    Теперь создадим простую демонстрационную программу, которая будет вызвать функции из нашей DLL.

    1. File -> New -> Project, в дереве Project Types: выбираем Visual C++ Projects -> Win32, в окошке Templates: выбираем Win32 Console Project. В поле Name: вводим имя проекта для приложения, использующего загрузку DLL, например DLLtest, в поле ввода Location: выбираем путь C:\VSPROJ (можно воспользоваться кнопкой Browse. ). Также выберем радиокнопку Add to Solution, это просто добавит в нашу группу проектов (в котором уже есть проект MyDLL) новый проект DLLtest. Жмем ОК.

    DLL-VisualStudio06.PNG

    2. Настроим свойства тестового приложения. Выберите тип приложения Console application и нажмите Finish.

    DLL-VisualStudio07.PNG

    После этого автоматически создастся папка C:\VSPROJ\DLLtest\, в ней появится файл DLLtest.cpp, и туда введется пустой код тела функции _tmain. В функцию _tmain мы впоследствии добавим вызовы функций из модуля MyDLL.

    3. Нужно настроить в проекте DLLtest ссылки на загружаемую DLL. Щелкаем правой кнопкой на папку DLLtest в дереве Solution Explorer - DLLtest, выберем Add Reference. откроется окно выбора внешних ссылок на загружаемые библиотеки. На закладке Projects выберите MyDLL c:\Vsproj\MyDLL\ и нажмите кнопку Select, а затем OK. В проекте DLLtest в дереве проекта появится папка References и подпапка MyDLL, в которой с помощью Object Browser можно просмотреть функции из нашей библиотеки MyDLL.

    DLL-VisualStudio08.PNG

    DLL-VisualStudio09.PNG

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

    4. Вставим в приложение вызовы функций Msg и ShortBeep, для этого добавим в модуль DLLtest.cpp включаемый заголовок mydllmodule.h, и установим добавочные пути поиска файлов заголовков проекта DLLtest. Жмем правую кнопку на DLLtest -> выбираем Properties -> Configuration Properties -> C/C++ -> General -> Additional Include Directories -> $(ProjectDir)\..\MyDLL и жмем OK.

    DLL-VisualStudio10.PNG

    Добавим в модуль DLLtest.cpp вызовы функций ShortBeep и Msg, код модуля DLLtest.cpp получится следующий:

    // DLLtest.cpp : здесь определена точка входа для консольного приложения.
    //

    using namespace std;

    int _tmain( int argc, _TCHAR* argv[])

    dllfuncs::DummyClass::ShortBeep();
    dllfuncs::DummyClass::Msg( "Hello, world!" );
    return 0;
    >

    Вариант без использования классов:

    // DLLtest.cpp : Defines the entry point for the console application.
    //

    using namespace std;

    int _tmain( int argc, _TCHAR* argv[])
    ShortBeep();
    Msg( "Hello, world!" );
    return 0;
    >

    [Пути поиска библиотек DLL при запуске приложения]

    При запуске приложения Windows выполняет поиск библиотек DLL в следующей последовательности:

    1. Каталог, в котором находится исполняемый модуль текущего процесса.
    2. Текущий каталог.
    3. Системный каталог Windows (обычно это папка System32). Путь к этому каталогу извлекается с помощью функции GetSystemDirectory.
    4. Каталог Windows. Путь к этому каталогу извлекается с помощью функции GetWindowsDirectory.
    5. Каталоги, указанные в переменной среды PATH.

    Примечание: переменная среды LIBPATH не используется.

    [Ошибки в проектах с DLL]

    1. <имя_программы> fatal error LNK1104: cannot open file 'путь_до_папки_проекта_DLL\Debug\имя_файла_библиотеки.lib', например:

    DLLtest fatal error LNK1104: cannot open file '\Vsproj\MyDLL\Debug\MyDLL.lib'

    DLL-VisualStudio12.PNG

    Проблема решается, если положить в путь поиска нужную DLL (для нашего примера файл MyDLL.dll нужно положить в папку C:\VSPROJ\DLLtest\). Самое лучшее решение - настроить команду в Post-Build Event, которая будет автоматически выполнить копирование DLL в папку отладки программы, которая использует эту DLL. Настраивается Post-Build Event просто, процесс по шагам, на нашем примере проектов MyDLL и DLLtest (предполагается, что обе папки проектов находятся на одном уровне в файловой системе, и входят в один Solution среды разработки Visual Studio, см. [3]):

    1. Сначала нужно настроить порядок компиляции проектов в Solution. DLL должна компилироваться первой. Порядок компиляции настраивается через свойства Solution, контекстное меню Project Build Order.

    DLL-VisualStudio13.PNG

    Порядок проектов в списке можно поменять с помощью закладки Dependencies, которая определяет, какой проект от какого зависит. Первым в списке в нашем примере должен быть проект MyDLL.

    2. Теперь осталось в проекте MyDLL настроить Post Build Event, копирующее результат компиляции - файл MyDLL.dll в папку Debug проекта DLLtest. Щелкаем правой кнопкой в Solution Explorer на проекте MyDLL, выбираем Properties -> Configuration Properties -> Build Events -> Post-Build Event -> вставляем в поле Command Line строку cmd /C copy /Y $(TargetPath) $(ProjectDir)\..\DLLtest\Debug\$(TargetFileName).

    DLL-VisualStudio14.PNG

    [Как отлаживать код DLL в Visual Studio]

    В IDE есть приятная возможность отладки DLL по тексту исходного кода, однако это настраивается не слишком очевидно. Процесс по шагам (на нашем примере проектов MyDLL и DLLtest).

    1. Сначала нужно задать при отладке стартовым приложением в Solution проект с DLL.

    2. Нужно убедиться, что оба проекта MyDLL и DLLtest скомпилированы в конфигурации для отладки Debug.

    3. Нужно настроить в свойствах проекта MyDLL запуск внешнего приложения, вызывающего нашу DLL. Щелкаем правой кнопкой в Solution Explorer на проекте MyDLL, выбираем Properties -> Configuration Properties -> Debugging -> в поле Command вводим строку $(ProjectDir)\..\DLLtest\Debug\DLLtest.exe.

    Теперь можно ставить точки останова в исходном коде DLL, и отлаживать его, как обычный код. Если у Вас не работает отладка по коду DLL, то возможно, что-то не так с символами отладки (отладочной информацией). Если отладочная информация по коду DLL недоступна, то точки останова в DLL будут в виде коричневых кружков со знаками вопроса. Разобраться с проблемой загрузки символов может помочь просмотр модулей Debug -> Windows -> Modules. На скриншоте показана как раз такая проблема с отладкой.

    Читайте также: