Delphi中将空的PChar字符串传递给函数时会出现空值问题

3

我正在使用Delphi XE6开发,如果有所关联。

我正在编写一个DLL,该DLL将从谷歌地图的方向API获取响应,并解析结果JSON以确定行程的总距离。

GetJSONString_OrDie按预期工作。它获取一个URL并试图从谷歌获取响应。 如果成功,则返回谷歌返回的JSON字符串。

ExtractDistancesFromTrip_OrDie应接收一个JSON字符串,并解析它以获取每个路段的总距离,并将这些距离保存在双精度数组中。

SumTripDistances接收一个双精度数组,对数组进行求和,并返回总数。

有趣的是,如果我将这些函数从DLL中取出并放入项目中直接调用,那么它按预期工作。 只有当它们被放入DLL时,事情似乎出了问题。 如上所述,GetJSONString_OrDie按预期工作并返回JSON字符串-但是当通过DLL的ExtractDistancesForTrip进行步进时,传递的PChar字符串是''。 为什么会这样?

unit Unit1;



interface
uses
  IdHTTP,
  IdSSLOpenSSL,
  System.SysUtils,
  System.JSON;

type
  arrayOfDouble = array of double;

function GetJSONString_OrDie(url : Pchar) : Pchar;
function ExtractDistancesForTrip_OrDie(JSONstring: Pchar) : arrayOfDouble;
function SumTripDistances(tripDistancesArray: arrayOfDouble) : double;

implementation



