- Введение
- Importing a Function from a DLL
- Setting the Calling Convention of a Function
- Handling Return Types and Arguments
- Importing Functions with Structures
- Importing Functions with Strings
- Более сложные методы взаимодействия
- Функции «обратного вызова»
- Маршалинг вызова функций
- Выбор набора символов
- Определение собственных имен
- Конфликт зависимости имени от «кодировки»
- Форматы вызова функций
- Анализ возвращаемого значения и обработка ошибок
- О чем же собственно речь?
- Заключение
Введение
В этой статье мы обсудим взаимодействие с динамически подключаемыми библиотеками.
Если вы уже занимались программированием для . NET, вы наверняка заметили, что
использовали общую библиотеку, предоставляемую средой исполнения, и наверняка не делали ни
одного системного вызова. То есть вы не обращались к Windows API напрямую, а следовательно,
ваш код платформенно независим. А это, в свою очередь, означает, что он может быть выполнен на любой платформе, где
будет присутствовать среда исполнения . NET с общей библиотекой исполнения.
Это, конечно, здорово, но что же делать, если
вы строго ориентированы на платформу Windows, и вам необходимо использовать уже разработанный вами код? Вы, наверное, очень обрадуетесь, когда узнаете, что сделать это будет очень легко.
Сейчас я поведаю вам все тонкости этого процесса.
I found a lot of questions about it, but no one explains how I can use this.
I have this:
Can someone help me to understand why it gives me an error on the DllImport line and on the public static line?
Does anyone have an idea, what can I do?
Thank you.
14 gold badges140 silver badges204 bronze badges
asked Oct 18, 2013 at 13:22
You can’t declare an extern local method inside of a method, or any other method with an attribute. Move your DLL import into the class:
answered Oct 18, 2013 at 13:24
31 gold badges291 silver badges286 bronze badges
Starting with C# 9, your syntax would be valid if you remove the public keyword from the SetForegroundWindow deceleration:
In C# 9 local functions can have attributes, see here
answered Mar 24, 2021 at 13:16
2 silver badges7 bronze badges
Importing a Function from a DLL
hWnd text caption type
IntPtrZero
The MessageBox function takes four parameters: the handle of the window that owns the message box (in this case, IntPtr. Zero is used to indicate that there is no owner window), the text to display in the message box («Hello, world!»), the caption of the message box («Message»), and the type of message box to display (0).
When the Main method is called, it calls the MessageBox function with the specified parameters, which displays a message box on the screen.
Setting the Calling Convention of a Function
CallingConvention CallingConventionStdCall
arg1 arg2
args
result
Consoleresult
Here is another example, using the CallingConvention property set to Cdecl:
CallingConvention CallingConventionCdecl
arg1 arg2
args
result
Consoleresult
In this example, we have set the CallingConvention property to Cdecl, which is the calling convention used by the function.
It is important to note that you should always use the correct calling convention for the function you are using. If you are not sure what calling convention to use, consult the documentation for the function or the DLL.
Handling Return Types and Arguments
hWnd text caption type
args
hWnd IntPtrZero
text
caption
type
result hWnd text caption type
Consoleresult
We then call the MessageBox function with the required arguments and store the result in an int variable. Finally, we output the result to the console.
Note that the CharSet parameter is set to CharSet. Auto, which means that the function will use the ANSI or Unicode character set depending on the operating system.
Importing Functions with Structures
x
y
lpPoint
To call the GetCursorPos function, you can create a new instance of the POINT structure and pass it as a parameter:
point
point
Console pointx pointy
This code will print the current position of the cursor on the screen.
Importing Functions with Strings
hWnd text caption type
args
hWnd IntPtrZero
text
caption
type // OK button only
hWnd text caption type
To call the MessageBox function, we simply pass the required parameters to the function.
hWnd text caption type
milliseconds
args
hWnd IntPtrZero
text
caption
type // OK button only
hWnd text caption type
// wait for 5 seconds
A couple of points I stumbled on while creating a dll wrapper:
Therefore, my wrapper looks like this (as suggested in using a class defined in a c++ dll in c# code):
__declspec(dllexport) on the class level exports all public members of the class.
SDKCreate() returns a pointer to that CcnOCRsdk class from the unmanaged dll which member function I have to call.
CcnOCRsdk_HKID calls that member function. Note that the pointer to the CcnOCRsdk is passed.
After the code builds into the dll, I have to use the DUMPBIN to find out what are the ‘mangled’ entry points for the wrapper dll I wrote.
For my wrapper, the results look like this
Now I’m finally ready to use my wrapper in the C#. When I did this without specifying the entry point exactly as in my output from dumpbin, I got ‘unable to find entry point’ error.
The RECO_DATA is defined as JaredPar suggested.
And the last step is to enjoy the results.
First I have to call the class constructor, and then pass the pointer to the actual call to the function
RECO_DATA recoData = new cnOCRsdk. RECO_DATA();
string num = «262125355174»;
IntPtr ptr = cnOCRsdk. SDKCreate();
bool res = cnOCRsdk. CcnOCRsdk_HKID(ptr, num, out recoData);
My res returns true, and I get the results I expected in recoData.
Более сложные методы взаимодействия
С первого взгляда может показаться, что передача структур достаточно тривиальна. Чего тут, описал структуру,
передал, и дело в шляпе. Не тут-то было. Ведь по умолчанию все данные являются управляемыми,
то есть их размещением в памяти будет управлять среда исполнения . NET. И нет никакой гарантии того, что поля структуры
будут расположены в памяти последовательно друг за другом, как нам хочется. Скажу даже больше, среда . NET будет размещать поля структуры в памяти, руководствуясь в первую очередь правилами оптимизации.
Вследствие чего могут возникнуть
очень неприятные ошибки, связанные с распределением памяти. Для того чтобы управлять размещением структур в памяти, используется атрибут LayoutKind. Данный атрибут может принимать три значения, перечисленные ниже:
Значения атрибута LayoutKind
Ну а теперь, как обычно смотрите примерчик:
Функции «обратного вызова»
Многие стандартные API функции в качестве аргумента принимают указатель на функцию. Для примера можно привести CreateThread,
EnumPrinters, SetWindowsHookEx, EnumWindows и многие другие не менее полезные функции.
Данный механизм, благодаря своей гибкости, позволяет настраивать поведение API функций в широких пределах. Разработчики среды исполнения . NET не забыли подумать и о нем,
мы с легкостью сможем применять этот механизм в наших приложениях для . NET.
Если вам все еще не понятно, о чем я тут толкую, взгляните на картинку.
Обратный вызов функций
Давайте рассмотрим, что нам придется для этого сделать.
Что бы вам было более понятно, я приведу пример.
У вас может возникнуть вопрос: каким же образом код из DLL вызывает код из среды . NET? Что, не
видите никаких проблем? А дело вот в чем. Ведь код . NET и родной код системы кардинально
различаются, и поэтому в принципе не могут вызывать друг друга. Для того чтобы решить эту проблему,
стандартный маршалер поступает следующим образом: он создает маленькую native-функцию.
Которая занимается только тем, что изменяет параметры и вызывает реальную . NET. Именно адрес этой функции передаётся в DLL. Таким образом, для вызова функций . NET из DLL используются переходники.
Передачаприём параметров на C# из внешней С++ dll-и
Привет коллегам.
Есть внешняя DLL с исходниками, написанная на С++.
Main.cpp выглядит так:
// Описание класса C_class()
Re: Передачаприём параметров на C# из внешней С++ dll-и
По-моему можно написать врапперы на managed c++
Схема следующая (успешно используется в MAPI):
Про то, как описывать и работать через COM-интерфейсы читать
2 Andrbig
Спасибо за обьяснения.
Собственно, насколько я понял, в C# я должен описать интерфейс:
затем реализвать на его основе класс
Указатель на интерфейс из DLL я вроде получил:
А вот как его дальше связать с интерфейсом(классом) описанным в C#?
Почти. Методы базового интерфейса описывать не надо. Судя по всему, твой интерфейс происходит от IUnknown (уж извини, я его назвал в C#-стиле, без всяких _). Тогда так:
Твой интерфейс в С++ коде тоже должен быть порожден от IUnknown. Не знаю точно, как там это на С++, но видимо как-то так:
Кроме того, твой интерфейс должен иметь IID 00020300-0000-0000-C000-000000000046 (как указано в декларации в С#).
или можно определить общий интерфейс?
Спасибо.
Это идентификатор интерфейса. Откуда он берется в С++ — не знаю, но знаю, что .net вызовет QueryInterface с этим идентификатором (раза 3, не меньше). Попробуй, может заработает вовсе без Guid(«00020300-0000-0000-C000-000000000046»).
Не онял где тут обертка. . Net будет вызывать по vtable и пофиг, откуда тот возьмется — из интерфейса или прямо из класса. Обращаю твое внимание, что приведенный тобой код не заработает, ибо говорится — «You must implement IUnknown as part of every interface», а ты этого не сделал. Вот правильный код:
данное сообщение получено с www.gotdotnet.ru
ссылка на оригинальное сообщение
Нашёл в инете такое решение моей задачи, насколько я понял, без привлечения COM, используя только Interop. По крайней мере, у меня на данном этапе стали отзываться функции DLL
So say you had a C++ class:
Now you’d compile that to a DLL using the Clr compiler option, and you can add that dll as an assembly in a managed project.
Next get a program called Dependancy Walker (http://www.dependencywalker.com/). It will give you the name of the functions within a DLL. Since C++ uses name mangling, you need this program to tell you the «name» of the function you want to use in a managed environment. It will be something like:
and get the name for the CreateMyClass function too.
After that you need to create the same class «MyClass» in your managed project and import the functionality from the previously generated . NET compatible DLL using the DllImport attribute:
Пока на собственное сообщение не было ответов, его можно удалить.
Source.zip — 30 KB
Маршалинг вызова функций
Давайте посмотрим несколько глубже: ведь все данные, которые вы используете в среде . NET, являются управляемыми,
то есть их расположением в памяти управляет среда исполнения . NET.
А обычные библиотеки, в свою очередь,
ничего не знают ни о среде исполнения, ни о типах данных, которые вы используете. Чтобы разрешить данную
проблему, в игру вступает процесс, называемый маршалингом (маршалинг от английского marshaling, что в переводе означает выстраивать в порядке, переносить).
Этот процесс позволяет перенести ваш вызов из среды исполнения . NET на уровень операционной системы,
непосредственно к самим библиотекам. А происходит это так.

Маршалинг вызова функций
Давайте рассмотрим процесс вызова функции из DLL по порядку.
ПРИМЕЧАНИЕ
Если в ходе исполнения функции в DLL возникнет необработанное исключение, то
среда исполнения преобразует его в исключение . NET, и вы сможете его обработать
в своем коде стандартными средствами . NET, если это будет необходимо.
Выбор набора символов
Вот что получилось у меня:
451 1C2 00013581 MessageBeep
452 1C3 000275D5 MessageBoxA
453 1C4 000275FD MessageBoxExA
454 1C5 000222CC MessageBoxExW
455 1C6 00048FF3 MessageBoxIndirectA
456 1C7 0002DDD1 MessageBoxIndirectW
457 1C8 0001FE1C MessageBoxW
458 1C9 00015126 ModifyMenuA
Вы с легкостью сможете убедиться, что там присутствуют функции MessageBoxA и MessageBoxW. Первая используется для строк ANSI, вторая — для строк Unicode. Стандартный маршалер системных вызовов . NET знает об этом, и если он не найдет в библиотеке фунцкию с заданным именем, то он автоматически добавит к имени постфикс W или A, и будет искать функции с таким именем. Если вы хотите вызвать функцию для конкретного набора символов, то вам поможет параметр CharSet, который
по умолчанию равен Charset. Ansi. Этот атрибут может принимать три значения: Unicode, Ansi и Auto. Здесь, наверное, надо
оговориться только по поводу Auto. Этот параметр предписывает маршалеру самостоятельно выбрать нужную функцию.
Вы можете задавать эти параметры следующим образом.
Вы должны понимать, что основное назначение этого атрибута
не в том, чтобы подбирать функции по именам, а в том, чтобы настроить режим конвертирования строк, которые будут участвовать
при передаче параметров, в функцию. Грубо говоря, этот параметр отвечает за то, к какому набору символов будет преобразована строка при передаче в функцию.
Для увеличения скорости маршалинга нужно правильно использовать параметры Unicode и ANSI. Если типы передаваемых строк совпадут, то маршалеру не понадобиться преобразовывать данные
из Unocode в ANSI или обратно, тем самым вы увеличите скорость маршалинга.
ПРИМЕЧАНИЕЯ бы вам советовал использовать везде, где только можно, набор символов Unicode, так как внутри вся система (здесь имеется ввиду Windows 2000) построена на Unicode, а ANSI-функции являются всего лишь заглушками
для преобразования ANSI в Unicode.
Определение собственных имен
Разработчики стандартного маршалера явно думали о нас, и поэтому нам предоставлена такая нужная и полезная возможность как переименовывание функций. То есть вы можете импортировать функцию, присвоив ей другое имя.
Эта возможность реализуется при помощи атрибута EntryPoint. Приведем пример:
Атрибут EntryPoint ведет себя немного хитрее, чем кажется, его поведение меняется в зависимости от формата строки, назначенной ему:
Таким образом, вы можете импортировать функцию из динамической библиотеки и объявить ей любое имя.
Данная возможность с первого взгляда может показаться не особо нужной. Но это на самом деле не так.
Представьте себе, что вам нужно импортировать функцию в определенное пространство имен, а она
конфликтует с уже существующим в нём именем. Как раз такие проблемы и призвана решить эта возможность.
Конфликт зависимости имени от «кодировки»
Большинство создателей динамически загружаемых библиотек не придерживается стандартов именования функций в соответствии с поддерживаемым набором символов (Unicode или ANSI), то есть они попросту не ставят постфиксов W и A. Если, к примеру, создатель библиотеки решил, что она будет работать с ANSI, то будьте уверены: ничто не заставит его сделать заглушки для Unicode. Тут-то и может возникнуть проблема, когда нужно вызвать функцию для соответствующего набора символов, которая не имеет нужного постфикса. Нам на помощь придет атрибут ExactSpelling, который
запретит (разрешит) маршалеру изменять определенное нами имя функции, при помощи атрибута EntryPoint.
А делается это вот так:
ПРИМЕЧАНИЕ
Параметр EntryPoint обязателен.
По умолчанию значение атрибута ExactSpelling равно False. При значении False маршалер подставляет в имени постфикс W или
A, в зависимости от значения параметра CharSet, а при значении True ему не позволено изменять имя функции.
Обязательно обратите внимание на заданный для функции набор символов, он должен совпадать с реальным. То есть в данном случае вам самим
предстоит выбирать между Unicode и ANSI. Если вы неправильно зададите кодировку, то ваш код попросту будет работать неправильно.
Для того чтобы это понять, поробуйте задать EntryPoint=»MessageBoxW», а CharSet=CharSet. Ansi. Или наоборот. Результат будет довольно интересный. У меня получилось вот что:
Форматы вызова функций
Существует несколько соглашений о вызове функции (call convention), я надеюсь что вы все представляете себе, что это такое. Если нет, то я поясню: грубо говоря, это набор правил, по которым
передаются параметры и возвращаемое значение. Вы можете самостоятельно задать соглашение о вызове импортируемой функции
при помощи атрибута CallingConvention. Этот атрибут иногда бывает просто жизненно необходим. Например, если вам потребовалось вызвать фукцию, подобную printf (она имеет переменное число аргументов и использует формат вызова cdecl). Как это делается, я продемонстрировал ниже.
Не забудьте задать атрибут CallingConvention. Cdecl, так как если вы этого не сделаете, может произойти ошибка при вызове функции или после. Этот формат вызова подразумевает, что стек очищается не самой функцией, а тем, кто ее вызывает.
Анализ возвращаемого значения и обработка ошибок
По умолчанию после вызова функции анализируется возвращаемое значение и если делается вывод о том,
что функция потерпела неудачу, то среда исполнения генерирует исключение. Чтобы этого избежать нужно использовать атрибут PreserveSig, который заставляет среду исполнения игнорировать возвращаемое значение.
Если возвращаемое значение не равно S_OK, то считается, что функция не смогла успешно выполнить свою работу.
Этот атрибут не так бесполезен, как кажется. Потому что значение функции может быть совершенно правильным, а среда исполнения, посчитав его «инвалидным», будет генерировать исключения при каждом вызове этой функции. Так что,
по-вашему, будет легче: отключить генерацию исключений или при каждом вызове их отлавливать и обрабатывать?
В дополнении ко всему сказанному, существует еще один атрибут: SetLastError. Он позволяет получить информацию
об ошибке после вызова функции. Если его значение равно False, то вы не сможете получить значение кода ошибки при помощи функции GetLastError, потому что стандартный маршалер «любезно» заберет это значение себе. Для того чтобы обрабатывать ошибки самостоятельно, установите значение этого атрибута равным True.
Значение по умолчанию для этого параметра для разных языков приведены в таблице ниже.
Значение по умолчанию для SetLastError
Если вы пишете на MSIL, то вам нужно будет использовать ключевое слово lasterr. То есть написать следующее:
. ( )
int32 MessageBox(int32 hWnd,
string text,
string caption,
unsigned int32 type)
О чем же собственно речь?
Допустим, вы хотите самостоятельно вызвать некоторую функцию Windows API. Для этого вам надо
будет знать, в какой библиотеке она размещена. Вы можете узнать это, найдя данную функцию
в Platform SDK и посмотрев в разделе Requirements значение пункта Library. Когда имя библиотеки найдено, можно считать, что пол дела уже сделано. Далее сделаем следующее.
В статье я буду рассказывать об аттрибуте DllImport, но взгянув на код, написанный на MSIL, вы,
к своему удивлению, не найдёте там даже упоминания об этом аттрибуте. Это нормально. Язык MSIL имеет встроенные
возможности по взаимодействию с DLL при помощи ключевых слов.
Когда будете писать собственную программу,
не забудьте, что необходимо подключить пространство имен System. Runtime. InteropServices
Заключение
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы
то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских
прав.





