Delphi 7 to XE8 assigning binary ADO parameter values - delphi

I'm converting a report that resides in a DLL to XE8 so that I can update our report control, which is problematic in Windows 10. In doing this, I've had to replace our ADO data access controls with the built-in ADO controls.
The issues is that our data uses binary keys, and I've run into an issue assigning values to query parameters. In our code, the keys are passed around as strings, and assigned to the parameters and converted by the control at runtime.
Previously, utilizing the old control, it utilized the Delphi DB unit which contains a method .AsBlob which was used in the assignment. See below...
Qry.Close;
Qry.ParamByName('#Id').AsBlob := IdStringValue;
Qry.Open;
In the control's implementation it handled setting the property, which called into SetAsBlob. See below...
Type TBlobData = string;
...
procedure TQryParameter.SetAsBlob(const Value: TBlobData);
begin
Self.DataType := ftVarBytes;
Self.Value := StringToVarArray(Value);
end;
Part of the issue is that Data.DB has changed TBlobData = string; to TBlobData = TArray<Byte>;.
I've tried assigning these values to the query parameters following the same method used in the previous implementation, but it doesn't work.
Qry.Close;
Qry.Parameters.ParamByName('#Id').DataType := ftVarBytes;
Qry.Parameters.ParamByName('#Id').Value := ADODB.StringToVarArray(IdStringValue);
Qry.Open;
I get a fairly generic MSSQL error due to the parameter mismatch, "Application uses a value of the wrong type for the current operation."
The ADO query parameter is defined as VarBytes and the stored procedure accepts BINARY(6) for its only parameter so everything appears to be correct.
I've tried casting IdStringValue from String to AnsiString prior to calling StringToVarArray but it makes no difference.
Anyone know of a way to deal with this? Thank you.

Convert the string value to array of bytes when assigning the parameter.
Qry.Parameters.ParamByName('#Id').AsBlob := TEncoding.Default.GetBytes(StringValue);

Related

Delphi 10.4 Community Edition - inifile.ReadString does not return default value when it should ...?

Am I not seeing the forest for the trees?
Delphi returns '' on inifile.ReadString() instead of the supplied default parameter. According to the help, it should return the default when either section, key, or value are missing.
I am pretty sure I have used this a number of times before without any issues before.
unit Unit1;
interface
uses
Classes, System.SysUtils, Inifiles, Dialogs;
implementation
const
DRIVE_SECTION = 'USB_Drives';
DRIVE_LETTER = 'DriveLetter';
INI_NAME = 'c:\temp\test.ini';
//--------------------------------------------------------
procedure CreateDefaultInifile(const IniName: TFileName);
var
slist: TStringList;
begin
slist := TStringList.Create;
try
slist.Add('[' + DRIVE_SECTION + ']');
slist.Add(DRIVE_LETTER + ' = ');
//
slist.SaveToFile(IniName);
finally
slist.Free;
end;
end;
begin
if not FileExists(INI_NAME) then
CreateDefaultInifile(INI_NAME);
// now we have an inifile with a key that has no value
var ini := TIniFile.Create(INI_NAME);
Showmessage(ini.ReadString(DRIVE_SECTION, DRIVE_LETTER, 'Missing_Value'));
// according to Delphi's help, the default ('Missing_Value' in this case) should be returned if no value is assigned!
ini.Free;
end.
end.
Well, your INI file looks like this:
[USB_Drives]
DriveLetter =
Clearly, there is a key named DriveLetter, and its value is the empty string.
Had the file instead been
[USB_Drives]
DriverFrogness =
you would indeed have received your Missing_Value, because then there isn't a DriveLetter key.
You apparently are under the impression that an empty string counts as a missing value. However, according to the documentation for the GetPrivateProfileString function, which the RTL's TIniFile.ReadString() is a thin wrapper around:
[in] lpDefault
A default string. If the lpKeyName key cannot be found in the initialization file, GetPrivateProfileString copies the default string to the lpReturnedString buffer.
The documentation for TIniFile.ReadString() (and for TMemIniFile.ReadString(), for that matter) is misleading. Specifically, the clause:
Default is the string value to return if the:
...
Data value for the key is not assigned.
is wrong, and needs to be removed. The Default value is returned only if the specified Section or Key is not found. The value of the Key has no effect whatsoever on whether the Default is returned or not.
I have now filed a bug report with Embarcadero about this:
RSP-39946: Docs for TIniFile.ReadString() and TMemIniFile.ReadString() are wrong.

Delphi TadoQuery - Parameter type conversion