{ Attempts to get JSON back from Google's Directions API }
function GetJSONString_OrDie(url : Pchar) : PChar;
var
  lHTTP: TIdHTTP;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
  {Sets up SSL}
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  {Creates an HTTP request}
  lHTTP := TIdHTTP.Create(nil);
  {Sets the HTTP request to use SSL}
  lHTTP.IOHandler := SSL;
  try
    {Attempts to get JSON back from Google's Directions API}
    Result := PWideChar(lHTTP.Get(url));
  finally
    {Frees up the HTTP object}
    lHTTP.Free;
    {Frees up the SSL object}
    SSL.Free;
  end;
end;

{ Extracts the distances from the JSON string }
function ExtractDistancesForTrip_OrDie(JSONstring: Pchar) : arrayOfDouble;
var
  jsonObject: TJSONObject;
  jsonArray: TJSONArray;
  numberOfLegs: integer;
  I: integer;
begin
  //raise Exception.Create('ExtractDistancesForTrip_OrDie:array of double has not yet been '+
  //                        'implemented.');

  jsonObject:= nil;
  jsonArray:= nil;
  try
    { Extract the number of legs in the trip }
    jsonObject:= TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(JSONstring), 0) as TJSONObject;
    jsonObject:= (jsonObject.Pairs[0].JsonValue as TJSONArray).Items[0] as TJSONObject;
    jsonArray:= jsonObject.Pairs[2].JSONValue as TJSONArray;
    numberOfLegs:= jsonArray.Count;

    {Resize the Resuls arrayOfDouble}
    SetLength(Result, numberOfLegs);

    {Loop through the json and set the result of the distance of the leg}
    {Distance is in km}
    for I := 0 to numberOfLegs-1 do
    begin
      Result[I]:= StrToFloat((((jsonArray.Items[I] as TJSONObject).Pairs[0].JsonValue as TJSONObject).Pairs[1]).JsonValue.Value);
    end;


  finally
    jsonObject.Free;
    jsonArray.Free;
  end;

end;


function SumTripDistances(tripDistancesArray: arrayOfDouble) : double;
var
  I: integer;
begin
  //raise Exception.Create('GetDistanceBetweenPoints_OrDie:double has not yet been ' +
 //                        'implemented.');

 Result:= 0;
 {Loop through the tripDistancesArray, and add up all the values.}
 for I := Low(tripDistancesArray) to High(tripDistancesArray) do
    Result := Result + tripDistancesArray[I];


end;

end.

以下是函数调用的方法:

方法如下:

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  ShareMem,
  IdHTTP,
  IdSSLOpenSSL,
  System.SysUtils,
  System.JSON,
  System.Classes;

type
  arrayOfDouble = array of double;

  testRecord = record
    testString : string;
    testInt : integer;
  end;

function GetJSONString_OrDie(url : Pchar) : PWideChar; stdcall; external 'NMBSGoogleMaps.dll';

function ExtractDistancesForTrip_OrDie(JSONstring: pchar) : arrayOfDouble;  stdcall; external 'NMBSGoogleMaps.dll';

function SumTripDistances(tripDistancesArray: arrayOfDouble) : double; stdcall; external 'NMBSGoogleMaps.dll';


var
  jsonReturnString: string;
  jsonReturnString2: Pchar;
  doubles: arrayOfDouble;
  totalJourney: double;
  uri:Pchar;

  a : testRecord;
  begin
  try
    uri:= 'https://maps.googleapis.com/maps/api/directions/json?origin=Boston,MA&destination=Concord,MA&waypoints=Charlestown,MA|Lexington,MA&key=GETYOUROWNKEY';


    { TODO -oUser -cConsole Main : Insert code here }
    jsonReturnString:= GetJSONString_OrDie(uri); //On step-through, uri is fine.

    jsonReturnString2:= stralloc(length(jsonreturnstring)+1);
    strpcopy(jsonreturnstring2, jsonreturnstring);
    jsonreturnstring2 := 'RANDOMJUNK';

    doubles:= ExtractDistancesForTrip_OrDie(jsonReturnString2); //On step-through, jsonReturnString2 is seen as '', rather than 'RANDOMJUNK'
    totalJourney:= SumTripDistances(doubles);
    WriteLn('The total journey was: ');
    WriteLn(totalJourney);

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.

当第一个函数被调用时,GetJSONString_OrDie,uri被传入并且在查看或打印出来时是正确的。 然而,在创建随机字符串并将其传递给ExtractDistancesForTrip_OrDie之后,该函数仅看到''。如果我更改ExtractDistancesForTrip_OrDie以接受整数,记录或任何内容-它会看到垃圾或随机数据。无论是否通过引用或值传递都没有关系。

我撤销了这个问题,因为你完全改变了它。请不要这样做。 - David Heffernan
为什么你要将它制作成DLL,你是否计划让非Delphi程序使用它?也许OLE字符串会更好一些? - Arioch 'The
目前没有计划让非Delphi程序使用它。 - Acorn
1个回答

5
你的调用约定不匹配。你的函数使用了“寄存器”调用约定,但你导入它们时却像使用“stdcall”一样。这就是为什么你传递的参数没有到达的原因。
在实现函数和导入函数时都应该使用“stdcall”。
在“GetJSONString_OrDie”中,你返回一个指向本地变量缓冲区的指针。当函数返回时,该本地变量将被销毁,指针将无效。你需要找到一种不同的方式来传递字符串数据出这个函数。
通常返回一个字符串是可以的。但由于这个函数是从 DLL 中导出的,所以这样做行不通。你需要使用调用者分配的缓冲区或在共享堆上分配缓冲区。
最后,动态数组不是有效的互操作类型。你不应该跨模块边界传递它们。

你是在说 GetJSONString_OrDie 吗?我能够查看和编辑它返回的 PChar,如果它在函数返回后被销毁了,那么这不可能发生吧? - Acorn
有道理!我对Delphi还比较新,你能提供一个“你需要使用调用者分配的缓冲区或在共享堆上分配的缓冲区”的例子吗?我猜我需要在堆上分配一个带有一定大小的字符串,然后将PChar复制到其中?但我不确定该如何做(或者这是否正确)。 - Acorn
周围有很多例子。到底使用什么取决于许多我不知道的因素。这个函数是从DLL导出的吗?谁在使用它?调用者能否分配缓冲区?我不知道这些细节。 - David Heffernan
你正在dll过程内进行分配。调用者必须在调用dll过程之前进行分配。 - LU RD
1
我的猜测是正确的。将它们都设置为stdcall。动态数组返回值也不好。 - David Heffernan
显示剩余14条评论

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