Acorn
Acorn

Reputation: 1187

Delphi PChar string is empty when passed to function

I am using Delpi XE6, if it matters.

I am writing a DLL that will get a response from Google's Directions API, parse the resulting JSON and determine the total distance of the trip.

GetJSONString_OrDie works as intended. It takes a URL and attempts to get a response from Google. If it does, it returns the JSON string that Google returns.

ExtractDistancesFromTrip_OrDie should take in a JSON string, and parse it to get the total distance of each leg of the journey, and stick those distances in an array of doubles.

SumTripDistances takes in an array of doubles, and sums up the array, and returns the total.

The curious thing is, if I take these functions out of the DLL and put them in a project and call them like that, it works as intended. Only when the are stuck into a DLL is when things seem to go wrong. As said, GetJSONString_OrDie works as intened and returns a JSON string - but when stepping through the DLL's ExtractDistancesForTrip, the passed PChar string is ''. Why is that?

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.

Here is how the functions are being called:

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.

When the first function is called, GetJSONString_OrDie, uri is passed in and is correct when viewed or printed out. However, after creating a random string and passing it into ExtractDistancesForTrip_OrDie, the function only sees ''. If I were to change ExtractDistancesForTrip_OrDie to accept an integer, or a record, or anything - it sees either garbage or random data. It does not matter if I pass by reference or value.

Upvotes: 3

Views: 711

Answers (1)

David Heffernan
David Heffernan

Reputation: 613521

Your calling conventions don't match. Your functions use register calling convention but you import them as if they were stdcall. That's why the parameters you pass are not arriving.

Use stdcall both when you implement the function, and also when you import it.

In GetJSONString_OrDie you are returning a pointer to the buffer of a local variable. As soon as the function returns, that local variable is destroyed and the pointer is invalid. You'll need to find a different way to pass string data out of this function.

Returning a string would normally be fine. But since this function is exported from a DLL that won't work. You'd need either a caller allocated buffer, or a buffer allocated on a shared heap.

Finally, dynamic arrays are not valid interop types. You should not pass these across module boundaries.

Upvotes: 5

Related Questions