Delphi: Receiving arrays in web service functions - web-services

I need to be able to receive a array (preferably a two dim array) in one of my functions in my webservice. I've tried looking on the internet, but could not find a example piece of code.
Using arrays is fine and normal, but do not have an idea how to make my webservice access them as part of the function parameters. It only needs to be oneway as I will return a string as the function results.
Any help would be appreciated.
Thank you

There's a demo called EchoService, I think it's in the D2007 (and higher) demo directory. It has about a dozen methods, from simple strings to TDateTime, arrays, and structures. Here is the cut-down, showing just the string array handling. I found it on my system here:
C:\Users\Public\Documents\RAD Studio\7.0\Demos\DelphiWin32\VCLWin32\WebServices\EchoService
EchoService_CGI.dpr
IEchoService = interface(IInvokable)
['{EE714217-1763-4EFF-93D7-85C6D5E8180C}']
function echoStringArray(const value: ArrayofString): ArrayofString; stdcall;
end;
TEchoService = class(TInvokableClass, IEchoService)
public
function echoStringArray(const value: ArrayofString): ArrayofString; stdcall;
end;
function TEchoService.echoStringArray(const value: ArrayofString): ArrayofString;
begin
Result := value;
end;

Related

Error invalid use of void expression. Trying to pass a parameter through to the function

I am trying to initialize a server to look as specific inputs based on the request it gets. there are a lot of them so I want to initialize it with a loop as follows:
void serverInit() {
for (int i = 1; i <= qty; i++) {
String s = "/readBatt" + i;
server.on(s, runTest(i));
}
server.begin();
Serial.println("Server started.");
}
It's telling me that server.on(s, runTest(i)); is an invalid use of void expression. I know it wants it formatted as server.on(s, runTest) but the function runTest(int n) takes a parameter. How can i pass this parameter through to the function?
It seems you are using the WebServer class from the ESP32 Arduino libraries. As you have gleaned already, the callback specified in the on() method does not accept any arguments.
You have an alternative, however. You can specify a 'placeholder' in the URL path - using curly brackets - {}. In the callback, then, the corresponding argument can be retrieved by using the pathArg() method - which accepts the argument index as parameter.
Example ...
You could define your API endpoint as /readBatt/<battery number>. To configure the server to handle requests to this endpoint, then, you would use something like
#include <uri/UriBraces.h>
server.on(UriBraces("/readBatt/{}"), runTest);
In your callback, you would retrieve the first argument as follows ...
static void runTest() {
String batteryNumber = server.pathArg(0);
Serial.println("Request to read battery");
String response = "You attempted to read battery " + batteryNumber;
response += ".\nThis endpoint is a placeholder. Check again soon!";
server.send(200, "text/plain", response);
}
Finally ... Suppose your ESP8266 was running on local IP address 192.168.1.9. You could access your new API endpoint by opening
http://192.168.1.9/readBatt/1
in your browser. (Replace 1 with the relevant battery number.)
I don't think there are versions of the pathArg() which return an integer, unfortunately, so you may have to perform a conversion at some point.
You can use what's called a "closure". A closure lets you compose a function which retains access to variables outside of its scope.
A closure is written using a "lambda expression" - basically an anonymous function. C++'s syntax for lambda expressions looks like this:
[capture variable list](argument list) { body}
The capture variable list is a list of variables you want to be made available inside the body. The argument list is the normal function argument list that would get passed in by the caller. You'd write the lambda expression you need like this:
[i]() { runTest(i); }
and use it like this:
server.on(s, [i]() { runTest(i); });
To be clear, #David Collins' answer is the better way to write the web server. Using a parameter in the URL is better than creating several URLs with the parameter embedded in them. I'm just answering the question of how to pass a parameter to a function that gets called without arguments. If you write the web server code the better way, you won't need to do this (although I would do a bounds check on the value passed in the URL to make sure you're getting a valid battery number).

How to unregister style using TStyleManager::UnRegisterStyle()

