从C++转换字节到Delphi AnsiString的正确方法

3
我有一个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;

这个程序在正常情况下工作良好,但是从C++端调用SetString时会出现AV错误。

请帮助我解决这个问题。


RredCat补充说明:

首先关于我们所使用的语言。我们需要在C#项目中调用Delphi动态链接库。为此,我创建了一个C++\CLI层(proxy)。 现在关于头文件中的C++\CLI代码:

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

在C++中,我在构造函数中设置了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);
}

  1. 不需要使用 TBytes,只需使用 M.MemoryM.Size
  2. 至于问题本身,请展示给我们 C++ 或者 C#,无论哪种都可以。
- David Heffernan
他提到了C++两次,并将其标记为C++,因此我正在更改标题。 - Warren P
2个回答

5

您的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));

或者

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

这是相同的。


1
如果Convert的第一个参数类型更好,那么错误将更容易被发现。例如,如果它是指向字节的指针,则将其设置为PByte。然后很明显传递^TBytes是错误的;如果您做出明智的决定并启用了“类型化@运算符”选项,编译器甚至会抱怨它。 - Rob Kennedy
这个回答正是我所需要的。 - Yuriy

1
你正在把这个任务搞得比必要的复杂。当你有一个现成的C或C ++代码库,或者一个具有非常大接口的本机库时,C ++ / CLI是非常好的选择。只要你只有一小部分函数,就应该直接使用p/invoke调用本地的Delphi DLL。
Serg已经指出了你的Delphi函数中多余的间接层级。所以我会把Delphi函数写成:
procedure TestFunction(Text: PAnsiChar); stdcall;
var
  Buf: AnsiString;
begin
  Buf := Text;
end;

在C#端,您可以像这样声明函数:
[DllImport(@"mydll.dll")]
static extern void TestFunction(string Text);

您可以在C#代码中像这样调用此函数:
TestFunction("Hello");
string str = GetStringFromSomewhere();
TestFunction(str);

就是这样!由于在 C# 端您有一个字符串变量,因此您可以依靠 p/invoke marshaller 提供一个以 null 结尾的字符串给本地代码。因此不需要传递长度。p/invoke 的默认调用约定是 stdcall,因此在 Delphi 代码中请优先考虑使用它。


在开始实现之前,我们已经调查了两种方法。PInvoke 对我们来说不太合适。因为在实际代码中,我们通过保留内存将结果作为 PChar 返回,并在 C++\CLI 中将其转换为字符串,然后调用释放此内存的方法(并将此 PChar 作为参数传递)。 - RredCat
你可以很容易地使用p/invoke来完成所有这些。一种简单的方法是返回一个Delphi WideString,然后使用MarshalAs将其作为BSTR在托管端进行封送处理。这真的是最简单的方法,比你的C++/CLI方法要容易得多。 - David Heffernan
我听说传递字符串是一种不好的方法。最好使用PChar或字节。这是正确的吗? - Yuriy
将字符串数据从C#传递到本地非常简单。在托管端声明参数为“string”,在本地端声明为“PAnsiChar”或“PWideChar”。在另一个方向上,“WideString”/“BSTR”是最简单的。这很容易做到,我相信你正在过度复杂化它。 - David Heffernan
传递文件作为字节数组怎么样?我需要做两件事:1)PByte到record;2)PByte到AnsiString。至于第二个问题,我在我的问题中写了一个例子。但当我写Move(TBytes(PArr)[0],FMyRecord,SizeOf(FMyRecord))时,仍然会出现AV错误。 - Yuriy
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接