Правильное преобразование байтов из C++ в Delphi AnsiString

У меня есть dll, которая принимает указатель на массив байтов из C++ и пытается переместить эти данные в AnsiString следующим образом.

procedure Convert(const PByteArr: Pointer; ArrSize: Cardinal); export; cdecl;
var
  Buf: AnsiString;
begin
  SetString(Buf, PAnsiChar(PByteArr^), ArrSize);
end;

Если я вызову этот метод из Delphi

procedure Try;
var
  M: TMemoryStream;
  Arr: TBytes;  
begin
  M := TMemoryStream.Create;
  try
    M.LoadFromFile('myfile.dat');
    SetLength(Arr, M.Size);
    M.Position := 0;
    M.Read(Arr[0], M.Size);
  finally
    M.Free;
  end;
  Convert(@Arr, Length(Arr));
end;

он отлично работает, но из С++, если выдает AV на SetString.

Пожалуйста, помогите мне с этим.


От РредКэт:

Позвольте мне немного пояснить вопрос Юрия: Прежде всего о языках, которыми мы пользуемся. Нам нужно вызвать Delphi dll в проекте C#. Я создал слой C++\CLI (прокси) для этих целей. Теперь о коде C++\CLI в заголовочном файле:

HINSTANCE hDelphiDLL;
typedef void (*pPBDFUNC)(byte* aBytes, int size);
pPBDFUNC Convert;

В cpp я устанавливаю Convert в конструкторе:

hDelphiDLL = LoadLibrary(<path to dll>);

if(NULL != hDelphiDLL ){
    pPBDFUNC clb= GetProcAddress(HMODULE(hDelphiDLL), "Convert");
        if(NULL != clb){
            Convert= pPBDFUNC (clb);
        }...

И последний метод, который я вызываю из C#:

void Class1::Test(byte* aBytes, int size){
    Convert(aBytes,size);
}

person Yuriy    schedule 01.02.2012    source источник
comment
1. TBytes не нужен. Просто используйте M.Memory и M.Size. 2. Что касается самого вопроса, покажите нам С++. Или C#, что бы это ни было на самом деле.   -  person David Heffernan    schedule 01.02.2012
comment
Он дважды упоминает C++ и помечает его как C++, поэтому я меняю название.   -  person Warren P    schedule 01.02.2012


Ответы (2)


Ваш код Delphi передает указатель на указатель на массив байтов. Упростите его, чтобы использовать только указатель на массив байтов:

procedure Convert(const PByteArr: Pointer; ArrSize: Cardinal); export; cdecl;
var
  Buf: AnsiString;
begin
  SetString(Buf, PAnsiChar(PByteArr), ArrSize);
end;

и назовите это как

Convert(@Arr[0], Length(Arr));

or

Convert(Pointer(Arr), Length(Arr));

что то же самое.

person kludg    schedule 01.02.2012
comment
Ошибку было бы легче обнаружить, если бы первый параметр Convert имел лучший тип. Например, если это указатель на байты, сделайте его PByte. Тогда становится ясно, что передавать ^TBytes неправильно; компилятор даже пожалуется на это, если вы сделаете мудрый поступок и включите опцию typed @ operator. - person Rob Kennedy; 01.02.2012
comment
Этот ответ именно то, что мне нужно. - person Yuriy; 24.02.2012

Вы делаете эту задачу намного сложнее, чем она должна быть. C++/CLI отлично подходит, когда у вас есть существующий код C или C++ или собственная библиотека с очень большим интерфейсом. Пока у вас есть только несколько функций, прямой путь p/invoke к родной DLL Delphi — это путь.

Серг уже указал на ложный дополнительный уровень косвенности в вашей функции Delphi. Поэтому я бы написал функцию Delphi как:

procedure TestFunction(Text: PAnsiChar); stdcall;
var
  Buf: AnsiString;
begin
  Buf := Text;
end;

На стороне С# вы можете объявить функцию следующим образом:

[DllImport(@"mydll.dll")]
static extern void TestFunction(string Text);

Вы можете вызвать эту функцию из кода C# следующим образом:

TestFunction("Hello");
string str = GetStringFromSomewhere();
TestFunction(str);

И это все! Поскольку в конце C# у вас есть строковая переменная, вы можете положиться на маршаллер p/invoke, чтобы предоставить строку с завершающим нулем для собственного кода. Следовательно, нет необходимости передавать длину. Соглашение о вызовах по умолчанию для p/invoke — stdcall, так что предпочтите его в коде Delphi.

person David Heffernan    schedule 01.02.2012
comment
Мы исследовали оба способа, прежде чем приступить к реализации. PInvoke нам не подходит. Поскольку в реальном коде мы возвращаем результат как PChar, резервируя память, преобразуем его в строку в C++\CLI и вызываем метод, который освобождает эту память (и передает этот PCHar в качестве параметра). - person RredCat; 02.02.2012
comment
Вы можете сделать все это очень легко с помощью p/invoke. Одним из простых способов было бы вернуть Delphi WideString, а затем использовать MarshalAs для маршалинга его как BSTR на управляемой стороне. Это действительно настолько просто, насколько это возможно, и намного проще, чем ваш подход C++/CLI. - person David Heffernan; 02.02.2012
comment
Слышал мнение, что попутные жала - плохой подход. Лучше использовать PChar или байты. Это правильно? - person Yuriy; 02.02.2012
comment
Передача строковых данных из C# в родной код тривиальна. Объявите параметр как string на управляемой стороне и PAnsiChar или PWideChar на собственной стороне. В другом направлении проще всего WideString/BSTR. Это довольно просто сделать, и я уверен, что вы сильно усложняете это. - person David Heffernan; 02.02.2012
comment
Как насчет передачи файла в виде массива байтов? Мне нужно сделать две вещи: 1) PByte для записи; 2) PByte в AnsiString. Что касается 2, я написал и пример в своем вопросе. У меня все еще есть AV, когда пишут Move(TBytes(PArr)[0], FMyRecord, SizeOf(FMyRecord)); ` - person Yuriy; 02.02.2012
comment
Ну, этот код неправильный. Все, что вы хотите сделать, легко в pinvoke. - person David Heffernan; 02.02.2012
comment
Не могли бы вы написать пример? - person Yuriy; 02.02.2012
comment
Я уже ответил на вопрос, который вы задали. Похоже, вы задаете новый вопрос. - person David Heffernan; 02.02.2012
comment
давайте продолжим обсуждение в чате - person Yuriy; 02.02.2012