While using the SuperObject library, a single JSON object currently gets indented like so:
{
"name": "value",
"int_arr": [
1,2,3],
"obj_arr": [
{
"this": "that"
},{
"some": "thing"
}],
"another": 123
}
However, this indentation/formatting is not the same "user-friendly" or "human-readable" as JSON is all hyped up to be. I understand in computer language, this doesn't necessarily matter, but I'd like to format it like so instead:
{
"name": "value",
"int_arr": [1,2,3],
"obj_arr": [
{
"this": "that"
},
{
"some": "thing"
}
],
"another": 123
}
For example, when using JSONLint to validate/format JSON code, it does so in a much cleaner manner.
How can I go about modifying the SuperObject library to format it differently? Is there a particular place in the library which defines these formatting rules? Or would I have to dig into the code in many different places to change this?
Thanks to the comment by David A, it was rather simple to implement these changes (after formatting the source and understanding how the library's code works). All the formatting is implemented in TSuperObject.Write, and all of such changes can be made here.
There was only one issue I could not figure out, which was arrays of types other than objects - the values will go to the next line. But at least array endings and arrays of objects have line breaks and indentation as desired.
Here's the modified version of TSuperObject.Write below (most subroutines not included to save space). Changes commented:
Constant:
const
TOK_SP: PSOChar = #32#32; //<-- added another #32
Subroutine:
procedure _indent(I: shortint; r: boolean);
begin
Inc(level, I);
if r then
with writer do
begin
{$IFDEF MSWINDOWS}
Append(TOK_CRLF, 2);
{$ELSE}
Append(TOK_LF, 1);
{$ENDIF}
for I := 0 to level - 1 do
Append(TOK_SP, 2); //<-- changed 1 to 2
end;
end;
Procedure body:
begin
if FProcessing then
begin
Result := writer.Append(TOK_NULL, 4);
Exit;
end;
FProcessing := true;
with writer do
try
case FDataType of
stObject:
if FO.c_object.FCount > 0 then
begin
k := 0;
Append(TOK_CBL, 1);
if indent then
_indent(1, false);
if ObjectFindFirst(Self, iter) then
repeat
{$IFDEF SUPER_METHOD}
if (iter.val = nil) or not ObjectIsType(iter.val, stMethod) then
begin
{$ENDIF}
if (iter.val = nil) or (not iter.val.Processing) then
begin
if (k <> 0) then
Append(TOK_COM, 1);
if indent then
_indent(0, true);
Append(TOK_DQT, 1);
if escape then
DoEscape(PSOChar(iter.key), Length(iter.key))
else
DoMinimalEscape(PSOChar(iter.key), Length(iter.key));
if indent then
Append(ENDSTR_A, 3)
else
Append(ENDSTR_B, 2);
if (iter.val = nil) then
Append(TOK_NULL, 4)
else
iter.val.Write(writer, indent, escape, level);
Inc(k);
end;
{$IFDEF SUPER_METHOD}
end;
{$ENDIF}
until not ObjectFindNext(iter);
ObjectFindClose(iter);
if indent then
_indent(-1, true);
Result := Append(TOK_CBR, 1);
end
else
Result := Append(TOK_OBJ, 2);
stBoolean:
begin
if (FO.c_boolean) then
Result := Append(TOK_TRUE, 4)
else
Result := Append(TOK_FALSE, 5);
end;
stInt:
begin
str(FO.c_int, st);
Result := Append(PSOChar(SOString(st)));
end;
stDouble:
Result := Append(PSOChar(SOString(gcvt(FO.c_double, 15, fbuffer))));
stCurrency:
begin
Result := Append(PSOChar(CurrToStr(FO.c_currency)));
end;
stString:
begin
Append(TOK_DQT, 1);
if escape then
DoEscape(PSOChar(FOString), Length(FOString))
else
DoMinimalEscape(PSOChar(FOString), Length(FOString));
Append(TOK_DQT, 1);
Result := 0;
end;
stArray:
if FO.c_array.FLength > 0 then
begin
Append(TOK_ARL, 1);
if indent then
_indent(1, true);
k := 0;
j := 0;
while k < FO.c_array.FLength do
begin
val := FO.c_array.GetO(k);
{$IFDEF SUPER_METHOD}
if not ObjectIsType(val, stMethod) then
begin
{$ENDIF}
if (val = nil) or (not val.Processing) then
begin
if (j <> 0) then begin
Append(TOK_COM, 1);
if ObjectIsType(val, stObject) then begin //
if indent then //<-- create line break after object array items
_indent(0, true); //
end; //
end;
if (val = nil) then
Append(TOK_NULL, 4)
else
val.Write(writer, indent, escape, level);
Inc(j);
end;
{$IFDEF SUPER_METHOD}
end;
{$ENDIF}
Inc(k);
end;
if indent then
_indent(-1, true); //<-- changed "false" to "true" to create line break at end of array
Result := Append(TOK_ARR, 1);
end
else
Result := Append(TOK_ARRAY, 2);
stNull:
Result := Append(TOK_NULL, 4);
else
Result := 0;
end;
finally
FProcessing := false;
end;
end;
That code would produce JSON data like so:
{
"name": "value",
"int_arr": [
1,2,3
],
"obj_arr": [
{
"this": "that"
},
{
"some": "thing"
}
],
"another": 123
}
Related
I'm working to create an installer and I need to edit and retrieve values from the JSON file.
To retrieve and edit the values from the Section_2 works fine. The problem is to edit and retrieve values from the children sections of Section_1. Bellow we can see an example:
{
"Section_1": {
"children_1": {
"children_1_1": "value_1",
"children_1_2": "value_2"
},
"children_2": "blablabla"
},
"Section_2": {
"children_2_1": "value_1",
"children_2_2": "value_2"
}
}
[Files]
Source: "{#ProjectUrl}\JSONConfig.dll"; Flags: dontcopy
[Code]
var
FileName: WideString;
StrValue: WideString;
StrLength: Integer;
function JSONQueryString(FileName, Section, Key, Default: WideString;
var Value: WideString; var ValueLength: Integer): Boolean;
external 'JSONQueryString#files:jsonconfig.dll stdcall';
function JSONWriteString(FileName, Section, Key,
Value: WideString): Boolean;
external 'JSONWriteString#files:jsonconfig.dll stdcall';
function editAppSettingsJson(Section_1: String; Section_2:String): Boolean;
begin
FileName := '{#AppSettingsJsonFile}';
SetLength(StrValue, 16);
StrLength := Length(StrValue);
Result := True;
{ Does not work. How can I edit it? }
if not JSONWriteString(FileName, 'children_1', 'children_1_1',
Section_1) then
begin
MsgBox('JSONWriteString Section_1:children_1:children_1_1 failed!',
mbError, MB_OK);
Result := False;
end;
{ Works fine. }
if not JSONWriteString(FileName, 'Section_2', 'children_2_1', Section_2)
then
begin
MsgBox('JSONWriteString Section_2:children_2_1 failed!', mbError,
MB_OK);
Result := False;
end;
end;
procedure InitializeWizard;
var
value_1: String;
value_2: String;
begin
value_1:= 'value_2';
value_2:= 'value_3';
editAppSettingsJson(value_1, value_2);
end;
In advance thank you very much for your support.
Regards, Diego Via
I do not think that JSONConfig.dll supports nested structures.
You can use JsonParser library instead. It can parse nested structures. Though it's not as easy to use as JSONConfig.dll – well, because it's more versatile.
The following code will do:
var
JsonLines: TStringList;
JsonParser: TJsonParser;
JsonRoot, Section1Object, Children1Object: TJsonObject;
Child11Value: TJsonValue;
begin
JsonLines := TStringList.Create;
JsonLines.LoadFromFile(FileName);
if ParseJsonAndLogErrors(JsonParser, JsonLines.Text) then
begin
JsonRoot := GetJsonRoot(JsonParser.Output);
if FindJsonObject(JsonParser.Output, JsonRoot, 'Section_1', Section1Object) and
FindJsonObject(JsonParser.Output, Section1Object, 'children_1', Children1Object) and
FindJsonValue(JsonParser.Output, Children1Object, 'children_1_1', Child11Value) and
(Child11Value.Kind = JVKString) then
begin
Log(Format('children_1_1 previous value %s', [
JsonParser.Output.Strings[Child11Value.Index]]));
JsonParser.Output.Strings[Child11Value.Index] := 'new value';
JsonLines.Clear;
PrintJsonParserOutput(JsonParser.Output, JsonLines);
JsonLines.SaveToFile(FileName);
end;
end;
end;
The code uses functions from my answer to How to parse a JSON string in Inno Setup?
So I'm currently working on a school project with a bunch of binary, 2s complement and other base conversions. Part of what i'm working on is a binary flipper; 1 becomes 0 and 0 becomes 1. I've written it as a function taking in and returning a string.
function flipbit(inp:string) : string;
var
new : string;
x:integer;
begin
writeln('new: ',new);
writeln('inp: ',inp);
new := '';
writeln('new assigned');
for x:= 1 to length(inp) do;
begin
writeln('loop started');
if strtoint(inp[x]) = 1 then
begin
new := new + '0';
writeln('0 added');
end;
if strtoint(inp[x]) = 0 then
begin
new := new + '1';
writeln('1 added');
end
else
begin
writeln('Something went wrong');
end;
end;
result := new;
end;
Basically the code iterates through a string of 1s and 0s, and re-writes them in another string but flips the bits.
I've used strtoint of inp[x] (inp is the input variable) and compared it to 1 (As an integer not a string)
I originally just tried comparing inp[x] without integer conversion to '1'. The code compiled, but 'new' did not change at all.
When I tried the method I'm using right now, the code crashes.
I've used debug writeln statements to try and narrow down where exactly the code is going wrong and it seems to be the if statement.
If anyone could help, that would be much appreciated. Thank you.
Why don't you receive as an array of integers and operate a not operation in each position? It has to be a string?
You don't need to convert chars to integers. You don't need to declare a local variable since you already have the 'result' variable.
function flipbit(inp: string): string;
var
i: integer;
begin
result := inp;
for i := 1 to Length(result) do
if result[i] = '0' then
result[i] := '1'
else
if result[i] = '1' then
result[i] := '0';
end;
Another version:
function flipbit(inp: string): string;
const
OTHER_VALUE: array['0'..'1'] of char = ('1', '0');
var
i: integer;
begin
result := inp;
for i := 1 to Length(result) do
if result[i] in ['0', '1'] then
result[i] := OTHER_VALUE[result[i]];
end;
I have a "parent" class which has a generic function to load a JSON string into the instance's properties called loadVals. I have two children with their own properties, and one of these props is a record.
The function sets successfully all the props of the main instance, but fails on setting the values on the record's props, no errors, I can see it loops successfully through the record's props but doesn't set the values.
wrote a small test console app, where you can see the behavior.
uses
System.SysUtils, System.TypInfo, RTTI, Data.DBXJSON;
type
TFieldValLoader = reference to procedure (const new_val: TValue);
tRec1 = record
x: integer;
y: String;
end;
tRec2 = record
a: integer;
b: String;
c: integer;
end;
TMyParent = class(TObject)
procedure loadVals(json_obj: TJSONObject);
end;
TMyChild1 = class(TMyParent)
h: integer;
my_rec: tRec1;
end;
TMyChild2 = class(TMyParent)
j: string;
my_rec: tRec2;
end;
{ TMyParent }
procedure TMyParent.loadVals(json_obj: TJSONObject);
procedure loadObj(Obj : TObject; my_json_obj: TJSONObject); forward;
procedure loadRecord(Obj : TValue; my_json_obj: TJSONObject);forward;
Procedure loadField( my_json_val: TJSONPair; _val: TValue; _loader: TFieldValLoader );
Begin
case _val.TypeInfo.Kind of
tkInteger:
_loader( TValue.From<integer>(StrToInt(my_json_val.JsonValue.Value)));
tkWChar, tkUString, tkVariant:
_loader( TValue.From(my_json_val.JsonValue.Value));
tkRecord:
loadRecord(_val, my_json_val.JsonValue as TJSONObject);
end;
End;
procedure loadRecord(obj : TValue; my_json_obj: TJSONObject);
var
i: Integer;
json_pair: TJSONPair;
ctx: TRttiContext;
obj_type: TRttiType;
my_field: TRttiField;
begin
ctx := TRttiContext.Create;
obj_type := ctx.GetType(obj.TypeInfo);
for I := 0 to my_json_obj.Size - 1 do
Begin
json_pair := my_json_obj.get(i);
my_field := obj_type.GetField(json_pair.JsonString.value);
WriteLn(' - '+ my_field.Name);
loadField(json_pair, my_field.GetValue(obj.GetReferenceToRawData),
procedure( const new_val: TValue )
Begin
// This does not work. (no feedback)!!!!
my_field.SetValue(obj.GetReferenceToRawData, new_val);
End
);
End;
End;
procedure loadObj(Obj : TObject; my_json_obj: TJSONObject);
var
i: Integer;
json_pair: TJSONPair;
ctx: TRttiContext;
obj_type: TRttiType;
my_field: TRttiField;
begin
ctx := TRttiContext.Create;
obj_type := ctx.GetType(obj.ClassInfo);
for I := 0 to my_json_obj.Size - 1 do
Begin
json_pair := my_json_obj.get(i);
my_field := obj_type.GetField(json_pair.JsonString.value);
WriteLn('* '+ my_field.Name);
loadField(json_pair, my_field.GetValue(obj),
procedure( const new_val: TValue )
Begin
// This does work
my_field.SetValue(obj, new_val);
End
);
End;
End;
begin
WriteLn('Loading '+ self.ClassName);
loadObj(self, json_obj);
end;
{ main Test Procedure }
var
my_child1: TMyChild1;
my_child2: TMyChild2;
begin
try
my_child1:= TMyChild1.Create;
my_child2:= TMyChild2.Create;
try
// load the json objs
my_child1.loadVals(TJSONObject.ParseJSONValue('{"h": 2, "my_rec": {"x": 4, "y": "test"}}') as TJSONObject);
my_child2.loadVals(TJSONObject.ParseJSONValue('{"j": "some", "my_rec": {"a": 8, "b": "any", "c": 9}}') as TJSONObject);
// print the loaded values
WriteLn('child 1 vals are: h: '+ intToStr(my_child1.h) +' my_rec.y= "'+ my_child1.my_rec.y +'" should equal to "test"');
WriteLn('child 2 vals are: j: '+ my_child2.j +' my_rec.b= "'+ my_child2.my_rec.b +'" should equal to "any"');
finally
my_child1.Free;
my_child2.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
// don't close the window, wait for [Enter]
Readln;
end.
I know records are different than classes, and I cannot find a way of getting this function to work; I really appreciate any help. Thanks
Your problem is that a record is a value type.
This line
loadField(json_pair, my_field.GetValue(obj),
gets the value of the record field. Keep in mind, its a value type so we get a copy of it.
Now you are setting the properties/fields on that copy which works. But then you never assign that back to the fields of your object.
So what you are doing here is basically like this:
my_child1:= TMyChild1.Create;
my_rec1 := my_child1.my_rec;
my_rec1.x := 4;
my_rec1.y := 'test';
So you can see that my_child1.my_rec never gets the values set to my_rec1.
You need to fix loadField as follows:
procedure loadField( my_json_val: TJSONPair; _val: TValue; _loader: TFieldValLoader );
begin
case _val.TypeInfo.Kind of
tkInteger:
_loader( TValue.From<integer>(StrToInt(my_json_val.JsonValue.Value)));
tkWChar, tkUString, tkVariant:
_loader( TValue.From(my_json_val.JsonValue.Value));
tkRecord:
begin
loadRecord(_val, my_json_val.JsonValue as TJSONObject);
_loader( _val); // <- set the record back to the field
end;
end;
end;
How can I parse Name: & Value text from within the tag with DIHtmlParser? I tried doing it with TCLHtmlParser from Clever Components but it failed. Second question is can DIHtmlParser parse individual tags for example loop through its sub tags. Its a total nightmare for such a simple problem.
<div class="tvRow tvFirst hasLabel tvFirst" title="example1">
<label class="tvLabel">Name:</label>
<span class="tvValue">Value</span>
<div class="clear"></div></div>
<div class="tvRow tvFirst hasLabel tvFirst" title="example2">
<label class="tvLabel">Name:</label>
<span class="tvValue">Value</span>
<div class="clear"></div></div>
You could use IHTMLDocument2 DOM to parse whatever elements you need from the HTML:
uses ActiveX, MSHTML;
const
HTML =
'<div class="tvRow tvFirst hasLabel tvFirst" title="example1">' +
'<label class="tvLabel">Name:</label>' +
'<span class="tvValue">Value</span>' +
'<div class="clear"></div>' +
'</div>';
procedure TForm1.Button1Click(Sender: TObject);
var
doc: OleVariant;
el: OleVariant;
i: Integer;
begin
doc := coHTMLDocument.Create as IHTMLDocument2;
doc.write(HTML);
doc.close;
ShowMessage(doc.body.innerHTML);
for i := 0 to doc.body.all.length - 1 do
begin
el := doc.body.all.item(i);
if (el.tagName = 'LABEL') and (el.className = 'tvLabel') then
ShowMessage(el.innerText);
if (el.tagName = 'SPAN') and (el.className = 'tvValue') then
ShowMessage(el.innerText);
end;
end;
I wanted to mention another very nice HTML parser I found today: htmlp (Delphi Dom HTML Parser and Converter). It's not as flexible as the IHTMLDocument2 obviously, but it's very easy to work with, fast, free, and supports Unicode for older Delphi versions.
Sample usage:
uses HtmlParser, DomCore;
function GetDocBody(HtmlDoc: TDocument): TElement;
var
i: integer;
node: TNode;
begin
Result := nil;
for i := 0 to HtmlDoc.documentElement.childNodes.length - 1 do
begin
node := HtmlDoc.documentElement.childNodes.item(i);
if node.nodeName = 'body' then
begin
Result := node as TElement;
Break;
end;
end;
end;
procedure THTMLForm.Button2Click(Sender: TObject);
var
HtmlParser: THtmlParser;
HtmlDoc: TDocument;
i: Integer;
body, el: TElement;
node: TNode;
begin
HtmlParser := THtmlParser.Create;
try
HtmlDoc := HtmlParser.parseString(HTML);
try
body := GetDocBody(HtmlDoc);
if Assigned(body) then
for i := 0 to body.childNodes.length - 1 do
begin
node := body.childNodes.item(i);
if (node is TElement) then
begin
el := node as TElement;
if (el.tagName = 'div') and (el.GetAttribute('class') = 'tvRow tvFirst hasLabel tvFirst') then
begin
// iterate el.childNodes here...
ShowMessage(IntToStr(el.childNodes.length));
end;
end;
end;
finally
HtmlDoc.Free;
end;
finally
HtmlParser.Free
end;
end;
Use a HTML Parser to work on your html files.
Maybe DIHtmlParser will do the job.
RegEx is not a parser and converting from HTML to JSON is not a wise option.
One can also use a combination of HTMLP parser with THtmlFormatter and OXml XPath parsing
uses
// Htmlp
HtmlParser,
DomCore,
Formatter,
// OXml
OXmlPDOM,
OXmlUtils;
function HtmlToXHtml(const Html: string): string;
var
HtmlParser: THtmlParser;
HtmlDoc: TDocument;
Formatter: THtmlFormatter;
begin
HtmlParser := THtmlParser.Create;
try
HtmlDoc := HtmlParser.ParseString(Html);
try
Formatter := THtmlFormatter.Create;
try
Result := Formatter.GetText(HtmlDoc);
finally
Formatter.Free;
end;
finally
HtmlDoc.Free;
end;
finally
HtmlParser.Free;
end;
end;
type
TCard = record
Store: string;
Quality: string;
Quantity: string;
Price: string;
end;
TCards = array of TCard;
function ParseCard(const Node: PXMLNode): TCard;
const
StoreXPath = 'div[1]/ax';
QualityXPath = 'div[3]';
QuantityXPath = 'div[4]';
PriceXPath = 'div[5]';
var
CurrentNode: PXMLNode;
begin
Result := Default(TCard);
if Node.SelectNode(StoreXPath, CurrentNode) then
Result.Store := CurrentNode.Text;
if Node.SelectNode(QualityXPath, CurrentNode) then
Result.Quality := CurrentNode.Text;
if Node.SelectNode(QuantityXPath, CurrentNode) then
Result.Quantity := CurrentNode.Text;
if Node.SelectNode(PriceXPath, CurrentNode) then
Result.Price := CurrentNode.Text;
end;
procedure THTMLForm.OpenButtonClick(Sender: TObject);
var
Html: string;
Xml: string;
FXmlDocument: IXMLDocument;
QueryNode: PXMLNode;
XPath: string;
NodeList: IXMLNodeList;
i: Integer;
Card: TCard;
begin
Html := System.IOUtils.TFile.ReadAllText(FileNameEdit.Text, TEncoding.UTF8);
Xml := HtmlToXHtml(Html);
Memo.Lines.Text := Xml;
// Parse with XPath
FXMLDocument := CreateXMLDoc;
FXMLDocument.WriterSettings.IndentType := itIndent;
if not FXMLDocument.LoadFromXML(Xml) then
raise Exception.Create('Source document is not valid');
QueryNode := FXmlDocument.DocumentElement;
XPath := '//div[#class="row pricetableline"]';
NodeList := QueryNode.SelectNodes(XPath);
for i := 0 to NodeList.Count -1 do
begin
Card := ParseCard(NodeList[i]);
Memo.Lines.Text := Memo.Lines.Text + sLineBreak +
Format('%0:s %1:s %2:s %3:s', [Card.Store, Card.Quality, Card.Quantity, Card.Price]);
end;
Memo.SelStart := 0;
Memo.SelLength := 0;
end;
Can you advice me how to get "Log On As" parameter of specific windows service?
I need to re-register service in our upgrade project and it needs to be run under the same account as it was set up originally.
I've found QueryServiceConfig in advapi32.dll with lpServiceStartName in returned structure but I am not able to make it work from Inno Setup.
You cannot use QueryServiceConfig function from InnoSetup script. To use this function, you would have to allocate buffer from heap and that's impossible in InnoSetup. Instead you can use WMI, or to be more specific, the Win32_Service WMI class, which contains the StartName property, that you've asked for. In InnoSetup script it might look like this:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
function GetServiceStartName(const AServiceName: string): string;
var
WbemLocator: Variant;
WbemServices: Variant;
WbemObject: Variant;
WbemObjectSet: Variant;
begin;
Result := '';
WbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
WbemServices := WbemLocator.ConnectServer('localhost', 'root\CIMV2');
WbemObjectSet := WbemServices.ExecQuery('SELECT * FROM Win32_Service ' +
'WHERE Name = "' + AServiceName + '"');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
WbemObject := WbemObjectSet.Item('Win32_Service.Name="' +
AServiceName + '"');
if not VarIsNull(WbemObject) then
Result := WbemObject.StartName;
end;
end;
procedure SvcStartNameTestButtonClick(Sender: TObject);
begin
MsgBox(GetServiceStartName('Netlogon'), mbInformation, MB_OK);
end;
procedure InitializeWizard;
var
SvcStartNameTestButton: TNewButton;
begin
SvcStartNameTestButton := TNewButton.Create(WizardForm);
SvcStartNameTestButton.Parent := WizardForm;
SvcStartNameTestButton.Left := 8;
SvcStartNameTestButton.Top := WizardForm.ClientHeight -
SvcStartNameTestButton.Height - 8;
SvcStartNameTestButton.Width := 175;
SvcStartNameTestButton.Caption := 'Get service start name...';
SvcStartNameTestButton.OnClick := #SvcStartNameTestButtonClick;
end;
Quite easier (and probably faster) would be to make an external library and call it from the script. If you have Delphi or Lazarus, you can use the following function, which uses the QueryServiceConfig function to get the lpServiceStartName member, that you asked for:
function GetServiceStartName(const AServiceName: string): string;
var
BufferSize: DWORD;
BytesNeeded: DWORD;
ServiceHandle: SC_HANDLE;
ServiceManager: SC_HANDLE;
ServiceConfig: PQueryServiceConfig;
begin
Result := '';
ServiceManager := OpenSCManager(nil, nil, SC_MANAGER_CONNECT);
if ServiceManager <> 0 then
try
ServiceHandle := OpenService(ServiceManager, PChar(AServiceName),
SERVICE_QUERY_CONFIG);
if ServiceHandle <> 0 then
try
if not QueryServiceConfig(ServiceHandle, nil, 0, BufferSize) and
(GetLastError = ERROR_INSUFFICIENT_BUFFER) then
begin
ServiceConfig := AllocMem(BufferSize);
try
if QueryServiceConfig(ServiceHandle, ServiceConfig, BufferSize,
BytesNeeded)
then
Result := ServiceConfig^.lpServiceStartName;
finally
FreeMem(ServiceConfig);
end;
end;
finally
CloseServiceHandle(ServiceHandle);
end;
finally
CloseServiceHandle(ServiceManager);
end;
end;
I didn't liked the idea of linking external library so I finally solved the problem this way:
function GetServiceLogonAs():string;
var
res : Integer;
TmpFileName, FileContent: String;
begin
TmpFileName := ExpandConstant('{tmp}') + '\Service_Info.txt';
Exec('cmd.exe', '/C sc qc "MyServiceName" > "' + TmpFileName + '"', '', SW_HIDE, ewWaitUntilTerminated, res);
if LoadStringFromFile(TmpFileName, FileContent) then
begin
Result := Trim(Copy(FileContent,Pos('SERVICE_START_NAME', FileContent)+20,Length(FileContent)-(Pos('SERVICE_START_NAME', FileContent)+21)));
DeleteFile(TmpFileName);
end
else
begin
ShowErrorMsg('Error calling: GetServiceLogonAs(" + MYSERVICE + ")', res);
Result := '';
end;
end;