I have class with integers and string and when i use integers everything is fine but when i use string program is crashing
//class
CatInfoType = class(TRemotable)
private
FcatId: Integer;
FcatName: string;
FcatParent: Integer;
FcatPosition: Integer;
FcatIsProductCatalogueEnabled: Integer;
published
property catId : Integer read FcatId write FcatId;
property catName : string read FcatName write FcatName;
property catParent : Integer read FcatParent write FcatParent;
property catPosition : Integer read FcatPosition write FcatPosition;
property catIsProductCatalogueEnabled: Integer
read FcatIsProductCatalogueEnabled
write FcatIsProductCatalogueEnabled;
end;
//program code
procedure TForm2.Button7Click(Sender: TObject);
var
rc: Integer;
k: CatInfoType;
l:String;
begin
k.catId:=4646;
k.catName:='777';//that crashing the program
end;
No...not quite
k.catId:=4646; // <--- that crashing the program
k.catName:='777';
The error message you could have included would have been something like
Access Violation at address xxxxxxxx in module 'MyProject.exe'. Read of address xxxxxxxx.
Here k is a class of CatInfoType - you haven't yet instantiated it, however. What you want is :
k := CatInfoType.Create;
k.catId := 4646;
//... etc
You failed to instantiate an instance. That would be done with
k := CatInfoType.Create(...);
Related
The title doesn't quite capture the essence of the issue.
I have a UDF function that returns a PChar.
function AccountDescription(sAccountId: PChar) : PChar; stdcall;
This was working fine but I realized I wanted to return #N/A if the accountId was not found.
I discovered CVErr(xlErrNA) and changed the Signature to return OleVariant.
But now I am receiving [Error] Incompatible types: 'OleVariant' and 'PAnsiChar'.
I could not find any information on how to resolve this so I figure my understanding of the problem must not be correct.
I tried just passing a string which compiled but produced a runtime error of "Invalid variant type".
The full code is:
function AccountDescription(sAccountId: PChar): OleVariant; stdcall;
var
strResult: string;
strPChar : PChar;
begin
try
strResult:= repo.GetAccount(sAccountId).Description;
strPChar := strAlloc(length(strResult)+1) ;
StrPCopy(strPChar, strResult) ;
Result := strPChar;
except
Result := CVErr(xlErrNA);
end;
end;
Note: Is excel responsible for destroying the string or is that my cleanup? Should I be creating a copy or should I just be returning a pointer to an existing string. After typing it I feel like I should be returning a pointer.
Update:
Removed some irrelevant code in the example.
Now using:
function AccountDescription(sAccountId: PChar): OleVariant; stdcall;
var
strResult: string;
begin
try
Result := PChar(repo.GetAccount(sAccountId).Description);
except
Result := CVErr(xlErrNA);
end;
end;
You do not need the PChar cast, you can assign a String directly to an OleVariant (it will be converted by the RTL into a BSTR that the receiver will then free when done using it):
Result := repo.GetAccount(sAccountId).Description;
As for reporting an error, do you have a viable CVErr() function in your Delphi code? In VB, CVErr() returns a Variant of type Error (varError in Delphi) containing an error code (xlErrNA is 2042). Delphi has a VarAsError() function for that same purpose:
Result := VarAsError(2042);
So, I'm trying to make a component that will do the job on setting the settings of a excel, libreoffice, etc... cells. At first I just wanted to set the value, but now, I need to change cell background color, change font name, style, set a formula, etc... So for that, I decided to do a type that will hold all the things I want to change and so, I did this:
type
TMyCell = class
private
FBgColor: TColor;
FValue: String;
FFormula: String;
FFormat: String;
FFont: TFont;
public
constructor Create;
destructor Destroy;
property Value: String read FValue write FValue;
property Formula: String read FFormula write FFormula;
property Format: String read FFormat write FFormat;
property BgColor: TColor read FBgColor write FBgColor;
property Font: TFont read FFont write FFont;
end;
{ TMyCell }
constructor TMyCell.Create;
begin
FFont := TFont.Create;
end;
destructor TMyCell.Destroy;
begin
FFont.Free;
end;
And my component look like this:
type
TMyPlan = class(TComponent)
private
FExcel: Variant;
procedure SetMyCell(Row, Column: Integer; Value: TMyCell);
function GetMyCell(Row, Column: Integer): TMyCell;
public
constructor Create(AOwner: TComponent);
destructor Destroy;
property Cell[Row, Column: Integer]: TMyCell read GetMyCell write SetMyCell;
end;
{ TMyPlan }
constructor TMyPlan.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FExcel := CreateOleObject('Excel.Application');
FExcel.Workbooks.Add(1);
end;
destructor TMyPlan.Destroy;
begin
FExcel := Unassigned;
inherited;
end;
function TMyPlan.GetMyCell(Row, Column: Integer): TMyCell;
begin
Result := TMyCell.Create;
Result.Value := FExcel.Cells[Row, Column];;
end;
procedure TMyPlan.SetMyCell(Row, Column: Integer; Value: TMyCell);
begin
FExcel.Cells[Row, Column] := Value.Value;
end;
Just to let you know, I already did some components, and I'm still learning how to do them properly, so this may have a not decent structure, anyway, this is the first time that I'm trying to do something like this, a property that has input parameters with subproperties, and it doesn't seem to work as I though it would.
Back to the topic, it doesn't matter how I call my property
Set: MyPlan.Cell[1, 1].Value := '1';
Get: ShowMessage(MyPlan.Cell[1, 1].Value);
Either way only the GetMyCell function is triggered. Why's that?
See my answer to this question: "Left side cannot be assigned to" for record type properties in Delphi
While what you're doing isn't quite the same thing, it is similar. However, in your case, you're allocating a new instance of TMyCell for every access to GetMyCell. This "temporary" instance is isn't being freed and will leak (Unless you're doing this on one of the ARC platforms).
The reason your SetMyCell isn't being called is because you're not actually setting the cell itself, rather you're setting a value on the cell instance (that I explained above is leaking).
I have a Delphi 5 legacy application and there's a part in which a "string" value is been assigned to an "OleVariant" variable. Something like this:
var
X: OleVariant;
S: string;
Begin
S:= ‘This string should contain 200 characters as per design’;
X:= S;
End;
If the length of “S” is greater than 128, then the value of “X” gets truncated and it only holds a maximum of 128 characters.
Is there a way to overcome this?
I believe there is a way, because if I create my own demo application from scratch (in the same PC, with the same Delphi 5), it allows me to pass longer string values and no truncating is done.
Maybe it is something about the project settings or compiler directives. I have played around with this idea, but I have no workaround yet.
Any help is appreciated. Thanks.
Demo:
procedure TForm1.Button1Click(Sender: TObject);
var
X: OleVariant;
S: string;
begin
//in the Edit I pass a string of 240 chars, let's say;
S:= Edit1.Text;
X:= S;
ShowMessage(IntToStr(Length(X)) + ' : ' + IntToStr(Length(S)));
//this showmessage shows "128 : 240"
end;
Try this OleVariantToString and StringToOleVariant functions at http://www.foxbase.ru/delphi/vzaimnye-preobrazovaniya-olevariant-i-string.htm
They work perfectly for me.
uses Classes, Variants;
function OleVariantToString(const Value: OleVariant): string;
var ss: TStringStream;
Size: integer;
Data: PByteArray;
begin
Result:='';
if Length(Value) = 0 then Exit;
ss:=TStringStream.Create;
try
Size := VarArrayHighBound (Value, 1) - VarArrayLowBound(Value, 1) + 1;
Data := VarArrayLock(Value);
try
ss.Position := 0;
ss.WriteBuffer(Data^, Size);
ss.Position := 0;
Result:=ss.DataString;
finally
VarArrayUnlock(Value);
end;
finally
ss.Free;
end;
end;
function StringToOleVariant(const Value: string): OleVariant;
var Data: PByteArray;
ss: TStringStream;
begin
Result:=null;
if Value='' then Exit;
ss:=TStringStream.Create(Value);
try
Result := VarArrayCreate ([0, ss.Size - 1], varByte);
Data := VarArrayLock(Result);
try
ss.Position := 0;
ss.ReadBuffer(Data^, ss.Size);
finally
VarArrayUnlock(Result);
end;
finally
ss.Free;
end;
end;
One explanation is that OleVariant holds the entire string but that you are looking at the debugger tooltip. In older Delphi versions the debugger tooltip truncates at 128 characters for strings held in a variant. Note that the debugger tooltip for a plain string does not truncate at this length. Try showing the variant in a dialog box and you will see that the entire string is present.
I checked this out on Delphi 6 and there was no truncation with your code (other than the debugger tooltip). Andreas did likewise on Delphi 4 and Rodrigo did so with Delphi 5. I cannot imagine that it could really be the case that strings in a Delphi 5 OleVariant are truncated at 128 characters.
If you really are seeing what you are report then I can think of the following explanations:
Your code is erroneously truncating the string, but you have not yet found the code that does this. Only you can debug that.
You have a local bug private to your Delphi installation. Are you by any chance compiling your own RTL?
I made this work. Summary: instead of filling an “OleVariant” with a “string”; I filled a “Variant” and then typecasted that “Variant” to “OleVariant”. Take a look at the code below so that you can get the idea.
procedure TForm1.Button1Click(Sender: TObject);
var
//X: OleVariant;
X: Variant;
S: string;
begin
//Let's say in the Edit1 I pass a string of 240 chars,
S:= Edit1.Text;
X:= S;
//ShowMessage(IntToStr(Length(X)) + ' : ' + IntToStr(Length(S)));
ShowMessage(IntToStr(Length(OleVariant(X))) + ' : ' + IntToStr(Length(S)));
//This ShowMessage shows "128 : 240"
end;
Honestly, I don’t know for sure why this makes a difference, but it does. It works ok now.
Thanks a lot for your help folks!
The given code which works without any problems in Delphi 2007. However in Delphi 2009 I am getting an exception.
Access violation shows read of address $00000000.
The problem exists only when assigning string, it works for numbers.
Also, when I am assigning Data.Text manually via the debugger options I am getting no AV - it works.
Honestly I am lost, anyone could help me with this please?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, VirtualTrees, StdCtrls;
type
TTest = record
Text: String;
Number: Integer;
end;
PTest = ^TTest;
type
TTestArray = array of TTest;
type
TForm1 = class(TForm)
VirtualStringTree1: TVirtualStringTree;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure VirtualStringTree1InitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
TestArray: array of TTest;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
SetLength(TestArray, 1);
TestArray[0].Text := 'test';
TestArray[0].Number := 12345;
VirtualStringTree1.AddChild(VirtualStringTree1.RootNode, #TestArray[0]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
VirtualStringTree1.NodeDataSize := SizeOf(TTest);
end;
procedure TForm1.VirtualStringTree1InitNode(Sender: TBaseVirtualTree;
ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
Data: PTest;
NodeData: PPointer;
begin
Data := Sender.GetNodeData(Node);
NodeData := Sender.GetNodeData(Node);
Data.Number := PTest(NodeData^)^.Number;
Data.Text := PTest(NodeData^)^.Text; //crash here!
end;
end.
When you call AddChild(..., #TestArray[0]), you're only initializing the first four bytes of the node's data. That's the Text field. The Text field holds a pointer to a TTest structure. It's supposed to hold a string reference.
The GetNodeData function returns a pointer to the node's data. The tree control has allocated a TVirtualNode record, and immediately after that, in consecutive memory, it has allocated NodeDataSize bytes for you to use, and GetNodeData returns the address of that space. You're supposed to treat that as a pointer to a TTest structure. And you do, for some of your code. It looks like you're trying to skirt the limitation that only the first four bytes of the structure get initialized when you call AddChild. (I can't say I recommend that. There are other ways to associate data with a node that don't require so much type punning.)
You assign Data correctly for the way the node data is supposed to be used. You assign NodeData correctly for what it really holds at the time of initialization — a pointer to a pointer to a TTest structure. You correctly dereference NodeData to read the Number field, and you also read the Text field correctly. However, the Data.Text field can't be overwritten the way you have it:
Data.Text := PTest(NodeData^)^.Text;
The Data.Text field doesn't current hold a valid string value, but string variables are required to hold valid values at all times (or at least all times where there's a possibility they'll be read or written). To assign a string variable, the program increments the reference count of the new value and decrements the reference count of the old one, but since the "old value" in this case isn't really a string, there's no valid reference count to decrement, and even if there were, the memory at that location couldn't be freed anyway — it belongs to TestArray.
There's a way around this, though. Copy the string in two steps. First, read the value from NodeData.Text into a spare string variable. Once you do that, you have no need for NodeData anymore, so you can overwrite the value it points to. If you set it to all-bits-zero, then you'll implicitly overwrite Data.Text as well, and with the value of an empty string. At that point, it's safe to overwrite as a string variable:
tmp := PTest(NodeData^)^.Text;
PTest(NodeData^) := nil;
Data.Text := tmp;
Another way around this is to re-arrange the order of the fields in the node data. Put the Integer field first, and the initialize Data.Number last instead of Data.Text. Integer values are always safe to overwrite, no matter their contents.
Whatever you do, make sure you finalize the record in the OnFreeNode event:
var
Data: PTest;
begin
Data := Sender.GetNodeData;
Finalize(Data^);
end;
That makes sure the string field gets its reference count reduced, if necessary.
You're missing the point here. You have already inited your node on button's click event, so there is no need to use OnInitNode to init it additionally. What you need is probably use OnGetText to display your data. E.g.:
procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Data: PTest;
begin
Data := PTest(Sender.GetNodeData(Node)^);
CellText := Data.Text;
end;
This code
procedure MyThreadTestA(const AStr: string);
Is faster than
procedure MyThreadTestB(AStr: string);
Whilst doing the same work, both pass a pointer.
However version B 'correctly' updates the referencecount of AStr and makes a copy if I change it.
Version A passes just a pointer and only the compiler prevents me from changing AStr.
Version A is not safe if I do dirty tricks in Assembler or otherwise to circumvent the compiler protection, this is well known but...
Is passed AStr by reference as a const parameters thread safe?
What happens if AStr's reference count in some other thread goes to zero and the string is destroyed?
No, such tricks are not thread-safe. Const prevents the add-ref, so changes by another thread will affect the value in unpredictable ways. Sample program, try altering the const in the definition of P:
{$apptype console}
uses SysUtils, Classes, SyncObjs;
type
TObj = class
public
S: string;
end;
TWorker = class(TThread)
public
procedure Execute; override;
end;
var
lock: TCriticalSection;
obj: TObj;
procedure P(const x: string);
// procedure P(x: string);
begin
Writeln('P(1): x = ', x);
Writeln('Releasing obj');
lock.Release;
Sleep(10); // give worker a chance to run
Writeln('P(2): x = ', x);
end;
procedure TWorker.Execute;
begin
// wait until TMonitor is freed up
Writeln('Worker started...');
lock.Acquire;
Writeln('worker fiddling with obj.S');
obj.S := 'bar';
TMonitor.Exit(obj);
end;
procedure Go;
begin
lock := TCriticalSection.Create;
obj := TObj.Create;
obj.S := 'foo';
UniqueString(obj.S);
lock.Acquire;
TWorker.Create(False);
Sleep(10); // give worker a chance to run and block
P(obj.S);
end;
begin
Go;
end.
But it's not just limited to threads; modifying the underlying variable location has similar effects:
{$apptype console}
uses SysUtils, Classes, SyncObjs;
type
TObj = class
public
S: string;
end;
var
obj: TObj;
procedure P(const x: string);
begin
Writeln('P(1): x = ', x);
obj.S := 'bar';
Writeln('P(2): x = ', x);
end;
procedure Go;
begin
obj := TObj.Create;
obj.S := 'foo';
UniqueString(obj.S);
P(obj.S);
end;
begin
Go;
end.
To add to Barry's answer: It is definitely thread-safe if the string that got passed came from a local variable inside the callers scope.
In that case that local variable will hold a valid reference and the only way (assuming just valid pascal code, no fiddling around in asm) for that local variable to be changed is if your call returns.
This also includes all cases where the source of the string variable is the result of a function call (including property access, e.g. TStrings.Strings[]) because in this case the compiler has to store the string in a local temp variable.
Thread-safety problems can only result if you are directly passing a string from a location where that string can be changed (by the same or another thread) before your call returns.