Reputation: 1187
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
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