На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
[!] Как относитесь к модерированию на этом форуме? Выскажите свое мнение здесь
Модераторы: Qraizer
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
> Функция со строковым параметром в DLL для использования из Delphi
    Доброго времени суток. Забрел к вам из соседнего раздела.
    Имею следующую проблему - есть код на С++, перевести его на Delphi сложно но использовать его нужно именно из ПО на Delphi.
    C знаю плохо. Код формально работает, но при некоторых обстоятельствах вызывает непредсказуемое поведение (работает на Delphi 7, не работает на некоторых других компиляторах - вылет ошибки или хлам в данных).
    Функция обязана выполниться и вернуть 2 строки. Идеальный пример аналога - GetVolumeInformation() которая возвращает имя тома и название файловой системы.

    Код DLL:
    ExpandedWrap disabled
      #define EXTERN_DLL extern "C" __declspec(dllexport)
       
      EXTERN_DLL BOOL MyFunction(char *param1, char *param2)
      {
      //обнуляю значения
      strcpy(param1,"");
      strcpy(param2,"");
      //что-то выполняется
      strcat(param1, что-то здесь);
      strcat(param2, что-то здесь);
      return true;
      //попутно в коде обработчики ошибок которые return false, тогда в param1 строка описание ошибки.
      }


    Вызываю из Delphi:
    ExpandedWrap disabled
      function MyFunction(param1, param2:string):bool; stdcall; external 'my.dll' name 'MyFunction';
      var
         s1,s2:string;
      begin
           setlength(s1,128);
           setlength(s2,128);
           if MyFunction(s1, s2) then writeln('TRUE') else writeln('FALSE');
           writeln('s1= ',pansichar(s1));
           writeln('s2= ',pansichar(s2));
      end;

    За качество паскального кода не бить, работает оно только в этом виде при заранее установленных длинах строк. Любая попытка передачи PAnsiChar, var s:string и так далее приводила к тем или иным Access Violation. Тип функции stdcall, cdecl - проверялись оба. В варианте DLL когда экспортируется функция _MyFunction@8 то же самое.

    Прошу дать рабочий прототип C++ функции "как оно должно быть на самом деле" включая метод вызова strcpy, strcat для таких параметров (задолбался тыкать звездочки куда попало)
    Сообщение отредактировано: Виталь -
      Дельфийские строки передавать в DLL нельзя (можно только на чтение), т.к. менеджеры памяти разные, а у тебя идёт модификация. Вылез за пределы 128 и всё...

      Используй PAnsiChar. Память выдели с запасом, либо сделай, как во многих некоторых WinAPI-функциях - первый вызов с нулевой строкой рассчитывает размер и возвращает требуемое число байтов. Выделил память - вызывай смело.

      Внутри сишной функции перераспределения памяти не делать. Если оно действительно необходимо, параметр придётся делать **char, а в Дельфи var PAnsiChar или PPAnsiChar.
      Сообщение отредактировано: MBo -
        Цитата Виталь @
        Идеальный пример аналога - GetVolumeInformation()

        В "идеальных примерах" WinAPI кроме указателя на буфер строки также передается и его размер. Если требуемый размер превышает переданный, то функция либо 1) возвращает false, а GetLastError выдает что-то типа ERROR_INSUFFICIENT_BUFFER, либо 2) функция возвращает не BOOL, а код ошибки со значением != 0 (в этом случае возврат 0 означает ERROR_SUCCESS - успешное выполнение).
          Цитата MBo @
          Дельфийские строки передавать в DLL нельзя (можно только на чтение), т.к. менеджеры памяти разные, а у тебя идёт модификация. Вылез за пределы 128 и всё...

          А причём тут манагер памяти ?
          Строки Паскаля и С (насколько я помню) разные по формату.
          Поэтому их нельзя использовать вместо друг друга.
          Можно посоветовать вместо указателя на Паскальную строку
          передать указатель на массив char.
          А дальше в Паскальной программе с ним разбираться.
          Фактически нужно будет преобразовать строку С в строку Паскаля.
          Либо переделать ф-ию в dll, чтобы в качестве результата
          она формировала именно Паскальную строку.
          Сообщение отредактировано: ЫукпШ -
            Не, смотрите в чем дело. Для передачи указателя в Delphi используется конструкция var s:string а не просто s:string
            С точки зрения Delphi, если параметр передается без var при его изменении внутри процедуры наружу эти изменения не выходят.
            Но, как ни странно, именно при такой конструкции у меня C++ библиотека правильно пишет контент по нужным адресам в памяти.
            Это абсолютно не поддается логике но работает. Я пытался объявить параметры функции как var либо как pchar либо pansistring - получаю ошибку и вылет, причем не понимаю откуда эта ошибка может исходить

            По размеру - должно влазить, я объявил строку на 150 знаков, в нее пишется порядка 10-20
            Сообщение отредактировано: Виталь -
              >С точки зрения Delphi, если параметр передается без var при его изменении внутри процедуры наружу эти изменения не выходят.

              Если функция Дельфийская, то компилятор делает в ней локальную копию, и работа внутри функции идёт уже с ней. Но С++ функция об этом не знает.

              >Но, как ни странно, именно при такой конструкции у меня C++ библиотека правильно пишет контент по нужным адресам в памяти.

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

              >Я пытался объявить параметры функции как var либо как pchar либо pansistring - получаю ошибку и вылет,

              Метод венгерского товарища Тыка работает плохо. Лучше разобраться. И убедись, что объявление в С соответствет STDCALL (это зависит от дефайнов)

              Если гарантировано, что длина возвращаемых строк не превышает 254 байта, при том же С-шном объявлении можно так:

              ExpandedWrap disabled
                var
                  p1, p2: array[0..MAX_PATH] of AnsiChar;  //память на стеке автоматически выделена
                 
                p1[0] := #0;
                p2[0] := #0;
                if MyFunction(p1, p2)...
                //p1,p2 по многим операциям (присвоению) совмеcтимы со строками и PAnsiChar
                 
                 
                EXTERN_DLL BOOL MyFunction(char *param1, char *param2)
                {
                //что-то выполняется
                strcat(param1, что-то здесь);
                strcat(param2, что-то здесь);
                return true;
                }
                Цитата Виталь @
                Для передачи указателя в Delphi используется конструкция var s:string а не просто s:string

                Указателя на что? В дельфи тип string по сути представляет собой указатель char* на первый символ строки. Соотв-но var s:string это указатель на указатель, т.е. char**.

                Цитата Виталь @
                С точки зрения Delphi, если параметр передается без var при его изменении внутри процедуры наружу эти изменения не выходят.
                Но, как ни странно, именно при такой конструкции у меня C++ библиотека правильно пишет контент по нужным адресам в памяти

                Ничего странного в этом нет. Во-первых, в общем случае "наружу не выходят" только изменения значения самого параметра, т.е. если параметр представляет собой (явный или неявный) указатель на что либо, то изменения этого указателя наружу не выйдут. Но память по этому указателю м.б. изменена и соотв-но "выйдет наружу". Простой пример - дельфийские классы, наследники TObject, например TStringList: передавая его без var, ты не можешь передать наружу только изменения самого указателя на объект (например создать новый экземпляр или обнулить переданный). Но с переданным объектом ты можешь делать, что угодно, например, очистить список или заполнить его строками.
                Во-вторых, параметры типа string, ведут себя подобно другим указателям (нельзя изменить сам указатель, но при определенных условиях можно изменить содержимое памяти по этому указателю) с той разницей, что компилятор автоматически отслеживает попытки изменения содержимого строки "штатными способами" (через дельфийские строковые операторы и функции) и при наличии таких "штатных" изменений автоматически создает копию строки, чтобы изменение её содержимого не "вышли наружу". Но, во-первых, эта копия создается не до вызова функции, а внутри самой дельфийской функции, во-вторых, содержимое строки можно изменить "нештатными" методами - через PChar или передачей строки в функции, принимающие указатель.
                Резюме: в external функцию MyFunction(param1, param2:string) строки передаются как указатели на первый символ строки, и т.к. эта функция не дельфийская и никаких авто-копий строк не создает, то все изменения содержимого строк передаются "наружу".

                Цитата Виталь @
                Я пытался объявить параметры функции как var либо как pchar либо pansistring - получаю ошибку и вылет,

                Var и pansistring это явно не то, а pchar должно работать в дельфях до 2009. Чтобы работало во всех версиях, нужно в дельфи объявлять строки как s1,s2:AnsiString и передавать в функцию как PAnsiChar(s1). Если не выходить за пределы выделенного размера строки, то всё должно работать (иначе бы ни один "идеальный аналог" из WinAPI типа GetVolumeInformation не работал бы).
                Сообщение отредактировано: leo -
                  Чуть приблизился к разгадке. Прототип свиснул в GetWindowText()

                  Код библиотеки:
                  ExpandedWrap disabled
                    static char *myVAR1 = "1110000000";
                    static char *myVAR2 = "222";
                     
                    EXTERN_DLL_EXPORT BOOL getvar(LPSTR lpString, int nMaxCount)
                    {
                        strcpy(lpString,myVAR1);
                        return true;
                    }
                     
                    EXTERN_DLL_EXPORT BOOL MyFunction()
                    {
                        strcpy(myVAR1,"TEST");
                        return false;
                    }


                  Код на Delphi:
                  ExpandedWrap disabled
                    function MyFunction; stdcall; external 'my.dll' name 'MyFunction';
                    function getvar(lpString: PChar; nMaxCount: Integer): BOOL; stdcall; external 'my.dll' name 'getvar';
                    ...
                     
                         //if MyFunction then writeln('TRUE') else writeln('FALSE');      
                         s:='asdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasd';
                         getvar(Pchar(s),length(s));
                         writeln('getvar= "',(s),'"'); //pansichar


                  Проблема:
                  Вот такой код выводит на экран
                  Цитата
                  1110000000 sadasdasdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasd

                  Т.е. первые байты заполняются нормально, далее ноль, хвост можно откусить функцией pansichar(). Однако, если вызвать MyFunction, которая должна заполнить myVAR1 myVAR2 реальными значениями - строка TEST в переменной НЕ ПОЯВЛЯЕТСЯ. Если вместо strcpy поставить strcat - Access Violation. Почему такой бред? Мне нужно запихнуть в нужные переменные значения во время вызова MyFunction()
                  Сообщение отредактировано: Виталь -
                    Цитата Виталь @
                    Чуть приблизился к разгадке.

                    Ты почитай про форматы строк Паскаля и С.
                    И тогда действительно приблизишься к разгадке.
                    ---
                    Первый байт Паскальной строки - размер данных строки.
                    Выделенные байты для неё могут занимать до 256, но при этом
                    данных может не быть совсем. (самый первый байт - это размер данных в строке)
                    Значит надо что сделать ?
                    ---
                    Как-то так:
                    1. Указать ф-ии dll в качестве приёмника байт массив байт достаточного размера.
                    В ксчестве "предохранителя" можно указать размер массива. Но для начала можно просто
                    указать большой буфер. Да хоть 1М.
                    2. Получили буфер с данными - это строка С, данные ограничены 0 с хвоста.
                    3. Надо написать процедуру, которая определит количество символов от начала массива до двоичного 0.
                    Т.е. определим размер данных строки.
                    4. после этого пропишем этот размер в первый байт Паскальной строки.
                    Если он, конечно, не превышает допустимое значение 255.
                    Видимо, как-то так:
                    s[0]:= Подсчитанная_Длина;
                    5. Скопируем в строку все полученные байты из буфера. Начиная с индекса 1 (s[1]:=.., s[2]:=.., s[3]:=.. ....).

                    Цикл на Паскале ты сможешь смастерить ?

                    какой-то форум про строки Паскаля
                    Сообщение отредактировано: ЫукпШ -
                      ЫукпШ
                      >Первый байт Паскальной строки - размер данных строки.
                      Это старый формат турбопаскаля, который поддерживается, но почти не используется, и в данном случае он не при деле.
                        Цитата MBo @
                        ЫукпШ
                        >Первый байт Паскальной строки - размер данных строки.
                        Это старый формат турбопаскаля, который поддерживается, но почти не используется, и в данном случае он не при деле.

                        Как я понял, cудя по результатам автора, происходит именно это.

                        Цитата

                        Так заполняется Паскальная строка:
                        s:='asdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasd';
                        getvar(Pchar(s),length(s));

                        writeln('getvar= "',(s),'"'); //pansichar


                        В строку были изначально занесено некоторое количество байт.
                        Часть из них было искажено dll-ю.
                        Был скопирован и двоичный 0.
                        Размер строки изменён не был.
                        Поэтому на экране оказалось это:
                        Цитата

                        Вот такой код выводит на экран
                        1110000000 sadasdasdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasd
                          Цитата ЫукпШ @
                          Как я понял, cудя по результатам автора, происходит именно это

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

                          Кроме того, также по отрицательному смещению хранится и число ссылок на строку (RefCnt) - в данном случае это важно, т.к. Виталь, думая, что он "чуть приблизился к разгадке", на самом деле столкнулся с еще одной "загадкой", причем как дельфи, так и Си. Речь идет о ссылках\указателях на константные строки (литералы).
                          ExpandedWrap disabled
                            static char *myVAR1 = "1110000000"; //С/С++
                            s:='asdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasdasdasdsddadsadasd'; //дельфи

                          Заменив динамическую инициализацию строки через SetLength(), на константно-литеральную s:='...' в общем случае можно нарваться на AV при вызове getvar, т.к. при этом s ссылается не на собственную копию строки в дин.памяти, а на литеральную константу, которая в зависимости от ситуации может размещаться компилятором как в RW памяти (в секции инициализированных данных), так и в read only (например, непосредственно в секции кода). При изменении строки штатными дельфийскими методами компилятор проверяет кол-во ссылок на строку - если оно не равно 1, то перед изменением создается уникальная копия строки (UniqueString) в динамической памяти. Это же относится и к ссылкам на литеральные константы, которые имеют специальное значение RefCnt = -1, и соотв-но при любой штатной попытке изменения такой строки компилятор создает копию литерала в дин.памяти. Но функция getvar ничего об этих дельфийских хитростях\загадках не знает и соотв-но может нарваться на AV, если строка s будет ссылаться на константу в read only памяти.

                          Похожая ситуация и в С-коде, т.к. переменная myVAR1 объявлена не как массив символов char[..], а как указатель char*, ссылающийся на литеральную константу, которая размещена неизвестно где. Поэтому не стоит удивляться тому, что при вызове strcat возникает AV. В принципе оно и при strcpy может выскочить, если компилятор разместит литерал в read only памяти - возможно такое или нет, пусть спецы по стандартам C/C++ ответят.
                          Сообщение отредактировано: leo -
                            Цитата leo @
                            Поэтому не стоит удивляться тому, что при вызове strcat возникает AV. В принципе оно и при strcpy может выскочить, если компилятор разместит литерал в read only памяти - возможно такое или нет, пусть спецы по стандартам C/C++ ответят.

                            leo, поэтому я и предлагал с самого начала использовать
                            массив байт для передачи результата работы функции dll.
                            Это наиболее универсальный "объект" для передачи данных вообще.
                            А что именно потом с этим делать - на вкус автора.
                            Можно и "старую" Паскальную строку сформировать, можно и "новую".
                            Можно и так оставить и выводить по-символьно.
                            Не исключено, что в Дельфи существуют библиотечные средства
                            для работы со строками С. Но предположим, их нет или нам не нравятся.
                            Тогда берём любимый Паскально-строчный объект.
                            Наследуемся от него, добавляем необходимые свойства и методы и получаем
                            свой собственный строчный объект, который умеет манипулировать
                            стоками С. Для начала хотя бы их преобразовывать в Паскальный эквивалент.
                            Сообщение отредактировано: ЫукпШ -
                              Цитата leo @
                              В принципе оно и при strcpy может выскочить, если компилятор разместит литерал в read only памяти - возможно такое или нет, пусть спецы по стандартам C/C++ ответят.

                              да именно так :D
                                Цитата ЫукпШ @
                                поэтому я и предлагал с самого начала использовать
                                массив байт для передачи результата работы функции dll.

                                Правильно. И MBo в #6 привел аналогичный вариант с array[..] of AnsiChar для дельфи-кода, который в основном и используется при получении строк из WinAPI. При этом последующая конвертация полученного массива символов в string делается простым присваиванием s:=p1 (аналогичное пере-присваивание s:=PAnsiChar(s) Виталь все равно вынужден делать для обрезки "хвоста" строки). А передача во внешнюю функцию PAnsiChar(s) используется сравнительно редко - в тех случаях, когда размер возвращаемой строки ничем не ограничен и м.б. любым\большим (например, при чтении файла через TStringList.LoadFromFile сначала выделяется строка s под размер файла, в нее читаются данные, и затем делается ее разбивка на строки).

                                Цитата ЫукпШ @
                                Не исключено, что в Дельфи существуют библиотечные средства
                                для работы со строками С

                                Есть, но они непривычны и неудобны по сравнению с дельфийской string. Поэтому после возврата строки в массив buf, его обычно сразу копируют в string простым присваиванием s:=buf.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0468 ]   [ 17 queries used ]   [ Generated: 19.04.24, 05:24 GMT ]