Getting friendly name of serial port from Windows Message in Delphi - delphi

I am having a problem acquiring the friendly name (dbcp_name) from DEV_BROADCAST_PORT using delphi.
I have tried using the microsoft help documentation which says it is a pointer to a null terminated string, but on that page there is a comment which indicates it is a variable-length structure, and dbcp_name is an array that contains the actual characters of the port name.
I have tried to extract this but I currently have not found a way as when I get it to return anything it is complete gibberish.
The code I have used follows:
PDevBroadcastPort = ^DEV_BROADCAST_PORT;
DEV_BROADCAST_PORT = packed record
dbcp_size : DWORD ;
dbcp_devicetype : DWORD;
dbcp_reserved : DWORD ;
dbcp_name : array[0..0] of ansichar; //TCHAR dbcp_name[1]; not valid
end;
I have tried different values for the length of the array, I had read somewhere that this was the correct declaration but I am not entirely sure. Also the commented out line is what the microsoft document gives for the line in C++
To extract the information I have tried this:
var
PData: PDevBroadcastPort;
FName: string;
...
PData := PDevBroadcastPort(Msg.lParam);
ShowMessage('Length '+Inttostr(length(PData^.dbcp_name)));
FName := '';
i:=0;
while((PData^.dbcp_name[i]) <> #0) and (i < 100) do
begin
FName := FName + (PData.dbcp_name[i]);
i := i +1;
ShowMessage(FName);
end;
I have tried setting the while loop to terminate at the length of the data structure but if I dont set it then it becomes huge.
Any help is appreciated and if I have left out any code which is needed for this question please let me know and I shall acquire it as soon as I can.
Thanks

The documentation doesn't say it's a pointer to a null-terminated string; it says it is a null-terminated string. That's typical for arrays that are declared at the end of a record with a length of just one element.
There's actually more memory after the designated record size, and that memory holds the remaining characters of the string. A pointer to that record field is also a pointer to the character data.
FName := PAnsiChar(#PData.dbcp_name);
Your array-traversing code should work, too, assuming you have bounds checking disabled for that stretch of code (or else you'd get an exception when the program detected you reading beyond the first element of the array).
That all presupposes that PData really is a pointer to a Dev_Broadcast_Port structure. You've given no information about what message you're handing, so I don't know whether you really have what you think you have.
If you're using Delphi 2009 or later, then the TCHAR type in the C declaration is equivalent to Delphi's WideChar type. Interpreting the field as an array of AnsiChar will get you wrong results, although for most port names, it might appear as though the array is a list of null-terminated one-character strings. Unless you're sure you have non-Unicode data, you should just use Char and PChar, and let the Delphi version determine which data type you have.
FName := PChar(#PData.dbcp_name);

The dbcp_name field contains the actual characters. The length of the character data, minus the null terminator, is dbcp_size - SizeOf(DEV_BROADCAST_PORT), so you can grab the name like this:
type
DEV_BROADCAST_PORTA = packed record
dbcp_size : DWORD;
dbcp_devicetype : DWORD;
dbcp_reserved : DWORD;
dbcp_name : array[0..0] of AnsiChar;
end;
DEV_BROADCAST_PORTW = packed record
dbcp_size : DWORD;
dbcp_devicetype : DWORD;
dbcp_reserved : DWORD;
dbcp_name : array[0..0] of WideChar;
end;
{$IFDEF UNICODE}
DEV_BROADCAST_PORT = DEV_BROADCAST_PORTW;
{$ELSE}
DEV_BROADCAST_PORT = DEV_BROADCAST_PORTA;
{$ENDIF}
PDEV_BROADCAST_PORT = ^DEV_BROADCAST_PORT;
.
var
PData: PDEV_BROADCAST_PORT;
FName: string;
...
PData := PDEV_BROADCAST_PORT(Msg.lParam);
SetString(FName, PData^.dbcp_name, (PData^.dbcp_size - SizeOf(DEV_BROADCAST_PORT)) div SizeOf(Char));
ShowMessage(FName);

Related

Passing strings from Delphi to Labview DLL

I have a simple Labview dll that takes a PascalString then returns the pascal string with no changes. This is just testing what we can do. The header is as follows:
void __stdcall Read_String_In_Write_String_Out(PStr String_input,
PStr String_output);
the Delphi code is as follows:
var
hbar : thandle;
str, str2 : PChar;
StringFunction : function (TestString: PChar): PChar; stdcall;
begin
hbar := LoadLibrary('C:\Interface.dll');
if hbar >= 32 then begin
StringFunction := getprocaddress(hbar, 'Read_String_In_Write_String_Out');
str := 'test';
str2 := StringFunction(str);
end;
end;
When running the program i get an Access Violation. I have no issues when doing simple math functions using dll's, but when it comes to strings everything breaks.
Can anyone help?
You say that the DLL function is taking in a Pascal String. According to Labview's documentation:
Pascal String Pointer is a pointer to the string, preceded by a length byte.
Pascal-Style Strings (PStr)
A Pascal-style string (PStr) is a series of unsigned characters. The value of the first character indicates the length of the string. A PStr can have a range of 0 to 255 characters. The following code is the type definition for a Pascal string.
typedef uChar Str255[256], Str31[32], *StringPtr, **StringHandle;
typedef uChar *PStr;
This would be equivalent to Delphi's ShortString type (well, more accurately, PShortString, ie a pointer to a ShortString).
Based on the DLL function's declaration, its 2nd parameter is not a return value, it is an input parameter taking in a pointer by value. So your use of StringFunction is wrong on 2 counts:
Getting the output in the wrong place. StringFunction should be a procedure with 2 parameters. However, the function can't modify the pointer in the 2nd parameter, all it can do is read/write data from/to whatever memory the pointer is pointing at. So, for output, you will have to pre-allocate memory for the function to write to.
Passing around the wrong kind of string data. PChar is PWideChar in Delphi 2009+, but "Pascal strings" use AnsiChar instead. And your test data is not even a Pascal string, as it lacks the leading length byte.
So, try something more like this instead:
var
hbar : THandle;
str1, str2 : ShortString;
StringFunction : procedure (String_input, String_output: PShortString); stdcall;
begin
hbar := LoadLibrary('C:\Interface.dll');
if hbar >= 32 then
begin
StringFunction := GetProcAddress(hbar, 'Read_String_In_Write_String_Out');
str1 := 'test';
StringFunction(#str1, #str2);
end;
end;

How to find out char code for a character of an Ansistring

In older versions of Delphi, like D7, you could do like ord(s[i]) where s was a string, but trying this with an AnsiString results in an exception (access violation).
P.S. I was w/delphi 7 for a long time.
Here are the steps to reproduce the error:
Create a new project and through a memo on the form (let it be memo1) than add the following code to the form create event handler:
procedure TForm1.FormCreate(Sender: TObject);
var u: ansistring;
begin
u := 'stringtest';
memo1.Lines.Add(inttostr(ord(u[2])));
end;
For me this code produces an AV.
It does work with an ansistring, but you cannot read past the end of it and you must make sure the string is initialized.
function CharCode(const S: ansistring; pos: integer): byte;
begin
if pos <= 0 then result:= 0
//else if s='' then Result:= 0 //unassigned string;
else if Length(s) < Pos then Result:= 0 //cannot read past the end.
else Result:= Ord(s[pos]);
end;
Note that if s='' is the same as asking if pointer(s) = nil. An empty string is really a nil pointer.
This is probably why you where getting an access violation.
If you want to force the ansistring to be a certain length you can use SetLength(MyAnsistring, NewLength);
The length of the (ansi)string is variable. That means it grows and shrinks as needed. If you read past the end of the string you may get an access violation.
Note that you don't have to get an AV, the RTL leaves a bit of slack in its allocation; it usually allocates a slightly bigger buffer than requested, this is due to performance and architectural reasons.
The other reason why you may not get an AV if reading past the end of a string is that your program may own both the string buffer and whatever happens to be right next to it.
For this reason it is a good idea to enable range checking in debug mode {$R+} it adds extra checks to protect against reading past the end of structures.
The difference between shortstring and (ansi)string
A short string has a fixed length and it lives on the stack.
A long string (ansi or wide) is a pointer to a record that gets allocated on the heap; it looks like this:
type
TStringRecord = record
CodePage: word;
ElementSize: word; //(1, 2 or 4)
ReferenceCount: integer;
Length: Integer;
StringData: array[1..length(s)] of char;
NullChar: char;
end;
The compiler hides all these details from you.
see: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Internal_Data_Formats

Delphi problems inserting a string, Incompatible types error

procedure TTelephoneNumberConverter.btnConvertClick(Sender: TObject);
var
number: string;
dupe: string;
converted: string;
begin
number := edtInput.Text ;
dupe := Copy(number, 4, 1) ;
converted := Insert(dupe , number , 4 ) ;
pnlOutput.Caption := converted;
end;
Ok guys I just have a quick question regarding Delphi 2010 and inserting strings into other strings. The purpose of this small piece of code is to take the 4th character in a specific string and to duplicate it and add it next to the specific character e.g. 12345 -> 123445
The only problem is I keep getting an error :
Incompatible types 'string' and 'procedure, untyped pointer or untyped parameter'.
I am probably missing something small and stupid but would appreciate if someone could maybe answer my question.
Insert is a procedure that modifies its second argument.
Its signature is:
procedure Insert(Source: string; var Dest: string; Index: Integer);
The compiler error you see occurs because Insert does not return anything and thus cannot be the rhs of an assignment.
Your code should therefore be:
converted := number;
Insert(dupe, converted, 4);
Copy is overkill for a single character. Use [] instead:
dupe := number[4];

Why might SetString intrinsic cause an "Incompatible types" error on PChar argument?

Please excuse the silly question, but I'm confused. Consider the following method (sorry for noisy comments, this is a real code under development):
function HLanguages.GetISO639LangName(Index: Integer): string;
const
MaxIso639LangName = 9; { see msdn.microsoft.com/en-us/library/windows/desktop/dd373848 }
var
LCData: array[0..MaxIso639LangName-1] of Char;
Length: Integer;
begin
{ TODO : GetLocaleStr sucks, write proper implementation }
//Result := GetLocaleStr(LocaleID[Index], LOCALE_SISO639LANGNAME, '??');
Length := GetLocaleInfo(LocaleID[Index], LOCALE_SISO639LANGNAME, #LCData, System.Length(LCData));
Win32Check(Length <> 0);
SetString(Result, #LCData, Length); // "E2008 Incompatible types" here, but why?
end;
If I remove the reference operator then implicit cast from $X+ comes to the rescue and method compiles. Why compiler refuses this code with reference operator is beyond my understanding.
This is Delphi XE2 and this behaviour might be specific to it.
And if I add a test-case dummy with equivalent prototype as intrinsic one within the scope of HLanguages.GetISO639LangName this error will magically go away:
procedure SetString(var s: string; buffer: PChar; len: Integer);
begin
{ test case dummy }
end;
You have to explicitly convert it to PChar:
SetString(result,PChar(#LCData),Length);
As you stated, SetString() is very demanding about the 2nd parameter type. It must be either a PChar either a PWideChar either a PAnsiChar, depending on the string type itself.
I suspect this is due to the fact that SetString() is defined as overloaded with either a string, a WideString, or an AnsiString as 1st parameter. So in order to validate the right signature, it needs to have exact match of all parameters types:
SetString(var s: string; buf: PChar; len: integer); overload;
SetString(var s: AnsiString; buf: PAnsiChar; len: integer); overload;
SetString(var s: WideString; buf: PWideChar; len: integer); overload;
Of course, all those are "intrinsics", so you won't find such definition in system.pas, but directly some procedure like _LStrFromPCharLen() _UStrFromPCharLen() _WStrFromPWCharLen() or such.
This behavior is the same since early versions of Delphi, and is not a regression in XE2.
I think there's a compiler bug in there because the behaviour with SetString differs from the behaviour with overloaded functions that you provide. What's more there's an interaction with the Typed # operator compiler option. I don't know how you set that. I always enable it but I suspect I'm in the minority there.
So I cannot explain the odd behaviour, and answer the precise question you ask. I suspect the only way to answer it is to look at the internals of the compiler, and very few of us can do that.
Anyway, in case it helps, I think the cleanest way to pass the parameter is like so:
SetString(Result, LCData, Length);
This compiles no matter what you set Typed # operator to.
I know this doesn't answer the specific question regarding SetString, but I'd like to point out that you can do the same thing by simply writing
Result := LCData;
When assigning to a string, Delphi treats a static array of char with ZERO starting index, as a Null terminated string with maximum length. Consider the following:
var
IndexOneArray : array [ 1 .. 9 ] of char;
IndexZeroArray : array [ 0 .. 8 ] of char;
S : string;
T : string;
begin
IndexOneArray := 'ABCD'#0'EFGH';
IndexZeroArray := 'ABCD'#0'EFGH';
S := IndexOneArray;
T := IndexZeroArray;
ShowMessage ( 'S has ' + inttostr(length(S)) + ' chars. '
+ #13'T has ' + inttostr(length(T)) + ' chars. ' );
end;
This displays a message that S has 9 chars, while T has 4.
It will also work when the zero-index array has 9 non-null characters. The result will be 9 characters regardless of what's in the following memory locations.
Because LCData is pointer to the array, not to the Char. Sure, sometimes it happens that an array or a record or a class start with char-type variable, but consequences are not what statically-typed compiler should rely upon.
You have to take the pointer to a character in that array, not to the array itself.
SetString(Result, #LCData[Low(LCData)], Length);

Assign String to Array of Characters

Question One
I have
var example : array[0..15] of char;
I want to assign the value from an input to that variable
example := inputbox('Enter Name', 'Name', '');
In the highscores unit I have record and array
type
points = record
var
_MemoryName : array[0..15] of char;
_MemoryScore : integer;
end;
var
rank : array[1..3] of points;
var s: string;
a: packed array[0..15] of char;
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a)) ;
returns -> (186): E2010 Incompatible types: 'array[0..15] of Char' and 'PWideChar'
var s: string;
a: packed array[0..15] of char;
s := InputBox('caption', 'Caption', 'Caption');
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(a) * sizeof(char));
scores.rank[1]._MemoryName := <<tried both s and a>> ;
returns (189): E2008 Incompatible types
Question One
There are many ways. One is:
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
a: packed array[0..15] of char;
begin
s := InputBox(Caption, Caption, Caption);
assert(length(s) <= 16);
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(s) * sizeof(char));
end;
But there might be a more elegant solution to your original problem, I suspect.
Question Two
Every time you wish a function/procedure didn't have a particular argument, you should realize that there might be a problem with the design of the project. Nevertheless, it isn't uncommon that Sender parameters are superfluous, because they are almost omnipresent because of the design of the VCL (in particular, the TNotifyEvent). If you know that the receiving procedure doesn't care about the Sender parameter, simply give it anything, like Self or nil.
Question Three
Consider this code:
procedure TForm4.FormCreate(Sender: TObject);
var
a: packed array[0..15] of char;
b: packed array[0..15] of char;
begin
a := b;
end;
This doesn't work. You cannot treat arrays like strings; in particular, you cannot assign static arrays like this (a := b).
Instead, you have to do something like...
Move(b[0], a[0], length(a) * sizeof(char));
...or simply loop and copy one value at a time. But the above simple assignment (a := b) does work if you declare a static array type:
type
TChrArr = packed array[0..15] of char;
procedure TForm4.FormCreate(Sender: TObject);
var
a: TChrArr;
b: TChrArr;
begin
b := a;
end;
Andreas has you covered for question 1.
Question 2
I would arrange that your event handler called another method:
procedure TForm5.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
RespondToEditControlKeyPress;
end;
That way you can just call RespondToEditControlKeyPress directly.
I'd guess that you want to call it with no parameters because you want code to run when the edit control's text is modified. You could perhaps use the OnChange event instead. And it may be that OnChange is more appropriate because pressing a key is not the only way to get text into an edit control.
By the way, it's better to ask one question at a time here on Stack Overflow.
For a quick way to copy string-type values into array-of-character type values. I suggest a small helper function like this:
procedure StrToCharArray( inputStr:String; var output; maxlen:Integer);
type
ArrayChar = Array[0..1] of Char;
begin
StrLCopy( PChar(#ArrayChar(output)[0]),PChar(inputStr),maxlen);
end;
Each time you call it, pass in the maximum length to be copied. Remember that if the buffer length is 15, you should pass in 14 as the maxlen, so that you leave room for the terminating nul character, if you intend to always terminate your strings:
StrToCharArray( UserInputStr, MyRecord.MyField, 14 );
This function will ensure that the data you copy into the record is null terminated, assuming that's what you wanted. Remember that in a fixed length character array it's up to you to decide what the rules are. Null terminated? Fully padded with spaces or null characters.... Strings and arrays-of-characters are so different, that there exist multiple possible ways of converting between the two.
If you don't intend to terminate your strings with nul, then you should use the FillChar+Move combination shown in someone else's answer.
The obvious answer is of course.
Don't use a packed array of char.
Use a string instead.
If you use ansistring, 1 char will always take 1 byte.
If you use shortstring ditto.
Ansistring is compatible with Pchar which is a pointer to a packed array of char.
So you can write
function inputbox(a,b,c: ansistring): pchar;
begin
Result:= a+b+c;
end;
var s: ansistring;
begin
s:= inputbox('a','b','c');
end;
Some advice
It looks like your are translating code from c to Delphi.
a packed array of char is exactly the same as the old (1995) shortstring minus the length byte at the beginning of shortstring.
The only reason I can think of to use packed array of char is when you are reading data to and from disk, and you have legacy code that you don't want to change.
I would keep the legacy code to read and write from disk and then transfer the data into an ansistring and from there on only use ansistring.
It's soooooooo much easier, Delphi does everything for you.
And... ansistring is much faster, gets automatically created and destroyed, can have any length (up to 2GB), uses less memory --because identical strings only get stored once (which means stringa:= stringb where a string is 20 chars is at least 5x faster using ansistrings than array's of char).
And of course best of all, buffer overflow errors are impossible with ansistring.
What about unicodestring?
Unicodestring is fine to use, but sometimes translation of chars happens when converting between packed array of char and unicodestring, therefore I recommend using ansistring in this context.
What you try to do is impossible, indeed:
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a));
That tries to assign a pointer (the result of StrPLCopy, a PWideChar in the last few versions of Delphi) to an array, which is indeed impossible. You can't copy an array like that. I would do:
StrLCopy(highscoresdata.position[1]._MemoryName, PChar(s),
Length(highscoresdata.position[1]._MemoryName));
That should work, and is IMO the simplest solution to copy a string to an array of characters. There is no need to use a as some kind of intermediate, and using Move is, IMO, rather low level and therefore a little tricky (it is easy to forget to multiply by the size of a character, it is unchecked, it does not add a #0, etc.), especially if you don't know what exactly you are doing.
This solution should even work for versions of Delphi before Delphi 2009, as it does not rely on the size of the character.
FWIW, I would not use packed arrays. Packed doesn't have a meaning in current Delphi, but could confuse the compiler and make the types incompatible.

Resources