I have two strings, which I need to compare for equality.
String 1 is created in this way:
var
inBuf: array[0..IN_BUF_SIZE] of WideChar;
stringBuilder : TStringBuilder;
mystring1:string;
...
begin
stringBuilder := TStringBuilder.Create;
for i := startOfInterestingPart to endOfInterestingPart do
begin
stringBuilder.Append(inBuf[i]);
end;
mystring1 := stringBuilder.ToString();
stringBuilder.Free;
String 2 is a constant string 'ABC'.
When string 1 is displayed in a debug console, it is equal to 'ABC'. But the comparisons
AnsiCompareText(mystring1, 'ABC')
mystring1 = 'ABC'
CompareStr(mystring1, 'ABC')
all report inequality.
I suppose that I need to convert string 2 ('ABC') to the same type as the string 1.
How can I do that?
Update 26.09.2012:
aMessage is displayed in the log output as {FDI-MSG-START-Init-FDI-MSG-END}
Here's the code for printing the length of strings:
StringToWideChar('{FDI-MSG-START-Init-FDI-MSG-END}', convString, iNewSize);
...
OutputDebugString(PChar('Len (aMessage): ' + IntToStr(Length(aMessage))));
OutputDebugString(PChar('Len (original constant): ' + IntToStr(Length('{FDI-MSG-START-Init-FDI-MSG-END}'))));
OutputDebugString(PChar('Len (convString): ' + IntToStr(Length(convString))));
And here's the log output:
[3580] Len (aMessage): 40
[3580] Len (original constant): 32
[3580] Len (convString): 0
It looks like you're keeping garbage data in your wide string after the meaningful part, in your update, Length(aMessage) returns 40, while your source string's length is 32.
In Delphi a wide string is COM BSTR compatible, meaning it can hold null characters, a null does not terminate it, it keeps its length at a negative offset of the character data. A possible null character in it helps it to be converted to other string types, but it doesn't alter its own termination.
Consider the below,
const
Source = '{FDI-MSG-START-Init-FDI-MSG-END}';
var
ws: WideString;
size: Integer;
begin
size := 40;
SetLength(ws, size);
StringToWideChar(Source, PWideChar(ws), size);
// the below assertion fails when uncommented
// Assert(CompareStr(Source, ws) = 0);
ws := PWideChar(ws); // or SetLength(ws, Length(Source));
// this assertion does not fail
Assert(CompareStr(Source, ws) = 0);
end;
Related
I have a C DLL with a number of functions I'm calling from Delphi. One of the functions (say Func1) returns a pointer to a struct - this all works fine. The structs created by calling Func1 are stored in a global pool within the DLL. Using a second function (Func2) I get a pointer to a block of memory containing an array of pointers, and I can access the array elements using an offset.
I need to be able copy the address in the returned pointer for a struct (from Func1) to any of the memory locations in the array (from Func2). The idea is that I can build arrays of pointers to pre-defined structs and access the elements directly from Delphi using pointer offsets.
I tried using:
CopyMemory(Pointer(NativeUInt(DataPointer) + offset), PStruct, DataSize);
where DataPointer is the start of my array and PStruct is returned from Func1, but that doesn't copy the address I need.
In .NET it works using Marshal.WriteIntPtr and looking at the underlying code for this using Reflector I think I need something trickier than CopyMemory. Anyone got any ideas for doing this in Delphi?
Edit: This is part of a wrapper around vector structures returned from the R language DLL. I have a base vector class from which I derive specific vector types. I've got the wrapper for the numeric vector working, so my base class looks fine and this is where I get DataPointer:
function TRVector<T>.GetDataPointer: PSEXPREC;
var
offset: integer;
h: PSEXPREC;
begin
// TVECTOR_SEXPREC is the vector header, with the actual data behind it.
offset := SizeOf(TVECTOR_SEXPREC);
h := Handle;
result := PSEXPREC(NativeUInt(h) + offset);
end;
Setting a value in a numeric vector is easy (ignoring error handling):
procedure TNumericVector.SetValue(ix: integer; value: double);
var
PData: PDouble;
offset: integer;
begin
offset := GetOffset(ix); // -- Offset from DataPointer
PData := PDouble(NativeUInt(DataPointer) + offset);
PData^ := value;
end;
For a string vector I need to (i) create a base vector of pointers with a pre-specified length as for the numeric vector (ii) convert each string in my input array to an R internal character string (CHARSXP) using the R mkChar function (iii) assign the address of the character string struct to the appropriate element in the base vector. The string array gets passed into the constructor of my vector class (TCharacterVector) and I then call SetValue (see below) for each string in the array.
I should have thought of PPointer as suggested by Remy but neither that or the array approach seem to work either. Below is the code using the array approach from Remy and with some pointer vars for checking addresses. I'm just using old-fashioned pointer arithmetic and have shown addresses displayed for a run when debugging:
procedure TCharacterVector.SetValue(ix: integer; value: string);
var
PData: PSEXPREC;
offset: integer;
offset2: integer;
PTest: PSEXPREC;
PPtr: Pointer;
PPtr2: Pointer;
begin
offset := GetOffset(ix);
PPtr := PPointer(NativeUInt(DataPointer) + offset); // $89483D8
PData := mkChar(value); // $8850258
// -- Use the following code to check that mkChar is working.
offset2 := SizeOf(TVECTOR_SEXPREC);
PTest := PSEXPREC(NativeUInt(PData) + offset);
FTestString := FTestString + AnsiString(PAnsiChar(PTest));
//PPointerList(DataPointer)^[ix] := PData;
//PPtr2 := PPointer(NativeUInt(DataPointer) + offset); // Wrong!
PPointerArray(DataPointer)^[ix] := PData;
PPtr2 := PPointerArray(DataPointer)^[ix]; // $8850258 - correct
end;
I'd have thought the address in PData ($8850258) would now be in PPtr2 but I've been staring at this so long I'm sure I'm missing something obvious.
Edit2: The code for SetValue used in R.NET is as follows (ignoring test for null string):
private void SetValue(int index, string value)
{
int offset = GetOffset(index);
IntPtr stringPointer = mkChar(value);
Marshal.WriteIntPtr(DataPointer, offset, stringPointer);
}
From reflector, Marshal.WriteIntPtr uses the following C:
public static unsafe void WriteInt32(IntPtr ptr, int ofs, int val)
{
try
{
byte* numPtr = (byte*) (((void*) ptr) + ofs);
if ((((int) numPtr) & 3) == 0)
{
*((int*) numPtr) = val;
}
else
{
byte* numPtr2 = (byte*) &val;
numPtr[0] = numPtr2[0];
numPtr[1] = numPtr2[1];
numPtr[2] = numPtr2[2];
numPtr[3] = numPtr2[3];
}
}
catch (NullReferenceException)
{
throw new AccessViolationException();
}
}
You say you want to copy the struct pointer itself into the array, but the code you have shown is trying to copy the struct data that the pointer is pointing at. If you really want to copy just the pointer itself, don't use CopyMemory() at all. Just assign the pointer as-is:
const
MaxPointerList = 255; // whatever max array count that Func2() allocates
type
TPointerList = array[0..MaxPointerList-1] of Pointer;
PPointerList = ^TPointerList;
PPointerList(DataPointer)^[index] := PStruct;
Your use of NativeUInt reveals that you are using a version of Delphi that likely supports the {$POINTERMATH} directive, so you can take advantage of that instead, eg:
{$POINTERMATH ON}
PPointer(DataPointer)[index] := PStruct;
Or, use the pre-existing PPointerArray type in the System unit:
{$POINTERMATH ON}
PPointerArray(DataPointer)[index] := PStruct;
I'm trying to read returned arrays from the TeamSpeak3 SDK, some of the methods returns arrays that are null terminated and multi dimensional with a mix of data types.
What "delhpi" structure should I pass as parameter and how can I read the returned values back in the a matching structure? a la.
type
TDeviceInfo = record
DeviceId : string; // maybe an integer
DeviceName : string;
end;
TDeviceInfoArr = array of TDeviceInfo
// or maybe
TDeviceInfoArr = array of array[0..1] of string;
var
DeviceArr : array of TDeviceInfoArr;
This is what the SDK Documentation says.
To get a list of all available playback and capture devices for the specified mode, call
unsigned int ts3client_getPlaybackDeviceList(modeID, result);
const char* modeID;
char**** result;
unsigned int ts3client_getCaptureDeviceList(modeID, result);
const char* modeID;
char**** result;
Parameters
• modeID
Defines the playback/capture mode to use. For different modes there might be different device lists. Valid modes are returned by
ts3client_getDefaultPlayBackMode / s3client_getDefaultCaptureMode and ts3client_getPlaybackModeList / ts3client_getCaptureModeList.
• result
Address of a variable that receives a NULL-terminated array { { char* deviceName, char* deviceID }, { char* deviceName, char* deviceID }, ... , NULL }.
Unless the function returns an error, the elements of the array and the array itself need to be freed using ts3client_freeMemory.
Returns ERROR_ok on success, otherwise an error code as defined in public_errors.h. In case of an error, the result array is uninitialized and must not be released.
Example to query all available playback devices:
char * defaultMode;
if (ts3client_getDefaultPlayBackMode( & defaultMode) == ERROR_ok) {
char * * * array;
if (ts3client_getPlaybackDeviceList(defaultMode, & array) == ERROR_ok) {
for (int i = 0; array[i] != NULL; ++i) {
printf("Playback device name: %s\n", array[i][0]); /* First element: Device name */
printf("Playback device ID: %s\n", array[i][1]); /* Second element: Device ID */
/* Free element */
ts3client_freeMemory(array[i][0]);
ts3client_freeMemory(array[i][1]);
ts3client_freeMemory(array[i]);
}
ts3client_freeMemory(array); /* Free complete array */
} else {
printf("Error getting playback device list\n");
}
} else {
printf("Error getting default playback mode\n");
}
First of all, I'm going to ignore error handling because I think we handled that in your last question. And I'm going to assume that ts3client_getDefaultPlayBackMode presents no problems.
So that leaves ts3client_getPlaybackDeviceList. Import it like this:
function ts3client_getPlaybackDeviceList(modeID: PAnsiChar;
out result: PPPAnsiChar): Cardinal; cdecl; external '...';
You will likely need to define PPPAnsiChar.
type
PPPAnsiChar = ^PPAnsiChar;
PPAnsiChar = ^PAnsiChar;
You might find that the RTL already defines PPAnsiChar.
So, next to calling the function. First of all declare a variable to hold the array, and so others to help iterate:
var
arr, myarr: PPPAnsiChar;
p: PPAnsiChar;
Then call the function:
ts3client_getPlaybackDeviceList(modeID, arr);
myarr := arr;
while myarr^ <> nil do
begin
p := myarr^;
Writeln('Playback device name: ', p^);
ts3client_freeMemory(p^);
inc(p);
Writeln('Playback device ID: ', p^);
ts3client_freeMemory(p^);
ts3client_freeMemory(myarr^);
inc(myarr);
end;
ts3client_freeMemory(arr);
This code is really quite vile I'm sure that you will agree. If you have a modern version of Delphi then you can enable pointer math to make it read better.
{$POINTERMATH ON}
ts3client_getPlaybackDeviceList(modeID, arr);
i := 0;
while arr[i] <> nil do
begin
Writeln('Playback device name: ', arr[i][0]);
Writeln('Playback device ID: ', arr[i][1]);
ts3client_freeMemory(arr[i][0]);
ts3client_freeMemory(arr[i][1]);
ts3client_freeMemory(arr[i]);
inc(i);
end;
ts3client_freeMemory(arr);
Although this code is better, it will never win a beauty contest.
Remember that I've neglected all error checking. You'll need to add that.
Based on David's suggesstions I found the following code working, thanks David!
{$POINTERMATH ON}
procedure TfrmMain.RequestPlaybackDevices;
var
arr, myarr: PPPAnsiChar;
p: PPAnsiChar;
defaultmode : PAnsiChar;
i : Integer;
begin
try
ts3check(ts3client_getDefaultPlayBackMode(#defaultmode));
ts3check(ts3client_getPlaybackDeviceList(defaultMode, #arr));
try
i := 0;
while arr[i] <> nil do
begin
LogMsg(format('Playback device name: %s',[UTF8ToUnicodeString(arr[i][0])]));
LogMsg(format('Playback device ID: %s',[UTF8ToUnicodeString(arr[i][1])]));
ts3client_freeMemory(arr[i][0]);
ts3client_freeMemory(arr[i][1]);
ts3client_freeMemory(arr[i]);
inc(i);
end;
finally
ts3client_freeMemory(arr);
end;
except
on e: exception do LogMsg(Format('Error RequestPlaybackDevices: %s', [e.Message]));
end;
end;
{$POINTERMATH OFF}
I have a string that contains 8 to 12 characters (alphanumeric). I would like to use the Format function to format it such that after first 4 characters a hyphen to be inserted and after next 3 characters another hyphen to be inserted:
cccc-ccc-c
if string has 8 chrs
cccc-ccc-cc
if string has 9 chrs
cccc-ccc-ccc
if string has 10 chrs
cccc-ccc-cccc
if string has 11 chrs
cccc-ccc-ccccc
if string has 12 chrs
Is it possible to use a single lined Format function to acquire the effect? I admit that the usage of Format function is beyond my grasp.
The function you are looking for is FormatMaskText located in System.MaskUtils. The Mask to be used is 'cccc-ccc-ccccc;0;'.
Use Insert instead of Format:
Insert(s, '-', 5);
Insert(s, '-', 9);
There is no built-in format specifier (or combination of them) that will do the formatting you're looking to do.
You can, of course, write your own function to do so (name it, of course, with something meaningful to the values you're formatting):
function MyFormat(Value: string): String;
begin
Assert(Length(Value) >= 8);
Result := System.Insert(Value, '-', 5);
Result := System.Insert(Result,'-', 9);
end;
Use it:
Value := MyFormat('12345678'); // Returns '1234-567-8'
Value := MyFormat('123456789'); // Returns '1234-567-89'
Value := MyFormat('1234567890'); // Returns '1234-567-890'
If you insist on trying to do it with Format, you need multiple calls to Copy (although you can skip the first one by using a width specifier). These can be done, of course, on a single line; I've spread it across multiple just for formatting here to eliminate horizontal scrolling.
Str := '12345678';
Value := Format('%.4s-%s-%s',
[Str,
Copy(Str, 5, 3),
Copy(Str, 8, MaxInt)]); // Return '1234-567-8'
Str := '1234567890';
Value := Format('%.4s-%s-%s',
[Str,
Copy(Str, 5, 3),
Copy(Str, 8, MaxInt)]); // Return '1234-567-890'
There is no way to use a "width specifer" type method to extract substrings within a string, though. (You can extract the first n characters using %.ns, but you can't do the first n characters starting at the fourth with any combination of specifiers.)
I'm tryin to scan an entire process memory but no success... What I'm doing is: for tests I'm using notepad, so I write there %B and this values in HEX are: 25(%) and 42(B). So the code is:
while (VirtualQueryEx(PIDHandle, Pointer(MemStart), MemInfo, SizeOf(MemInfo)) <> 0) do
begin
if ((MemInfo.State = MEM_COMMIT) and (not (MemInfo.Protect = PAGE_GUARD)
or (MemInfo.Protect = PAGE_NOACCESS)) and (MemInfo.Protect = PAGE_READWRITE)) then
begin
SetLength(Buff, MemInfo.RegionSize);
if (ReadProcessMemory(PIDHandle, MemInfo.BaseAddress, Buff,
MemInfo.RegionSize, ReceivedBytes)) then
begin
for I := 0 to SizeOf(Buff) do
begin
if (IntToHex(Buff[i], 1) = '25') and (IntToHex(Buff[i+2], 1) = '42') then
Form1.Memo1.Lines.Append(IntToHex(Buff[i], 1));
end;
end;
end;
MemStart:= MemStart + MemInfo.RegionSize;
end;
CloseHandle(PIDHandle);
end;
The var 'Buff' is TBytes (I read about TBytes and think it's same as array of byte). So I'm converting the bytes to Hex, and searching for values: 25 and 42 respectively. The code is like:
if (IntToHex(Buff[i], 1) = '25') and (IntToHex(Buff[i+2], 1) = '42') then
Because have 00 between the hex values. So I need to add '+2'. How can I scan the entire memory for this values??
Notepad uses Unicode so you'll need to look for UTF-16 encoded data, $0025 and $0042.
I don't understand why you feel the need to convert into hex strings before comparing. There's nothing special about hex that requires the use of strings. Hexadecimal is just a number system with base-16. So, decimal 32 is the same as hexadecimal 20, i.e. 32=$20. Do your comparison directly with integral values:
if (Buff[i]=$25) and (Buff[i+2]=$42) then
That said, taking into account the $00 bytes your test should really be something like this:
var
Target: string;
....
Target := '%B';
if CompareMem(#Buff[i], #Target[1], Length(Target)*SizeOf(Char)) then
....
I don't want to get too deep into the rest of your code, but this line
for I := 0 to SizeOf(Buff) do
is wrong on many different levels.
SizeOf(Buff) returns the size of a pointer since a dynamic array variable is essentially just a pointer. A useful thing to remember is that SizeOf is evaluated at compile time.
If you used Length instead of SizeOf then you would be iterating over the end of the list. To loop over a dynamic array, loop from 0 to Length(...)-1.
But in this case you are accessing index i+2 inside the loop, so you should loop from 0 to Length(...)-3.
But in fact you need to compare against 4 consecutive bytes to find a match. Perhaps like this:
TargetByteLength = Length(Target)*SizeOf(Char);
for i := 0 to Length(Buff)-TargetByteLength do
if CompareMem(#Buff[i], #Target[1], TargetByteLength) then
....
I am working on a COM Object library with function that returns a VARIANT with a SAFEARRAY of BSTRs. How can I display the values from this VARIANT instance and save it inside a TStringList? I tried searching the net with no clear answer.
I tried the following with no success:
Variant V;
String mystr;
VarClear(V);
TVarData(V).VType = varOleStr;
V = ComFunction->GetValues(); //<<<<----- V is empty
mystr = (wchar_t *)(TVarData(V).VString);
Memo1->Lines->Add(mystr);
VarClear(V);
You can use TWideStringDynArray and let Delphi do the conversion:
procedure LoadStringsFromVariant(const Values: TWideStringDynArray; Strings: TStrings);
var
I: Integer;
begin
Strings.BeginUpdate;
try
for I := Low(Values) to High(Values) do
Strings.Add(Values[I]);
finally
Strings.EndUpdate;
end;
end;
When you call this with your Variant safearray of BSTRs it will be converted to TWideStringDynArray automatically. An incompatible Variant will cause the runtime error EVariantInvalidArgError.
To check if a Variant holds a safe array of BSTR you can do this:
IsOK := VarIsArray(V) and (VarArrayDimCount(V) = 1) and (VarType(V) and varTypeMask = varOleStr);
uses ActiveX;
var
VSafeArray: PSafeArray;
LBound, UBound, I: LongInt;
W: WideString;
begin
VSafeArray := ComFunction.GetValues();
SafeArrayGetLBound(VSafeArray, 1, LBound);
SafeArrayGetUBound(VSafeArray, 1, UBound);
for I := LBound to UBound do
begin
SafeArrayGetElement(VSafeArray, I, W);
Memo1.Lines.Add(W);
end;
SafeArrayDestroy(VSafeArray); // cleanup PSafeArray
if you are creating ComFunction via late binding (CreateOleObject) you should use:
var
v: Variant;
v := ComFunction.GetValues;
for i := VarArrayLowBound(v, 1) to VarArrayHighBound(v, 1) do
begin
W := VarArrayGet(v, [i]);
Memo1.Lines.Add (W);
end;
How can I display the values from this VARIANT instance and save it inside a TStringList?
The COM VARIANT struct has parray and pparray data members that are pointers to a SAFEARRAY, eg:
VARIANT V;
LPSAFEARRAY sa = V_ISBYREF(&V) ? V_ARRAYREF(&V) : V_ARRAY(&V);
The VCL Variant class, on the other hand, has an LPSAFEARRAY conversion operator defined, so you can assign it directly (but only if the Variant.VType field that not have the varByRef flag present, that is), eg:
Variant V;
LPSAFEARRAY sa = V;
Either way, once you have the SAFEARRAY pointer, use the SafeArray API to access the BSTR values, eg:
bool __fastcall VariantToStrings(const Variant &V, TStrings *List)
{
// make sure the Variant is holding an array
if (!V_ISARRAY(&V)) return false;
// get the array pointer
LPSAFEARRAY sa = V_ISBYREF(&V) ? V_ARRAYREF(&V) : V_ARRAY(&V);
// make sure the array is holding BSTR values
VARTYPE vt;
if (FAILED(SafeArrayGetVartype(sa, &vt))) return false;
if (vt != VT_BSTR) return false;
// make sure the array has only 1 dimension
if (SafeArrayGetDim(sa) != 1) return false;
// get the bounds of the array's sole dimension
LONG lBound = -1, uBound = -1;
if (FAILED(SafeArrayGetLBound(sa, 0, &lBound))) return false;
if (FAILED(SafeArrayGetUBound(sa, 0, &uBound))) return false;
if ((lBound > -1) && (uBound > -1))
{
// access the raw data of the array
BSTR *values = NULL;
if (FAILED(SafeArrayAccessData(sa, (void**)&values))) return false;
try
{
List->BeginUpdate();
try
{
// loop through the array adding the elements to the list
for (LONG idx = lBound; l <= uBound; ++idx)
{
String s;
if (values[idx] != NULL)
s = String(values[idx], SysStringLen(values[idx]));
List->Add(s);
}
}
__finally
{
List->EndUpdate();
}
}
__finally
{
// unaccess the raw data of the array
SafeArrayUnaccessData(sa);
}
}
return true;
}
VarClear(V);
TVarData(V).VType = varOleStr;
You don't need those at all. The VCL Variant class initializes itself to a blank state, and there is no need to assign the VType since you are assigning a new value to the entire Variant immediately afterwards.
V = ComFunction->GetValues(); //<<<<----- V is empty
If V is empty, then GetValues() is returning an empty Variant to begin with.
mystr = (wchar_t *)(TVarData(V).VString);
TVarData::VString is an AnsiString& reference, not a wchar_t* pointer. To convert a VCL Variant (not a COM VARIANT) to a String, just assign it as-is and let the RTL works out the detail for you:
String mystr = V;