I want to unregister a specific style using this code:
void __fastcall TfrmMain::btnUnregStyleClick(TObject *Sender)
{
TCustomStyleServices *MyStyle;
// Get wanted style
MyStyle = TStyleManager::Style["Emerald"]; // this will return a TCustomStyleServices obj
if (MyStyle != NULL)
{
// Remove it
TStyleManager::UnRegisterStyle(MyStyle); // This will set default Windows style (no style)
}
}
It works. The style seems to be unregistered, and the GUI switches automatically to the default Windows style.
But when program shuts down, I get this error:
Project Project.exe raised exception class $C0000005 with message
'access violation at 0x5005fd50: read of address 0xffffffd0'.
Here is the Call stack:
:5005fd50 rtl250.#System#TObject#InheritsFrom$qqrp17System#TMetaClass + 0x8
:50d12a8d vcl250.#Vcl#Styles#TStyleEngine#Notification$qqr54Vcl#Themes#TCustomStyleEngine#TStyleEngineNotificationpv + 0x1d
:00e5a612 vclwinx250.#Vcl#Winxctrls#TSearchBox#$bcdtr$qqrv + 0x1e
:0041fa0f __cleanup + 0x1F
:0041fb92 ; __wstartup
[Update: this crash is fixed if I remove TeeChart from my form. But UnRegisterStyle() still won't work]
If after UnRegisterStyle() I call:
TStyleManager::LoadFromFile(usStylePath);
TStyleManager::SetStyle("Emerald");
it will tell me that the "emerald style is already registered".
So, obviously UnRegisterStyle() fails.
Getting the "styles" list via TStyleManager::StyleNames() shows that the list remains unchanged after UnRegisterStyle().
Embarcadero has no help on this function. Should I call something else, additionally to UnRegisterStyle()?
Answer has been updated! Again.
Well, I am not very experienced user of VCL's styles but I will try to explain what to do to avoid your problem. Before you will read further I should tell you: there is no way to unregister any style.
Style or not Style
First, you must know that VCL's styles uses internal FRegisteredStyles dictionary to store all registered styles. Style became registered after TStyleManager.LoadFromFile(YourStyleName) has been called. Unfortunately, style never gets removed from dictionary.
Do not use UnregisterStyle procedure. It doesn't unregister style at all. Simply removes specified style from the list of available styles. Thus, after you call UnregisterStyle(YourStyle) you just wipe out YourStyle from internal list, not from dictionary mentioned earlier and cannot set this style.
After successful call to UnregisterStyle() you may call LoadFromFile() method and wonder why applicaition said:
Style 'Emerald' already registered.
This happens because LoadFromFile() method loads specified style and checks its existing in internal dictionary of registered styles. This checking always returns true as UnregisterStyle() procedure doesn't delete specified style from dictionary.
The same thing related also with property StyleNames. This property returns name of style uses internal FRegisteredStyles dictionary to obtain name of style with specified index.
If you want to know my opinion, such behaviour of style is weird. Method UnregisterStyle() should also delete specified style from dictionary. Maybe I am wrong but this is a real bug. On the other hand, there is no bug - you are trying to use undocumented method. The truth is somewhere near.
Styled Result
As conclusion, I would recommend you to use direct access to styles to decide can you use the selected style or not. See the code below (assuming you have styling of application turned on):
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(TStyleManager.Style['Emerald']) and TStyleManager.Style['Emerald'].Available then
// Do your code
else
ShowMessage('Specified style is not available!');
end;
If you have sources of VCL.Themes you can easily check the plausibility of what I wrote here.
High Technology
I have created class-helper for TStyleManager that allows to check if style-file registered and unregister it if needed. In order to fill TStyleInfo record with real values from style-file, we need save specified style to TMemoryStream and then pass stream to IsValidStyle function. Also we could use physical path to style (f.e. C:\Embarcadero\Delphi\Styles\Emerald.vsf) instead of variant with stream, but latter looks more elegant.
Unfortunately, according to Remy's comment (I don't know how to make a link to it), C++ Builder (which is used by OP) doesn't support class-helper feature. The only solution is to create simple unit which contains all code needed for class-helper. To work with it is enough to add such unit to uses clause and call appropriate public procedures\functions.
This unit have the following structure:
unit StyleManager_CH;
interface
uses
VCL.Themes;
function IsStyleRegistered_CH(var AStyle: TCustomStyleServices): Boolean;
procedure UnregisterStyleEx_CH(var AStyle: TCustomStyleServices);
implementation
uses
System.SysUtils, System.Classes, System.Generics.Collections;
type
TStyleManagerHelper = class helper for TStyleManager
public
class function IsStyleRegistered(var AStyle: TCustomStyleServices): Boolean;
class procedure UnregisterStyleEx(var AStyle: TCustomStyleServices);
end;
class function TStyleManagerHelper.IsStyleRegistered(var
AStyle: TCustomStyleServices): Boolean;
begin
Result := Assigned(AStyle) and
TStyleManager.FRegisteredStyles.ContainsKey(AStyle.Name);
end;
class procedure TStyleManagerHelper.UnregisterStyleEx(var
AStyle: TCustomStyleServices);
var
MS: TMemoryStream;
StyleInfo: TStyleInfo;
SourceInfo: VCL.Themes.TStyleManager.TSourceInfo;
begin
if Assigned(AStyle) then
begin
MS := TMemoryStream.Create;
try
AStyle.SaveToStream(MS);
MS.Position := 0;
if AStyle.IsValidStyle(MS, StyleInfo) then
begin
if TStyleManager.FRegisteredStyles.ContainsKey(StyleInfo.Name) then
begin
SourceInfo := TStyleManager.FRegisteredStyles.Items[StyleInfo.Name];
if Assigned(SourceInfo.Data) then
FreeAndNil(SourceInfo.Data);
TStyleManager.FStyles.Remove(AStyle);
TStyleManager.FRegisteredStyles.Remove(StyleInfo.Name);
FreeAndNil(AStyle);
end;
end;
finally
MS.Free;
end;
end;
end;
function IsStyleRegistered_CH(var AStyle: TCustomStyleServices): Boolean;
begin
Result := TStyleManager.IsStyleRegistered(AStyle);
end;
procedure UnregisterStyleEx_CH(var AStyle: TCustomStyleServices);
begin
TStyleManager.UnregisterStyleEx(AStyle);
end;
end.
Addendum _CH stands for class-helper abbreviation. There were also fixed some memory leaks.
I am not sure if there is another way to reload style-file "on-the-fly".