I send with Jquery and Ajax Parameters from the Webpage to my Delphi Webserver. Those Parameters will be used in the select, but as soon as i get to TADOQ.Open; i get a exception which contains the message bellow the code section.
I'm new to Delphi, so it's not easy for me to understand how to work with UnicodeString, AnsiString and String. Also i don't understand why it says double, when i don't use it anywhere.
Is there a simple way to fix the Problem? (Small Code and easy to unterstand)
EDIT: Added Process before calling SearchChosen();
KoyoWebSqlReader:=CKoyoWebSqlReader.Create;
KoyoWebSqlReader.OpenConnection;
Response.ContentType:='application/json';
Txt:=KoyoWebSqlReader.SearchChosen(
StrToInt(SplitString(Request.ContentFields[0], '=')[1]),
StrToDateTime(SplitString(Request.ContentFields[1], '=')[1])
).toJsonString;
Response.Content:=Txt;
KoyoWebSqlReader.CloseConnection;
// -----
function CKoyoWebSqlReader.SearchChosen(TicketNumber:Integer; Start:TDateTime):CKoyoMeasurement;
var Search:TADOQuery;
var Measurement:CKoyoMeasurement;
begin
Measurement:=CKoyoMeasurement.Create;
Search:=TADOQuery.Create(nil);
Search.Connection:=Connection;
Search.SQL.Clear;
Search.SQL.Text:=
'SELECT * FROM Measurements WHERE TicketNumber = :TicketNumber AND Start = :Start;';
Search.Parameters.ParamByName('TicketNumber').Value:=TicketNumber;
Search.Parameters.ParamByName('Start').Value:=Start;
try
Search.Open;
// Fill Measurement
except on EX:Exception do
begin
// Log Exception
end;
end;
Search.Close;
Result:=Measurement;
end;
Variant of Type (UnicodeString) could not be converted into (Double)

Delphi Dynamic Array assign to temporary local dynamic array variable

I have problem with new released delphi 10.2 the new compiler show error when
var
FGlobalVar: array of integer;
procedure SomeProc()
var
ALocalVar: array of integer;
begin
ALocalVar := Pointer(FGlobalVar); {assign dynamic array}
{Do Something}
end;
In previous version compiler delphi not show any errors.
That code should never have compiled, and Tokyo closes the loop hole. The problem with that cast is that reference counting can by bypassed. The code as you have it does not suffer that problem, but if the cast is written on the left hand side of the assignment, then no reference is taken.
Pointer(LocalVar) := GlobalVar;
Written this way round, LocalVar is assigned a reference to the dynamic array but the reference count is not incremented. I appreciate that your code is not written this way round, but I believe that this is the reason why the developers chose to make the change.
In any case, yhere is no need for the cast if you use types that are compatible. Switch to TArray<Integer> and the cast is not necessary. Further, your code will be able to interact with generic methods.

COM Exception "Bad Variable Type" Error in Delphi Call of Domino GetDocumentByKey Method

I have a legacy Delphi 2 application that I need to convert from communicating with Notes via OLE Automation to communicating via COM early binding. I am using Delphi 7 since the code base is large and I want to avoid the work of dealing with the Unicode support in the more current versions of Delphi.
The basics are working: the program opens the database then the view and searches for a particular document using the NotesView.GetDocumentByKey method. The GetDocumentByKey call works when the first parameter is a single string cast to an OleVariant as shown below (opening of DB and view not shown).
var
Key: OleVariant;
const ExactMatch: WordBool = True;
begin
Key := 'AKeyValue';
Doc := View.GetDocumentByKey(Key, ExactMatch);
The bad variable type error occurs when the first parameter is a variant array as required when it is desired to search the view based on multiple columns as shown below.
var
TwoKeysV: OleVariant;
const ExactMatch: WordBool = True;
begin
TwoKeysV := VarArrayCreate([0, 1], varOleStr);
TwoKeysV[0]:= WideString('Key1');
TwoKeysV[1]:= WideString('Key2');
Doc := View.GetDocumentByKey(TwoKeysV, ExactMatch);
I have tried several variations on the two key assignment statements with no success. For example, just assigning the key string without a cast still produces the bad variable type, and using the StringToOleString function is rejected by the compiler as an invalid assignment (PWideChar to Variant).
I can't test this, so I'm not sure this works.
HELP: If this method is used under COM, with a keyArray parameter of an array, it must be defined as an array of type Variant
So you need to pass: an array of type Variant
Based on How to use variant arrays in Delphi.
Note: Code edited by Keeloid to match code that worked by casting key string to WideString.
var
TwoKeysV: OleVariant;
const ExactMatch: WordBool = True;
begin
TwoKeysV := VarArrayCreate([0, 1], varVariant);
TwoKeysV[0]:= WideString('Key1'); {WideString = varOleStr}
TwoKeysV[1]:= WideString('Key2');
Doc := View.GetDocumentByKey(TwoKeysV, ExactMatch);

Elegant way for handling this string issue. (Unicode-PAnsiString issue)

