Writing string to TMemoryStream - Pointer to string - delphi

What is the difference between
this:
SourceString := 'I am doing just fine!';
MemoryStream.ReadBuffer(Pointer(SourceString)^, xxx);
(full source code available here: http://edn.embarcadero.com/article/26416)
and this code (mine):
SetLength(SourceString, xxx);
MemoryStream.ReadBuffer(SourceString[1], xxx);
Do I really have to use Pointer(SourceString)^ or SourceString[1] is ok also?
The code (both of them) will work with Delphi 2010 (unicode)?

1: The SourceString[1] version is more readable. I prefer not to work with pointers when they aren't completely necessary.
2: This code will not work with Unicode. You'll have to multiply it: xxx * sizeof(Char). (This will work with both pre- and post-Unicode versions of Delphi.) But unless you're making heavy use of non-Ansi chars, this will be a big waste of space. What I prefer to do is:
procedure TStreamEx.WriteString(const data: string);
var
len: cardinal;
oString: UTF8String;
begin
oString := UTF8String(data);
len := length(oString);
self.WriteBuffer(len, 4);
if len > 0 then
self.WriteBuffer(oString[1], len);
end;
procedure TStreamEx.ReadString(const data: string);
var
len: cardinal;
iString: UTF8String;
begin
self.ReadBuffer(len, 4);
if len > 0 then
begin
SetLength(iString, len);
self.ReadBuffer(iString[1], len);
result := string(iString);
end
else result := '';
end;
(This is part of a class helper for TStream I wrote that makes it a lot easier to read and write various things to and from streams. But if you don't like class helpers, it shouldn't be too hard to adapt the basic idea to a different format.)

In the generated asm code:
pointer(aString)^ will pass the string address directly to the procedure/function/method;
aString[1] will call UniqueString then pass the string address to the procedure/function/method.
So pointer(aString)^ is to be used if you're about to read the data, not modify it.
And aString[1] is to be used if you're about to modify aString in the called function.
In practice, I use pointer(aString)^ which produces more efficient code.
Note that this implicit UniqueString is not so slow: if the current reference count of the string is 1 (which means that there is only one part of your code using the string, which is very likely), it returns immediately. But there is a LOCK asm prefix in the UniqueString used to check the reference count value, and use of this LOCK asm is not multi-thread friendly. That's why I try to avoid using aString[1] when I'm coding.
Additional note: if aString is '', pointer(aString) will return nil.

Related

E2250 There is no overloaded version of 'StrPas' that can be called with these arguments

My code :
function ThisModuleName: Char; //bulo String
var
p: array [0..512] of char;
fileNamePart: pchar;
begin
GetModuleFileName(HInstance, #p[0], 512);
GetFullPathName(#p[0], 512, #p[0], fileNamePart);
result := StrPas(WideString(#p[0])); //stalo WideString
end;
In Delphi 7 compiles.
In Delphi 10.2 it gives an error:
[dcc32 Error] verinfo.pas(98): E2250 There is no overloaded version of 'StrPas' that can be called with these arguments
This code is wrong on all Delphi versions. I doubt it compiles anywhere. I'm guessing that the code you presented is not the Delphi 7 code, but rather the code after you've hacked at it for a while.
The return type should be string and not char. Furthermore, the cast to WideString is bogus. Finally, a zero-based array of characters can be treated as PChar.
Your function should be translated like so:
function ThisModuleName: string;
var
p: array [0..511] of Char;
fileNamePart: PChar;
begin
GetModuleFileName(HInstance, p, Length(p));
GetFullPathName(p, Length(p), p, fileNamePart);
Result := p;
end;
Having said all of that, while this is a faithful translation of the code in the question, it does not return a module name. I really don't know what your code is trying to do, but the call to GetFullPathName appears to be wrong in your code.
My guess is that you are trying to convert potential short 8.3 file names to long names. I believe that you need an extra buffer to make that work. Here's what that code looks like, with some error checking added:
function ThisModuleName: string;
var
ModuleFileName, Buffer: array [0..511] of Char;
FilePart: PChar;
begin
Win32Check(GetModuleFileName(HInstance, ModuleFileName, Length(ModuleFileName))<>0);
Win32Check(GetFullPathName(ModuleFileName, Length(Buffer), Buffer, FilePart)<>0);
Result := Buffer;
end;
Instead of asking a question for every problem you encounter in your porting project it might pay dividends to learn a bit more about Unicode Delphi.
Instead of calling the API directly, you can call System.SysUtils.GetModuleName, which simply returns a string.
It wraps GetModuleFilename, and by doing so it also shows how to call that function. I hope I'm allowed to quote a couple of lines from the unit mentioned above. It also uses the MAX_PATH constant, which contains the maximum length of a path.
Note that GetModuleFilename already returns a fully qualified path, so calling GetFullPathName afterwards is redundant.
function GetModuleName(Module: HMODULE): string;
var
ModName: array[0..MAX_PATH] of Char;
begin
SetString(Result, ModName, GetModuleFileName(Module, ModName, Length(ModName)));
end;
This is mainly useful if you want the path of a dll, if you're interested in the main executable, you can simply use Application.ExeName.

Fastest way to access Data by Key

I have hundreds of variables that have to be accessed with a key.
Type of Keys is string (max:50 character) and Data is array of bytes (max:500 Byte).
I used this way :
define this types :
type
TMyBuf = array[0..(500-1)] of Byte;
TMyKey = string[50];
TMyBufs = TDictionary<TMyKey,TMyBuf>;
var
MyBufs :TMyBufs;
and used :
var vMyBuf :TMyBuf;
vMyData :TBytes ABSOLUTE vMyBuf;
vMyDataLen :Word;
begin
List := srvData.Contexts.LockList;
SetLength(vMyBuf, 500);
try
if(List.Count > 0) then begin
for i := 0 to List.Count-1 do begin
with TMyContext(List[I]) do begin
if SetedIdent then begin
try
vMyBuf:= MyBufs.Items[SeledData];
//extract length of data which stored in two byte
vMyDataLen:= ( ( (vMyBuf[1] shl 8)and $FF00) or (vMyBuf[0] and $FF) );
Connection.IOHandler.Write(vMYData, vMYDataLen);
finally
end;
end;
end;
end;
end;
finally
srvData.Contexts.UnlockList;
SetLength(vMyBuf, 0);
end;
end;
There is a similar code to write data.
1.Is it direct access to the Values? No need to copy the Value dictionary(vMyBuf:= MyBufs.Items[SeledData];).
2.Is there a better way?
You would be better off to make use of the implicit by-reference semantics of Classes
and use TObjectDictionary.
type
TMyBuf = class
public
Data:array[0..(500-1)] of Byte;
end;
TMyKey = string[50];
TMyBufs = TObjectDictionary<TMyKey,TMyBuf>;
var
MyBufs :TMyBufs;
This would allow you to write a single byte into the dictionary easily. You would of course have to allocate each TMyBuf by invoking it's constructor. Similarly cleaning up would be easier if you used a TObjectDictionary which can own (meaning therefore know how to free) all object references placed into it.
The other thing you might not know is that on a Unicode delphi, string[50] is an ancient TurboPascal/DOS-era shortstring type and not a unicode string.
I suggest that unless you REALLY need to, you not worry about using string[50] and simply use string. If you wish to validate at runtime that the string is 50 characters or less and throw an exception, then do it that way.

Delphi Unicode and Console

I am writing a C# application that interfaces with a pair of hardware sensors. Unfortunately the only interface that is exposed on the devices requires a provided dll written in Delphi.
I am writing a Delphi executable wrapper that takes calls the necessary functions for the DLL and returns the sensor data over stout. However, the return type of this data is a PWideChar (or PChar) and I have been unable to convert it to ansi for printing on command line.
If I directly pass the data to WriteLn, I get '?' for each character. If I look through the array of characters and attempt to print them one at a time with an Ansi Conversion, only a few of the characters print (they do confirm the data though) and they will often print out of order. (printing with the index exposed simply jumps around.) I also tried converting the PWideChar's to integer straight: 'I' corresponds to 21321. I could potentially figure out all the conversions, but some of the data has a multitude of values.
I am unsure of what version of Delphi the dll uses, but I believe it is 4. Definately prior to 7.
Any help is appreciated!
TLDR: Need to convert UTF-16 PWideChar to AnsiString for printing.
Example application:
program SensorReadout;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
SysUtils,
dllFuncUnit in 'dllFuncUnit.pas'; //This is my dll interface.
var state: integer;
answer: PChar;
I: integer;
J: integer;
output: string;
ch: char;
begin
try
for I := 0 to 9 do
begin
answer:= GetDeviceChannelInfo_HSI(1, Ord('a'), I, state); //DLL Function, returns a PChar with the results. See example in comments.
if state = HSI_NO_ERRORCODE then
begin
output:= '';
for J := 0 to length(answer) do
begin
ch:= answer[J]; //This copies the char. Originally had an AnsiChar convert here.
output:= output + ch;
end;
WriteLn(output);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
ReadLn(I);
end.`
The issue was PAnsiChar needed to be the return type of the function sourced from the DLL.
To convert PWideChar to AnsiString:
function WideCharToAnsiString(P: PWideChar): AnsiString;
begin
Result := P;
end;
The code converts from UTF-16, null-terminated PWideChar to AnsiString. If you are getting question marks in the output then either your input is not UTF-16, or it contains characters that cannot be encoded in your ANSI codepage.
My guess is that what is actually happening is that your Delphi DLL was created with a pre-Unicode Delphi and so uses ANSI text. But now you are trying to link to it from a post-Unicode Delphi where PChar has a different meaning. I'm sure Rob explained this to you in your other question. So you can simply fix it by declaring your DLL import to return PAnsiChar rather than PChar. Like this:
function GetDeviceChannelInfo_HSI(PortNumber, Address, ChNumber: Integer;
var State: Integer): PAnsiChar; stdcall; external DLL_FILENAME;
And when you have done this you can assign to a string variable in a similar vein as I describe above.
What you need to absorb is that in older versions of Delphi, PChar is an alias for PAnsiChar. In modern Delphi it is an alias for PWideChar. That mismatch would explain everything that you report.
It does occur to me that writing a Delphi wrapper to the DLL and communicating via stdout with your C# app is a very roundabout approach. I'd just p/invoke the DLL directly from the C# code. You seem to think that this is not possible, but it is quite simple.
[DllImport(#"mydll.dll")]
static extern IntPtr GetDeviceChannelInfo_HSI(
int PortNumber,
int Address,
int ChNumber,
ref int State
);
Call the function like this:
IntPtr ptr = GetDeviceChannelInfo_HSI(Port, Addr, Channel, ref State);
If the function returns a UTF-16 string (which seems doubtful) then you can convert the IntPtr like this:
string str = Marshal.PtrToStringUni(ptr);
Or if it is actually an ANSI string which seems quite likely to me then you do it like this:
string str = Marshal.PtrToStringAnsi(ptr);
And then of course you'll want to call into your DLL to deallocate the string pointer that was returned to you, assuming it was allocated on the heap.
Changed my mind on the comment - I'll make it an answer:)
According to that code if "state" is a code <> HSI_NO_ERRORCODE and there is no exception then it will write the uninitialised string "output" to the console. Which could be anything including accidentally showing "S" and "4" and a series of 1 or more question marks
type of answer(variable) is PChar. use length function good for string variable.
use strlen instead of length.
for J := 0 to StrLen(answer)-1 do
also accessible range of PChar(char *) is 0..n-1
To convert UTF-16 PWideChar to AnsiString you can use simple cast:
var
WStr: WideString;
pWStr: PWideString;
AStr: AnsiString;
begin
WStr := 'test';
pWStr := PWideString(WStr);
AStr := AnsiString(WideString(pWStr));
end;

How do I stop this Variant memory leak?

I'm using an old script engine that's no longer supported by its creators, and having some trouble with memory leaks. It uses a function written in ASM to call from scripts into Delphi functions, and returns the result as an integer then passes that integer as an untyped parameter to another procedure that translates it into the correct type.
This works fine for most things, but when the return type of the Delphi function was Variant, it leaks memory because the variant is never getting disposed of. Does anyone know how I can take an untyped parameter containing a variant and ensure that it will be disposed of properly? This will probably involve some inline assembly.
procedure ConvertVariant(var input; var output: variant);
begin
output := variant(input);
asm
//what do I put here? Input is still held in EAX at this point.
end;
end;
EDIT: Responding to Rob Kennedy's question in comments:
AnsiString conversion works like this:
procedure VarFromString2(var s : AnsiString; var v : Variant);
begin
v := s;
s := '';
end;
procedure StringToVar(var p; var v : Variant);
begin
asm
call VarFromString2
end;
end;
That works fine and doesn't produce memory leaks. When I try to do the same thing with a variant as the input parameter, and assign the original Null on the second procedure, the memory leaks still happen.
The variants mostly contain strings--the script in question is used to generate XML--and they got there by assigning a Delphi string to a variant in the Delphi function that this script is calling. (Changing the return type of the function wouldn't work in this case.)
Have you tried the same trick as with the string, except that with a Variant, you should put UnAssigned instead of Null to free it, like you did s := ''; for the string.
And by the way, one of the only reasons I can think of that requires to explicitly free the strings, Variants, etc... is when using some ThreadVar.

Improve speed of own debug visualizer for Delphi 2010

I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;

Resources