I am passing TProcessItem structure as a return value from my function. While in GetProcessFromHandle function, the ExeFile property of the structure returned by FindByID, contains the expected executable name, but in the structure returned by GetProcessFromHandle it somehow becomes empty. I added two Messageboxes to demonstrate it. Is anyone able to explain what is happening here?
function GetProcessFromHandle(hWnd: HWND): TProcessItem;
var
ProcessInfo: TProcessInfo;
PID: Cardinal;
begin
Result := nil;
GetWindowThreadProcessId(hWnd, #PID);
ProcessInfo := TProcessInfo.Create(nil);
try
Result := ProcessInfo.RunningProcesses.FindByID(PID);
if Assigned(Result) then ShowMessage(Result.ExeFile); //first message
finally
ProcessInfo.Free;
end;
end;
procedure Test;
var
Process: TProcessItem;
begin
Process := GetProcessFromHandle(FindWindow(nil, 'My App'));
if Assigned(Process) ShowMessage(Process.ExeFile); //second message
end;
The TProcessInfo object owns the TProcessItem object that GetProcessFromHandle() is returning a pointer to. As such, when the TProcessInfo object is freed, so is the TProcessItem object, and thus the pointer being used by Test() is invalid. So you run into undefined behavior. You are lucky your code did not just crash instead.
If you want to return the path to the EXE file, return only that value, eg:
function GetProcessExeFileFromHandle(hWnd: HWND): string;
var
ProcessInfo: TProcessInfo;
ProcessItem: TProcessItem;
PID: DWORD;
begin
Result := '';
GetWindowThreadProcessId(hWnd, #PID);
ProcessInfo := TProcessInfo.Create(nil);
try
ProcessItem := ProcessInfo.RunningProcesses.FindByID(PID);
if Assigned(ProcessItem) then
Result := ProcessItem.ExeFile;
finally
ProcessInfo.Free;
end;
end;
procedure Test;
var
ProcessExeFile: string;
begin
ProcessExeFile := GetProcessExeFileFromHandle(FindWindow(nil, 'My App'));
ShowMessage(ProcessExeFile);
end;
Related
I need to modify function parameter variable (string) in my Pascal Script code and get it in the Delphi function, after the script finish it's work.
My script code:
function OnBroadcastMessage(iCID, iUIN: integer; var sUsersList: string; dtActualTo: double; bMustRead, bReadNotify: boolean; sMsg: string): boolean;
begin
sUsersList := '3';
result := true;
end;
begin
end.
My Delphi XE3 code (only tiny example, without any checks):
var
Compiler: TPSPascalCompiler;
Exec: TPSExec;
ProcNo: cardinal;
ParamList: TIfList;
Data: AnsiString;
begin
Compiler := TPSPascalCompiler.Create;
Compiler.Compile(Script)
Compiler.GetOutput(Data);
Compiler.Free;
Exec.LoadData(Data);
ProcNo := Exec.GetProc('OnBroadcastMessage');
ParamList := TIfList.Create;
ParamList.Add(#iCID);
ParamList.Add(#iUIN);
ParamList.Add(#sUsersList);
ParamList.Add(#dtActualTo);
ParamList.Add(#bMustRead);
ParamList.Add(#bReadNotify);
ParamList.Add(#sMsg);
result := Exec.RunProc(ParamList, ProcNo);
FreePIFVariantList(ParamList);
end;
This solution was wrong, I'm got an error at line "result := Exec.RunProc(ParamList, ProcNo);".
"Project mcserv.exe raised exception class $C0000005 with message 'access violation at 0x00a56823: read of address 0x0000000d'.".
How I do wrong?
You need to create PPSVariant for string parameters :
Param := CreateHeapVariant(fExec.FindType2(btString));
PPSVariantAString(Param).Data := AnsiString('test value');
Another way is to work with Exec.RunProcPVar() method.
You just have to define an array of variant with your parameters :
var
vparams : array of Variant;
begin
Compiler := TPSPascalCompiler.Create;
Compiler.Compile(Script);
Compiler.GetOutput(Data);
Compiler.Free;
Exec.LoadData(Data);
ProcNo := Exec.GetProc('OnBroadcastMessage');
SetLength(vparams, 7);
vparams[0] := iCID;
vparams[1] := iUIN;
vparams[2] := sUsersList;
vparams[3] := dtActualTo;
vparams[4] := bMustRead;
vparams[5] := bReadNotify;
vparams[6] := sMsg;
Result := Exec.RunProcPVar(vparams, procno);
end;
I'm implementing the Exec method of TWebBrowser based on this answer. This method is triggered whenever a script error occurs. Now I need to get error information.
I first get hold of the event object of the TWebBrowser.
var
Doc: IHTMLDocument2;
HTMLWindow: IHTMLWindow2;
Event: IHTMLEventObj;
MethodName: String;
MethodResult: OleVariant;
DispatchId: Integer;
Param: array of OleVariant;
begin
//Avoid non-error calls
if nCmdID != OLECMDID_SHOWSCRIPTERROR then
Exit;
//Get hold of the event object
Doc := MapForm.WebBrowser.Document as IHTMLDocument2;
if not Assigned(Doc) then
Exit;
HTMLWindow := Doc.parentWindow;
if not Assigned(HTMLWindow) then
Exit;
//Get the current event
Event := HTMLWindow.event;
And then I'm trying to get the information I need (as demonstrated in this link) using GetIDsOfNames and Invoke functions of the interface. A working Delphi code for using these methods are in this documentation link.
Here is how I use these functions on the Event object.
MethodName := 'errorMessage';
Result := Event.GetIDsOfNames(GUID_NULL, #MethodName, 1, SysLocale.DefaultLCID, #DispatchId);
Result := Event.Invoke(DispatchId, GUID_NULL, SysLocale.DefaultLCID, DISPATCH_METHOD, Param, #MethodResult, nil, nil);
The GetIDsOfNames fuGetIDsOfNames function executes properly, outputs an acceptable integer to DispatchId and returns S_OK.
But the Invoke function just fails. It returns some negative integer as HRESULT and doesn't output anything to MethodResult.
How can I work around this?
The error values you are trying to access are not object methods, they are properties, so Invoke() is going to fail due to your use of DISPATCH_METHOD. Use DISPATCH_PROPERTYGET instead.
However, OleVariant (and Variant) has built-in support for IDispatch.Invoke(), so you don't need to mess with it manually at all. You can call object methods and read/write object properties normally, and the compiler will produce the necessary IDispatch calls for you.
Try something more like this:
function TWebBrowser.Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD;
const vaIn: OleVariant; var vaOut: OleVariant): HRESULT; stdcall;
var
Event: OleVariant;
ErrorLine: Integer;
ErrorChar: Char;
ErrorCode: Integer;
ErrorMsg: String;
ErrorUrl: String;
begin
if (CmdGroup = nil) or not IsEqualGUID(CmdGroup^, CGID_DocHostCommandHandler) then
begin
Result := OLECMDERR_E_UNKNOWNGROUP;
Exit;
end;
if nCmdID <> OLECMDID_SHOWSCRIPTERROR then
begin
Result := OLECMDERR_E_NOTSUPPORTED;
Exit;
end;
Event := (IUnknown(vaIn) as IHTMLDocument2).parentWindow.event;
ErrorLine := Event.errorLine;
ErrorChar := Event.errorCharacter;
ErrorCode := Event.errorCode;
ErrorMsg := Event.errorMessage;
ErrorUrl := Event.errorUrl;
...
if (should continue running scripts) then
begin
vaOut := True;
end else
begin
vaOut := False;
end;
Result := S_OK;
end;
I need to check given value is already exists in the memory table, so i have tried following method.
function TForm4.findValueExists(id: Integer): Boolean;
var
state: Boolean;
begin
state := DBGrid1.DataSource.DataSet.Locate('code', id, []);
end;
here is the Search button procedure
procedure TForm4.butSearchClick(Sender: TObject);
var
id: Integer;
Name: String;
Sell: Double;
Qty: Integer;
Amount: Double;
OldQty: Integer;
RecordExist: Boolean;
begin
if txtprocode.Text <> '' then
begin
FDQuery1.Params.ParamByName('ID').Value := txtprocode.Text;
if FDQuery1.Active then
FDQuery1.Close;
FDQuery1.Open();
try
FDQuery1.First;
if not FDQuery1.Eof then
begin
id := FDQuery1.FieldByName('Id').AsInteger;
Name := FDQuery1.FieldByName('name').AsString;
Sell := FDQuery1.FieldByName('selling').AsFloat;
Qty := 1;
Amount := Sell * Qty;
if Form4.findValueExists(id) then
begin
FDMemTable1.Edit;
OldQty := DBGrid1.Fields[3].Value;
FDMemTable1.FieldByName('Qty').AsInteger := (OldQty + 1);
FDMemTable1.FieldByName('amount').AsFloat := (Sell * (OldQty + 1));
FDMemTable1.Post;
end
else
begin
FDMemTable1.InsertRecord([id, Name, Sell, Qty, Amount]);
end;
end;
finally
end;
end;
end;
unfortunately this method always gives me result as 'false'. but physically i can found matched result for given id.
here is UI
Guys i`m new to Delphi language.
You're not returning a value from your FindValueExists function, so you have absolutely no way of knowing if it locates the record or not. If you'd turn on compiler hints and warnings, the compiler would have pointed that fact out to you. (It would have informed you function findValueExists might not return a value, and also that Value assigned to 'state' is never used.)
Change your findValueExists so that it actually returns the result of the Locate call.
function TForm4.findValueExists(id: Integer): Boolean;
begin
Result := DBGrid1.DataSource.DataSet.Locate('code', id, []);
end;
The code below is from the JSonMarshall project in chapter 7 of Marco Cantu's Delphi 2010 Handbook. The source code is available from here http://cc.embarcadero.com/item/27600. I have made two changes to it:
Add JSon to the implementation Uses clause to get it to compile.
Added the line
theName := 'XXX'; // added by me
to the TDataWithList.Create constructor to assist debugging
I am running the code in Delphi Seattle (without update 1)
The purpose of the project is to demo a custom converter and reverter for the TDataWithList declared type. The custom converter seems to work fine, judging by the result output to Memo1.
However, attempting to run the reverter results in a "Read of address 00000000" AV on the line
sList.Add (Args[I]);
in btnUnmarshalReverterClick. The immediate cause of this is that contrary to what
the author evidently intended, when the above line executes, sList is Nil.
My question is simply why is sList Nil and how to fix this problem?
I have tried, not entirely successfully, to trace through the DBXJSONReflect source
to find out why.
After
Obj := ObjectInstance(FRTTICtx, objType);
in function TJSONUnMarshal.CreateObject, TDataWithList(obj).theName is 'XXX'
as I'd expect and TDataWithList(obj).theLList is an initialized, but empty,
TStringList.
However, by the time the anonymous method in btnUnmarshalReverterClick is called, TDataWithList(Data).theList is Nil.
Update: The reason that TDataWithList(Data).theList (incorrectly, imo) becomes Nil is that it is set to Nil in TJSONPopulationCustomizer.PrePopulate by a call to PrePopulateObjField. So I suppose the question is, why does PrePopulate allow an object's field which has been initialized in its constructor to be overwritten as if it knows better that the object's constructor.
Update2:
There may be an additional problem, in that as far as I can tell, in
TInternalJSONPopulationCustomizer.PrePopulateObjField, the assignment which overwrites TListWithData.theList with Nil, namely
rttiField.SetValue(Data, TValue.Empty);
does not seem to result in the TStringlist destructor being called.
Btw, I get the same error running the project in XE4, which is the earliest version I have which includes JSonUnMarshal.
Code:
type
[...]
TDataWithList = class
private
theName: String;
theList: TStringList;
public
constructor Create (const aName: string); overload;
constructor Create; overload;
function ToString: string; override;
destructor Destroy; override;
end;
[...]
procedure TFormJson.btnMarshalConverterClick(Sender: TObject);
var
theData: TDataWithList;
jMarshal: TJSONMarshal;
jValue: TJSONValue;
begin
theData := TDataWithList.Create('john');
try
jMarshal := TJSONMarshal.Create(
TJSONConverter.Create); // converter is owned
try
jMarshal.RegisterConverter(TDataWithList, 'theList',
function (Data: TObject; Field: string): TListOfStrings
var
I: Integer;
sList: TStringList;
begin
sList := TDataWithList(Data).theList;
SetLength(Result, sList.Count);
for I := 0 to sList.Count - 1 do
Result[I] := sList[I];
end);
jValue := jMarshal.Marshal(theData);
try
Memo1.Lines.Text := jValue.ToString;
finally
jValue.Free;
end;
finally
jMarshal.Free;
end;
finally
theData.Free;
end;
end;
procedure TFormJson.btnUnmarshalReverterClick(Sender: TObject);
var
jUnmarshal: TJSONUnMarshal;
jValue: TJSONValue;
anObject: TObject;
begin
jValue := TJSONObject.ParseJSONValue(
TEncoding.ASCII.GetBytes (Memo1.Lines.Text), 0);
try
jUnmarshal := TJSONUnMarshal.Create;
try
jUnmarshal.RegisterReverter(TDataWithList, 'theList',
procedure (Data: TObject; Field: string; Args: TListOfStrings)
var
I: Integer;
sList: TStringList;
begin
sList := TDataWithList(Data).theList;
for I := 0 to Length(Args) - 1 do
sList.Add (Args[I]);
end);
anObject := jUnmarshal.Unmarshal(jValue);
try
ShowMessage ('Class: ' + anObject.ClassName +
sLineBreak + anObject.ToString);
finally
anObject.Free;
end;
finally
jUnmarshal.Free;
end;
finally
jValue.Free;
end;
end;
function TMyData.ToString: string;
begin
Result := theName + ':' + IntToStr (theValue);
end;
{ TDataWithList }
constructor TDataWithList.Create(const aName: string);
var
I: Integer;
begin
theName := aName;
theList := TStringList.Create;
for I := 0 to 9 do
theList.Add(IntToStr (Random (1000)));
end;
constructor TDataWithList.Create;
begin
// core initialization, used for default construction
theName := 'XXX'; // added by me
theList := TStringList.Create;
end;
destructor TDataWithList.Destroy;
begin
theList.Free;
inherited;
end;
function TDataWithList.ToString: string;
begin
Result := theName + sLineBreak + theList.Text;
end;
rttiField.SetValue(Data, TValue.Empty); simply overrides the field value because as the name implies it's a field, not a property with get / set methods. The destructor of TStringList is not called due to simple pointer assignment.
The solution here is to declare a property:
TDataWithList = class
...
strict private
theList: TStringList;
...
public
property Data: TStringList read ... write SetData
...
end;
TDataWithList.SetData(TStringList aValue);
begin
theList.Assign(aValue);
end;
We can use the SuperObject library to invoke methods of a certain object by its name and giving its parameters as a json string using the SOInvoker method like in this answer
I'd like to know how do I send a created object as a parameter. I tried to send it like
LObjectList := TObjectList.Create;
LSuperRttiCtx := TSuperRttiContext.Create;
LSuperObjectParameter := LObjectList.ToJson(LSuperRttiCtx);
SOInvoke(MyInstantiatedObject, 'MyMethod', LSuperObjectParameter);
but inside MyMethod the LObjectList reference is lost.
What am I doing wrong?
The superobject library can be downloaded here
It will works if you use array of records intead of object list.
If you still want to use object list you will have to write encoders and decoders like this. I have written encoder/decoder for TObjectList, you will have to do the same for your objects and embed the class name somewhere.
ctx.SerialToJson.Add(TypeInfo(TObjectList), ObjectListToJSON);
ctx.SerialFromJson.Add(TypeInfo(TObjectList), JSONToObjectList);
function ObjectListToJSON(ctx: TSuperRttiContext; var value: TValue;
const index: ISuperObject): ISuperObject;
var
list: TObjectList;
i: Integer;
begin
list := TObjectList(value.AsObject);
if list <> nil then
begin
Result := TSuperObject.Create(stArray);
for i := 0 to list.Count - 1 do
Result.AsArray.Add(encodeyourobject(list[i]));
end else
Result := nil;
end;
function JSONToObjectList(ctx: TSuperRttiContext; const obj: ISuperObject; var Value: TValue): Boolean;
var
list: TObjectList;
i: Integer;
begin
list := nil;
case ObjectGetType(obj) of
stNull:
begin
Value := nil;
Result := True;
end;
stArray:
begin
list := TObjectList.Create;
for i := 0 to obj.AsArray.Length - 1 do
list.Add(decodeyourobject(obj.AsArray[i]));
Value := list;
Result := True;
end;
else
result := False;
end;
end;