Consider the following scenario:
type
PStructureForSomeCDLL = ^TStructureForSomeCDLL;
TStructureForSomeCDLL = record
pName: PAnsiChar;
end
function FillStructureForDLL: PStructureForSomeDLL;
begin
New(Result);
// Result.pName := PAnsiChar(SomeObject.SomeString); // Old D7 code working all right
Result.pName := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString)); // New problematic unicode version
end;
...code to pass FillStructureForDLL to DLL...
The problem in unicode version is that the string conversion involved now returns a new string on stack and that's reclaimed at the end of the FillStructureForDLL call, leaving the DLL with corrupted data. In old D7 code, there were no intermediate conversion funcs and thus no problem.
My current solution is a converter function like below, which is IMO too much of an hack. Is there a more elegant way of achieving the same result?
var gKeepStrings: array of AnsiString;
{ Convert the given Unicode value S to ANSI and increase the ref. count
of it so that returned pointer stays valid }
function ConvertToPAnsiChar(const S: string): PAnsiChar;
var temp: AnsiString;
begin
SetLength(gKeepStrings, Length(gKeepStrings) + 1);
temp := Utf8ToAnsi(UTF8Encode(S));
gKeepStrings[High(gKeepStrings)] := temp; // keeps the resulting pointer valid
// by incresing the ref. count of temp.
Result := PAnsiChar(temp);
end;
One way might be to tackle the problem before it becomes a problem, by which I mean adapt the class of SomeObject to maintain an ANSI Encoded version of SomeString (ANSISomeString?) for you alongside the original SomeString, keeping the two in step in a "setter" for the SomeString property (using the same UTF8 > ANSI conversion you are already doing).
In non-Unicode versions of the compiler make ANSISomeString be simply a "copy" of SomeString string, which will of course not be a copy, merely an additional ref count on SomeString. In the Unicode version it references a separate ANSI encoding with the same "lifetime" as the original SomeString.
procedure TSomeObjectClass.SetSomeString(const aValue: String);
begin
fSomeString := aValue;
{$ifdef UNICODE}
fANSISomeString := Utf8ToAnsi(UTF8Encode(aValue));
{$else}
fANSISomeString := fSomeString;
{$endif}
end;
In your FillStructure... function, simply change your code to refer to the ANSISomeString property - this then is entirely independent of whether compiling for Unicode or not.
function FillStructureForDLL: PStructureForSomeDLL;
begin
New(Result);
result.pName := PANSIChar(SomeObject.ANSISomeString);
end;
There are at least three ways to do this.
You could change SomeObject's class
definition to use an AnsiString
instead of a string.
You could
use a conversion system to hold
references, like in your example.
You could initialize result.pname
with GetMem and copy the result of the
conversion to result.pname^ with
Move. Just remember to FreeMem it
when you're done.
Unfortunately, none of them is a perfect solution. So take a look at the options and decide which one works best for you.
Hopefully you already have code in your application to properly dispose off of all the dynamically allocated records that you New() in FillStructureForDLL(). I consider this code highly dubious, but let's assume this is reduced code to demonstrate the problem only. Anyway, the DLL you pass the record instance to does not care how big the chunk of memory is, it will only get a pointer to it anyway. So you are free to increase the size of the record to make place for the Pascal string that is now a temporary instance on the stack in the Unicode version:
type
PStructureForSomeCDLL = ^TStructureForSomeCDLL;
TStructureForSomeCDLL = record
pName: PAnsiChar;
// ... other parts of the record
pNameBuffer: string;
end;
And the function:
function FillStructureForDLL: PStructureForSomeDLL;
begin
New(Result);
// there may be a bug here, can't test on the Mac... idea should be clear
Result.pNameBuffer := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString));
Result.pName := Result.pNameBuffer;
end;
BTW: You wouldn't even have that problem if the record passed to the DLL was a stack variable in the procedure or function that calls the DLL function. In that case the temporary string buffers will only be necessary in the Unicode version if more than one PAnsiChar has to be passed (the conversion calls would otherwise reuse the temporary string). Consider changing the code accordingly.
Edit:
You write in a comment:
This would be best solution if modifying the DLL structures were an option.
Are you sure you can't use this solution? The point is that from the POV of the DLL the structure isn't modified at all. Maybe I didn't make myself clear, but the DLL will not care whether a structure passed to it is exactly what it is declared to be. It will be passed a pointer to the structure, and this pointer needs to point to a block of memory that is at least as large as the structure, and needs to have the same memory layout. However, it can be a block of memory that is larger than the original structure, and contain additional data.
This is actually used in quite a lot of places in the Windows API. Did you ever wonder why there are structures in the Windows API that contain as the first thing an ordinal value giving the size of the structure? It's the key to API evolution while preserving backwards compatibility. Whenever new information is needed for the API function to work it is simply appended to the existing structure, and a new version of the structure is declared. Note that the memory layout of older versions of the structure is preserved. Old clients of the DLL can still call the new function, which will use the size member of the structure to determine which API version is called.
In your case no different versions of the structure exist as far as the DLL is concerned. However, you are free to declare it larger for your application than it really is, provided the memory layout of the real structure is preserved, and additional data is only appended. The only case where this wouldn't work is when the last part of the structure were a record with varying size, kind of like the Windows BITMAP structure - a fixed header and dynamic data. However, your record looks like it has a fixed length.
Wouldn't PChar(AnsiString(SomeObject.SomeString)) work?

Resources