Rewinding input data of TJSONIterator class with TRewindReaderProc

I'm trying to parse JSON in a program built with embarcadero's c++builder (Tokyo 10.2 Update 3), which is not easy considering their severe lack of documentation.
I am using the TJSONIterator Find method that returns true or false if the Path (eg [0]['key'] or car.model['colour']) you give exists in the JSON data, which according to embarcadero's documentation needs a rewinding procedure passed to the constructor of the TJSONIterator class and if it's not there then an exception is thrown stating that.
The rewinding procedure should inherit the _di_TRewindReaderProc interface so here is my class.
class rewindclass : public TJSONIterator::_di_TRewindReaderProc
{
public:
void __fastcall Invoke(System::Json::Readers::TJsonReader* AReader)
{
//code to rewind Iterator
Areader->Rewind();
}
};
I'm not sure what should go into the Invoke function because as I said the documentation is useless. Obviously you have to do something with the TJsonReader that's passed and the only function I can see that could be used is Rewind but I don't think that's it because the only thing the documentation says about the TRewindReaderProc is
Reference to a procedure that rewinds the input data of the specified JSON reader.
Note: TJsonReader.Rewind does not rewind the input data, it resets the state of the JSON
reader. This procedure must rewind the actual data stream that provides the input data
of the JSON reader.
and I cannot see what else could be used instead. It says the actual data stream that provides the input must be reset but I'm not sure how to do this.
I'm using a TStringReader to read in the JSON data which is fed into a TJsonTextReader class constructor and that's fed into a TJSONIterator class constructor with a class that is using the _di_TRewindReaderProc interface.
//create rewindclass
rewindclass *rewind = new rewindclass();
//setting up TJSONIterator class
TStringReader *sread = new TStringReader(this->Memo1->Text);
TJsonTextReader *jread = new TJsonTextReader(sread);
TJSONIterator *jit = new TJSONIterator(jread, *rewind);
This code compiles ok but when I debug it and step into the TJSONIterator constructor the TJsonTextReader is not passed through and because of that when I call the Find method a second time it throws an exception saying no callback procedure set.
So does anyone know why the _di_TRewindReaderProc is not being passed through and what should go into the Invoke method?
I struggled for hours with the same problem in Delphi and finally I managed to find a way to make it works. I was using a TFileStream and TStreamReader as underlying readers instead of a TStringReader but I hope you understand the concepts behind any reader. This is my code:
procedure LoadFromFile(AFileName: string);
var
FS: TFileStream;
StreamReader: TStreamReader;
JTReader: TJsonTextReader;
JIterator: TJSONIterator;
RewindProc: TJSONIterator.TRewindReaderProc;
begin
RewindProc := procedure (AReader: TJsonReader)
begin
TStreamReader(TJsonTextReader(AReader).Reader).DiscardBufferedData;
TStreamReader(TJsonTextReader(AReader).Reader).BaseStream.Seek(0, TSeekOrigin.soBeginning);
end;
FS:=TFileStream.Create(AFileName, fmOpenRead);
StreamReader:=TStreamReader.Create(FS);
JTReader:=TJsonTextReader.Create(StreamReader);
JIterator:=TJSONIterator.Create(JTReader , RewindProc);
JIterator.Find('some.path.here');
JIterator.Find('other.path.here');
end;
The lines inside the RewindProc procedure make the magic. As documentation said, you must make the rewind of the underlying readers and that is what I did with these two lines. The first one clean the internal buffer of the StreamReader and the second one move the file pointer to the beginning.
I hope this help you in some way because the documentation is really poor and I can't find any article on Internet about how to set up properly the rewind procedure to be passed to the JSON iterator.
PD: Then I found some other troubles working with TIterator.Find but that is other matter so I focused on your specific question.
Since TJSONIterator alreadys calls Rewind on the reader, before it calls your Rewind procedure, calling Rewind again is not necessary. Instead reset the stream and discard all buffers of the Reader:
procedure TForm1.Button1Click(Sender: TObject);
const
JsonRec = '{"some":{"path":{"there":"ahi", "here":"acqui"}}}';
var
StringStream: TStringStream;
StreamReader: TStreamReader;
JsonTextReader: TJsonTextReader;
Iterator: TJSONIterator;
begin
JsonTextReader:= nil;
Iterator:= nil;
StringStream:= TStringStream.Create(JsonRec);
try
StreamReader:= TStreamReader.Create(StringStream);
JsonTextReader:= TJsonTextReader.Create(StreamReader);
Iterator:= TJSONIterator.Create(JsonTextReader,
procedure (AReader: TJSONReader)
var
v: TValue;
begin
StringStream.Seek(0, soBeginning);
StreamReader.DiscardBufferedData;
//workaround for RSP-24517
v:= TRttiContext.Create.GetType(TJsonTextReader).GetField('FChars').GetValue(AReader);
v.SetArrayElement(0, #0);
end);
if Iterator.Find('some.path.here') then
ecDebug.Lines.Add(Iterator.AsString);
if Iterator.Find('some.path.there') then
ecDebug.Lines.Add(Iterator.AsString);
finally
Iterator.Free;
JsonTextReader.Free;
StreamReader.Free;
StringStream.Free;
end;
end;
There doesn't seem to be a way to reset a TStringReader however, that's why I'm using a TStringStream instead.
Update: I've added the necessary call to DiscardBufferedData in the rewind procedure. This only showed up after tests with bigger files.
Update2: With json files larger than 1K, there's a workaround to a bug in TJsonTextReader required, which fails to clear FChars, so it isn't re-reading the json file after a call to .Rewind which causes an exception "Unexpected character encountered while parsing value...". To access the private FChars, I'm using RTTI as described in https://stackoverflow.com/a/36717896/386473. The bug is logged in QP as https://quality.embarcadero.com/browse/RSP-24517.

Calling an xll UDF from VBA

I would like to call one of my User Defined Function from VBA.
My User Defined Function is declared in C++:
XLOPER12*WINAPI HelloWorld()noexcept{
THROWS();
static XLOPER12 res=[](){
static std::array<wchar_t,13>str={
11,'H','e','l','l','o',' ','W','o','r','l','d','\0'};
XLOPER12 tmp;
tmp.xltype=xltypeStr;
tmp.val.str=str.data();
return tmp;}();
return &res;}
This is a simplified version of a real function from the field which can either return a String or a double or even arrays. Of course here I am only returning a String but this limit the return type of my UDF to LPXLOPER12.
I can successfully register my function with xlfRegister specifying a pxTypeText of "U$". I can then call my UDF from Excel:
=HelloWorld()
And it works!
If I try to call my function from VBA as suggested here:
Sub macro_test()
Dim hw As Variant
hw = Application.Run("D:\Path\MyAddIn.xll!HelloWorld")
End Sub
I get an error message from Application.run:
Run-time error '1004': Application-defined or object-defined error
If I try to call my function from VBA as suggested here:
Private Declare PtrSafe Function HelloWorld Lib "C:\Path\MyAddIn.xll" () As Variant
Sub macro_test()
Dim hw As Variant
hw = HelloWorld()
End Sub
I get an empty result instead of "Hello World".
What am I doing wrong ?
Miscellaneous pieces of information:
Using Excel 2013
Using VS 2017 15.5
Using the first method (Application.Run), VBA does not call my function (I cannot step into my function with the debugger).
Using the second method, VBA calls my function (I can step into my function with the debugger).
Using the second method, when I add xlbitDLLFree to my xltype, function xlAutoFree12 is not called, which makes me think that somehow the return value is not understood properly by VBA.
If your XLL is loaded and its UDFs are registered so that=HelloWord() in a cell works then you should just be able to call it from VBA like this (unless there is a problem with parameterless string functions)
var=Application.run("HelloWorld")
You can also use Evaluate
var=Application.Evaluate("=HelloWorld()")
I tested my REVERSE.TEXT XLL function like this and it worked correctly.
Sub testing()
Dim var As Variant
var = Application.Run("REVERSE.TEXT", "Charles")
var = Application.Evaluate("=REVERSE.TEXT(""Charles"")")
End Sub
Reverse.Text is registered using UQQ$ (there are 2 parameters , the Text and the Number of characters)

Delphi 10.1 - Wrong Match.Value in TMatchEvaluator when calling TRegEx.Replace()

I've found one bug with latest Delphi 10.1 Berlin (and in 10.2 Tokyo too).
If you call TRegEx.Replace with TMatchEvaluator specified, you may got wrong TMatch inside Evaluator function. In Delphi XE-XE5 it seems works good.
Evaluator:
function TForm1.EvaluatorU(const Match: TMatch): string;
var
lChar: Word;
lMatchVal: string;
begin
Result := '';
lMatchVal := Match.Groups[1].Value;
lChar := StrToIntDef('$'+lMatchVal, 0);
if lChar <> 0 then
Result := Char(lChar);
end;
Call:
Result := TRegEx.Replace('\u0418\u0443, \u0427\u0436\u044d\u0446\u0437\u044f\u043d', '\\u([0-9a-f]{4})', EvaluatorU, [roIgnoreCase]);
First call of Evaluator will bring right TMatch.Value (or TMatch.Group[].Value) context, but second call will bring wrong Match.Value to callback function(
Do you have some idea about workaround?
I going to check this issue on TPerlRegEx class, maybe something wrong is in wrapper (TRegEx) functions.
Update: with TPerlRegEx Replacement with callback function (OnReplace) works good...
Update2: it seems there is a bug in TPerlRegEx. It returns wrong GroupOffsets. On First call of OnReplace callback this value is correct. Next calls returns +1 offset more than needed. But call of TPerlRegEx.Groups returns correct subgroup value...
Last update and solution found:
I've found a problem in TPerlRegEx.UTF8IndexToUnicode function optimization.
There are LastIndex*/LastIndexResult* fields used to optimize sequential call of function with same params. But after replacement made via callback functions and when MatchAgain is called into TPerlRegEx.ReplaceAll function, this can make a bad trick(
Simple solution is copy System.RegularExpressionsCore.pas from \source\rtl\common to your project directory and replace call of TPerlRegEx.UTF8IndexToUnicode to deprecated unoptimized UTF8IndexToUnicode function... Or clear this internal fields somewhere in ClearStoredGroups function for example.
upd. Here is my embarcadero quality central issue.