Передать строку в dll c
При создании проекта DLL, автоматически генерируемый средой комментарий предупреждает, что передавать между приложением и библиотекой параметры типа String так просто нельзя, и предлагает объявлять ShareMem первым в списке юнитов проекта либо использовать типы PChar или ShortString. Постараюсь подробнее описать, из-за чего возникает такая необходимость, и какие тут есть проблемы, а также методы их решения, однако все тонкости типа String рассматриваться не будут.
Суть проблемы:
1. Приложение и библиотека содержат свой собственный экземпляр менеджера памяти, который представляет собой структуры данных, хранящие информацию о выделенных и свободных блоках памяти, и набор функций для выделения/освобождения памяти, которые с этими данными работают.
2. Менеджер памяти не может освободить или перераспределить блок, который он не выделял, так как в его записях нет информации об этом блоке.
3. При работе с типом String неявно происходит интенсивная работа с менеджером памяти, компилятор автоматически вставляет код для выделения, перераспределения и освобождения памяти под строку, а сама переменная типа String является лишь указателем на память, где строка хранится. Для работы с памятью используются функции, предоставляемые менеджером. В результате при передаче String-ов между приложением и библиотекой зачастую получается, что одна из сторон имеет дело с памятью, информации о которой нет в записях менеджера. Первое же обращение к менеджеру памяти в этом случае приведет к исключению.
Методы решения:
1. Передавать ShortString или String[x] вместо String. ShortString - это обыкновенный статический массив (максимальная длина - 255 символов). Динамическая память не используется, неявных обращений к менеджеру памяти нет. Недостаток – ограничение длины строки 255 символами.
2. Передавать PChar, но если планируется передавать String под видом PChar, есть некоторые оговорки. Нужно помнить, что String – это финализируемый тип, память под строку автоматически освобождается при потери последней ссылки на строку, а когда мы переходим к PChar компилятор перестает следить за ссылками. Вот так, например, в экспортной функции DLL делать нельзя:
Когда придет время использовать указатель, возвращенный MyFunc, если повезет, то он все еще будет указывать на корректный буфер строки, однако не факт, в этой области памяти могут уже быть совсем другие данные или память может быть возвращена системе (в этом случае получим AV).
Однако нижеприведенный код, будет работать (если конечно приложение не захочет использовать полученный указатель после выгрузки библиотеки с помощью FreeLibrary):
Разница в том, что строковые литералы, коим является 'Some String', хранятся не в динамической памяти, а в статической памяти исполняемого модуля (exe или dll), и указатель на строку валиден все время пока исполняемый модуль загружен в адресное пространство процесса.
При передаче строк типа PChar важно следить, чтобы выделял и освобождал память один и тот же менеджер. Если для работы с памятью используется менеджер Delphi (функции GetMem/FreeMem), то лучше поступить так, как обычно принято в WinAPI: выделяет и освобождает память вызывающая сторона (приложение), а библиотека работает с уже подготовленным буфером. Либо отказаться от менеджера памяти Delphi и работать с памятью через системные функции LocalAlloc/LocalFree или HeapAlloc/HeapFree. В этом случае не важно кто выделяет и освобождает память, так как в любом случае менеджер памяти общий – не дельфийский, а системный.
3. Передавать WideString. Очень простой вариант, работать с WideString так же просто, как и со String, за памятью следить не нужно, она автоматически выделяется и освобождается. Однако при работе с этим типом дельфийский менеджер памяти не используется, используются системные функции. Единственный недостаток в том, что при присвоении String-у WideString-а и наоборот происходит автоматически перекодировка ANSIUNICODE. При передаче String-ов между библиотекой и приложением в виде WideString будет тратиться процессорное время на перекодировку в UNICODE и обратно.
Как передать строку в DLL написанную на C++ ?
. опишу что уже пробовал:
Есть программа на делфи
в нете много похожих тем, но я не увидел подходящего решения;
понял что в делфи и си работа с памятью идёт наоборот, думаю это исправил stdcall ;
пробовал посылать в dllуказатель на строку, как это любит С, но толку не увидел;
ещё думаю что проблема в string так ка он 4 байта весит а в С 1;
помогите разобраться, я уже запутался окончательно.
У вас же указатель,поэтому не 1,а 4.Что у вас не так работает?Может быть,проект на C++ в Unicode
ANSIString пихали али что?
Описание строковых типов:
Тип String: по смещению -4 храниться длина строки, а по смещению -8 храниться счётчик ссылок на строку (когда он обнуляется то строка уничтожается) Сама строка располагается в памяти как есть (каждая буква занимает 1 байт).При копировании строки (s1:= s2;) реального копирования не происходит, увеличивается только счётчик ссылок, но если после этого изменить одну из строк, (s1:= s1 + 'a';) то произойдёт физическое копирование содержимого строк, и теперь s1 и s2 будут показывать на разные адреса в памяти.
Примечание Fantasist'a: Это верно только если s1 - локальная переменная, или s1 и s2 - обе не локальные. Если s1 не локальная (т.е. глобальная или член класса), а s2 - локальная то происходит копирование.
Соглашения об вызовах и преобразованиях типов обычно способны разрешить конфликты и несовместимость между C++ и PASCAL. Хороший ресурс по вопросам совместимости между Delphi и C++ расположен на сайте Borland.
Alegun, Спасибо за столь обьемный ответ.
Я смотрел через sizeof(string) в делфи, то мне выдаёт 4 байта, тоисть как я понимаю это масив элементов по 4 байта,
по вашим словам, как я понял, мне надо будет в Си сместится в моём char *str на 8 байт, где и будетсама строка, так?
Но почему же когда я выводил строку через API MessageBox в длл, то мне не вывело мою строку, а непонятный набор символов, ведь конец файла это '\0' ?
В боксе я вижу первую букву, значит следующая - нуль.
Походу делфи дает Си указатель на копию символа, так как даже PString не помог. И сразу после уведомления месыджбокса идёт acces volation, тоисть С++ пробует найти конец строки, а система ему говорит: "не твоё не лезь".
Идеи ещё есть?
Воспроизвести Вашу проблему не могу, т.к. нет Вашей dll на С++.
Однако ж, может помогут вопросы:
1. dll ожидает ASCII-строку или однобайтовую unicode-строку?
2. Вы передаёте английский текст или русский?
3. Пролог-программа в GUI или консольная?
4. Если в консольная, то наверное надо посмотреть в цели (goal) какую кодировку строк использует консоль.
5. Какой версией Пролога пользуетесь? Наверное VIP7?
есть такие приметы у программистов:
1. Если видна только первая буква(цифра и тп), то вы получили строку в UNICODE (UTF16), а смотрите её как ASCII. т.к. обычная буква в UNICODE - это буква и 0 (т.е. буква и конец строки)
2. Если вы видете иероглифы разные, то вам дали строку в ASCII, а вы пытаетесь её понять в UNICODE
это конечно не 100% но обычно достаточно понять в чём ошибка.
да, напоминаю. Все обычные строки в VIP - UNICODE UTF16.
Тут можно или использовать класс string8 и преобразовывать после получения, или в C/C++ использовать не char*, а wchar_t* (что кажется разумней)
Если вы об этом WideCharToMultiByte и MultiByteToWideChar - с русскими буквами не прокатывает,из пролога в opengl только таким способом мне получалось передавать строки.Я даже пробовал из с++ WideCharToMultiByte и MultiByteToWideChar переносить в пролог,тоже самое.
Вот так заработало только вместо русских иероглифы и куда надо передавать строку вместо pointer заменить string8.Кодировки перебрал не помогает.
String8=string8::mapFromString ( Source, CodePage).
Если String8 через setString8 вообще никакие символы не отображаются.
я ничего не понял. Можно прислать то, что можно запустить и посмотреть?
вот такое работает:
WideCharToMultiByte & MultiByteToWideChar должны работать всегда, все претензии к Микрософт.
Маленький совет:
Всегда, где возможно, надо использовать UNICODE. И только там, где абсолютно необходимо - string8. И никаких проблем не будет.
GLFWwindow* glfwCreateWindow ( int width,
int height,
const char * title,
GLFWmonitor * monitor,
GLFWwindow * share
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
glfwCreateWindow:(unsigned,unsigned,string8,pointe r,pointer)->pointer procedure language c as"glfwCreateWindow".
%%%%%%%%%%%%%%%%%%%
window3():-
String8=string8::mapFromString ( "stroka", 866),
_=glfwInit(),
Window = glfwCreateWindow(800, 800, String8, null, null),
glfwMakeContextCurrent(Window),
W=glfwWindowShouldClose(Window),
std::repeat,
if not(W>0) then
glfwSwapBuffers(Window),
glfwPollEvents()
end if;
glfwTerminate().
GLFWwindow* glfwCreateWindow ( int width,
int height,
const char * title,
GLFWmonitor * monitor,
GLFWwindow * share
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
glfwCreateWindow:(unsigned,unsigned,string8,pointe r,pointer)->pointer procedure language c as"glfwCreateWindow".
%%%%%%%%%%%%%%%%%%%
window3():-
String8=string8::mapFromString ( "stroka", 866),
_=glfwInit(),
Window = glfwCreateWindow(800, 800, String8, null, null),
glfwMakeContextCurrent(Window),
W=glfwWindowShouldClose(Window),
std::repeat,
if not(W>0) then
glfwSwapBuffers(Window),
glfwPollEvents()
end if;
glfwTerminate().
вместо "stroka" "строка"
если почитать описание ф-ии, то там написано
, т.е. там не ASCII а UTF8 - туда и надо конвертировать (используя предикат toUtf8).
2. я не уверен, за "language c" в строке "procedure language c as"glfwCreateWindow"." Это может быть и stdcall.
т.к. у меня нет этих функций, я не могу проверить.
Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2022, Jelsoft Enterprises Ltd.
Для описания и вызова функций из обычных dll на c/c++ используется класс Marshal.
Можно выделить память, преобразовать строку, структуру в неуправляемую память и обратно.
В примере вызывается функция GetComputerName, которая заполняет выделенную в программе память, а затем результат преобразуется в string и выводится на консоль.
так сложно из-за того, что внешняя dll требует указатель на область памяти не только на чтение, но и на запись. При таком подходе явно видно с какой памятью работает внешняя dll.
Если под IntPtr понимается LPWSTR, то два варианта. Либо также применить к возвращаемому значению Marshal.PtrToStringUni и получишь строку, но гораздо проще сразу описать во внешней функции тип возвращаемого значения:
[DllImport("mydll.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string MyFunc();
sb1.Append("Hello world");
sb2.Append(" world");
sbb = TestString(sb1, sb2);
C++:
extern "C" __declspec(dllexport)
char* __stdcall TestString(char* str1, char* str2)
char* str[4096];
strncat(str, str1, 4096);
strncat(str, str2, 4096);
return str;
>
Подскажите куда копать. Думаю, эту задачу можно разбить на несколько мелких вопросов. Ответ мне понравился. Наверное, его можно считать решением. Но я не могу сказать, что он меня удовлетворил и я нашел, что искал. Очень надеюсь на помощь.
либо так:
public static extern string TestString(string str1, string str2);
либо так:
public static extern string TestString(IntPtr str1, IntPtr str2);
А теперь практика:
Тестовая функция прибавит к первой строке вторую. Максимальный размер первой строки (то есть размер выделенного буфера) указывается в третьем параметре.
C++:
extern "C" __declspec(dllexport)
void __stdcall TestString(wchar_t * str1, const wchar_t* str2, size_t str1len)
wcsncat(str1, str2, str1len);
return;
>
string TestConcat(string str1, string str2)
// Выделяем буфер для склеивания строки во внешней функции
var pStr1 = Marshal.AllocHGlobal(4096);
// Формируем массив байт соответствующий строке str1
var data = Encoding.Unicode.GetBytes(str1);
Marshal.Copy(data, 0, pStr1, data.Length);
// Теперь у нас в памяти по указателю pStr1 выделено 4096 байт и записана строка str1
// Вызов внешней функции
TestString(pStr1, str2, new IntPtr(4096));
// Теперь декодируем полученный результат.
var result = Marshal.PtrToStringUni(pStr1);
// Обязательно освобождаем выделенную память
Marshal.FreeHGlobal(pStr1);
В рамках задачи написания dll-библиотеки, в которую собираюсь передавать строки, их изменять, и возвращать одну модифицированную, решаю вопрос с использованием типов данных. Пытаюсь в dll запихнуть подобное:
string MyFunc(string s1, string s2)
string s3 = s1 + s2;
return s3;
>
- не получается. Хочу уточнить, могу ли я как-то использовать string, или придется пользоваться либо массивом символов, либо указателем на символ?
и дальше в тексте либо std::string, либо using std.
@becks расскажите, почему? Чем по-вашему отличается вызов функции в dll от вызова функции в основной программе?
@becks вон вы о чём. Ключевая фраза по вашей ссылке -- "I can't control what compiler these plugins are built with". Сравните с вопросом автора: "В рамках задачи написания dll-библиотеки, в которую собираюсь передавать строки. ".
@jcmvbkbc, подскажите, пожалуйста, как вы собираетесь использовать подобную библиотечную функцию, допустим, в Delphi? Откуда компилятор узнает, что такое std::string, как у него организована память? Или, если ваша библиотека собрана, допустим, MINGW, а основное приложение на MSVC\Builder. Есть основные вещи из стандарта, которым должен следовать каждый компилятор, остальное на откуп производителя.
@jcmvbkbc Вы уж простите, но я Вам не чувак.
>класс string в dll С++
Из этой строчки вы поняли, что и dll и основное приложение написано на C++? Я понял, что dll написана на С++, а про основное приложение ничего не сказано. Это первое.
Второе. Тем не менее, даже, если речь шла только про С++, тут ничего не сказано про компиляторы. Это не будет работать (или скорее всего не будет работать) с разными компиляторами\версиями компилятора! Так что в любом случае Вы даете плохой совет.
Третье. Это уже мой совет автору. Используйте в dll только pod-типы. Внутри можно использовать все, что захотите, но снаружи должны быть видны только pod'ы. Это безопасно и универсально, избавит вас от головной боли в использовании библиотечки.
> я Вам не чувак
а было очень похоже, когда речь пошла про дельфи.
>> класс string в dll С++
> Из этой строчки вы поняли, что и dll и основное приложение написано на C++?
из этой строчки я понял, что dll пишется автором на C++. Основное приложение должно бы быть написано на С++ чтобы с лёгкостью передавать std::string куда-нибудь, иначе мы бы сначала увидели какой-нибудь другой вопрос, например, "как из дельфи передать в dll на С++ std::string".
> в любом случае Вы даете плохой совет
во-первых в моих ответах нет советов.
Во-вторых, не в любом же. В типичном случае использования одного компилятора всё будет работать нормально.
> Используйте в dll только pod-типы. Внутри можно использовать все, что захотите, но снаружи должны быть видны только pod'ы
вы бы тогда уж добавили, что функции видные снаружи должны быть хотя бы extern "C", а то ведь и mangling может быть разный, и конвенция вызова.
Я, признаться, не улавливаю здесь совершенно никакой корреляции.
И Вы цепляетесь исключительно к моему упоминанию delphi, отводя на второй план мною указанное возможное различие компиляторов. Если уж Вы сочли мою догадку в использовании автором dll не из С++ глупой, почему свою догадку про
вы не находите таковой? В постановке вопроса про один компилятор не слова. Ну и ваш типичный случай - это парниковый вариант.
Основное приложение должно бы быть написано на С++ чтобы с лёгкостью передавать std::string куда-нибудь, иначе мы бы сначала увидели какой-нибудь другой вопрос
"Основное приложение должно бы быть написано на С++"
WTF? С чего бы это оно должно и как это вы поняли?
вы бы тогда уж добавили, что функции видные снаружи должны быть хотя бы extern "C", а то ведь и mangling может быть разный, и конвенция вызова.
К счастью, все это было в моей ссылке выше, которую, конечно же, Вы прочитали? )
Я подытожу, спорить дальше смысла не вижу.
Если Вы(автор) только набиваете шишки, Вы можете пойти по такому пути. Когда кому-то еще потребуется использовать вашу библиотеку вы обязательно рано или поздно наткнетесь на проблемы.
Читайте также: