Jump to content

Remy Lebeau

Members
  • Content Count

    2684
  • Joined

  • Last visited

  • Days Won

    113

Everything posted by Remy Lebeau

  1. I have updated the repo now.
  2. Remy Lebeau

    THttpClient vs TIdHttp in Linux

    Did you try capturing and comparing the raw HTTP requests that THTTPClient and TIdHTTP are generating to see what is different between them?
  3. Remy Lebeau

    THttpClient vs TIdHttp in Linux

    Can you be more specific? Yes. What does that have to do with the issue?
  4. OK, I will make that change tomorrow. I added that file earlier today, along with the non-cool DCR, and the LRS for FreePascal. Which changes? That's good to hear, thanks.
  5. Doubtful, given it was just created 2 days ago.
  6. Remy Lebeau

    Upgrading to new RAD Studio vs. project Lib names

    Nope. The documentation mentions the unit name and header file, but not the lib file.
  7. Remy Lebeau

    TButton : change backgroundcolor

    Embarcadero posted a blog article about that for TEdit, but the same approach should work for a TButton as well: Edit Custom Style to change the background color of a FMX TEdit It is just a property like any other. What is stopping you from setting it?
  8. Remy Lebeau

    Calling a Delphi DLL function from a C++ Builder application

    The C++ code is not creating a valid AnsiString object for the Delphi code to manipulate. The struct would have needed to look more like this instead: struct DECLSPEC_DRECORD TMyRecord { AnsiString MyString; int MyInteger; }; But, that still would not have worked correctly in your case, because the internal structure of AnsiString changed in 2009, so passing an AnsiString object between C++Builder 6 and Delphi 12.1 would cause Undefined Behavior, It it never safer to pass non-POD types across a DLL boundary, unless very strict prerequisites are met (ie, sharing a common RTL, sharing the same memory manager, etc), which is not the case in this example. To do what you are attempting, you would have to allocate AND free the string memory inside of the DLL, and then pass that memory around as needed, eg: library MyDLL; uses System, SysUtils; type TMyRecord = record MyString: PAnsiChar; MyInteger: Integer; end; function FillRecord(var Rec: TMyRecord): Boolean; stdcall; begin Rec.MyString := StrNew(PAnsiChar('Hello from Delphi')); Rec.MyInteger := 42; Result := True; end; procedure DisposeRecord(var Rec: TMyRecord); stdcall; begin StrDispose(Rec.MyString); Rec.MyString := nil; end; exports FillRecord, DisposeRecord; begin end. struct DECLSPEC_DRECORD TMyRecord { char* MyString; int MyInteger; }; extern "C" __declspec(dllimport) bool __stdcall FillRecord(TMyRecord *Rec); extern "C" __declspec(dllimport) void __stdcall DisposeRecord(TMyRecord *Rec); TMyRecord iMyRec = {}; Memo1->Lines->Clear(); Memo1->Lines->Add(Format("Address: %p", ARRAYOFCONST((&iMyRec)))); if (FillRecord(&iMyRec)) { AnsiString iData = iMyRec.MyString; Memo1->Lines->Add("iMyRec.MyString: " + iData); int iNumber = iMyRec.MyInteger; Memo1->Lines->Add("iMyRec.MyInteger: " + IntToStr(iNumber)); DisposeRecord(&iMyRec); } else { Memo1->Lines->Add("Error calling FillRecord"); }
  9. Remy Lebeau

    TButton : change backgroundcolor

    That is true only if its ControlType property is set to Platform. The default is Styled instead, in which case it is not a native Windows control. (This is posted in an FMX group, not a VCL group)
  10. Tools menu, Manage Features, select "Help" in the "Reference" section of the right sidebar, click Apply.
  11. Mouse.CursorPos is expressed in screen coordinates, not client coordinates. You would need to use PaintBox1.ScreenToClient() in this case: procedure TForm23.PaintBox1Click(Sender: TObject); begin if PaintBox1.ScreenToClient(Mouse.CursorPos).Y > 100 then Exit; // do something... end;
  12. Remy Lebeau

    Avoid parameter evaluation

    Really? http://docwiki.embarcadero.com/RADStudio/en/Anonymous_Methods_in_Delphi
  13. Remy Lebeau

    C++ to Delphi conversion problem

    You don't seem to be understanding the issue here. That code shows you how one EFTPOS_CONTEXT is being copied to another. Yes, it does show you how the 'char*' members are allocated, which CAN easily be translated to Delphi. But the operations on the std::vector members CANNOT be translated to Delphi, without knowing the PRECISE implementation of std::vector that the SDK is using. STL containers like std::vector DO NOT TRANSLATE to Delphi, without re-writing the SDK. This would be a whole different story if the SDK DLL were the one creating and managing the std::vector objects, but based on what you have shown, it is not, at least not completely. It is expecting the EXE code to participate in the object management. And that means your Delphi code would have to create and free the objects, and without the implementation details, there is nothing you can do in Delphi to make those std::vector objects valid so the SDK can use them properly. So, this is a lost cause trying to use EFTPOS_CONTEXT in Delphi directly, as it is simply not compatible with non-C++ languages. That is the way the SDK authors wrote it. Period. I'm sorry, but you are just going to have to find another solution. If you absolutely must use this SDK in a Delphi project, then you will have to wrap any needed SDK access inside of your own C++ DLL so the std::vector objects can be used correctly. And then you can have that DLL export a plain C-style interface (ie, one that doesn't expose access to the vectors) for your Delphi code to use. There is no other "safe" way to handle this situation. For example: // MyDLL.cpp #include <algorithm> #include "sdk.h" extern "C" __declspec(dllexport) void __cdecl InitSDKContext(char** contextId, EFTPOS_ERROR* error, void** context) { *context = new EFTPOS_CONTEXT; InitContext(contextId, error, *static_cast<EFTPOS_CONTEXT*>(context)); } extern "C" __declspec(dllexport) void __cdecl FreeSDKContext(void* context) { delete static_cast<EFTPOS_CONTEXT*>(context); } extern "C" __declspec(dllexport) char* __cdecl GetSDKContextLicenseKey(void* context) { return static_cast<EFTPOS_CONTEXT*>(context)->licenseKey; } extern "C" __declspec(dllexport) char* __cdecl GetSDKContextVatNumber(void* context) { return static_cast<EFTPOS_CONTEXT*>(context)->vatNumber; } extern "C" __declspec(dllexport) unsigned int __cdecl GetSDKContextDevices(void* context, EFTPOS_DEVICE *devices, unsigned int maxDevices) { EFTPOS_CONTEXT *ctx = static_cast<EFTPOS_CONTEXT*>(context); if (!devices) return ctx->devices.size(); auto numDevices = std::min(ctx->devices.size(), maxDevices); std::copy_n(ctx->devices.begin(), numDevices, devices); return numDevices; } extern "C" __declspec(dllexport) unsigned int __cdecl GetSDKContextProviders(void* context, SIGNATURE_PROVIDER *providers, unsigned int maxProviders) { EFTPOS_CONTEXT *ctx = static_cast<EFTPOS_CONTEXT*>(context); if (!providers) return ctx->providers.size(); auto numProviders = std::min(ctx->devices.size(), maxProviders); std::copy_n(ctx->providers.begin(), numProviders, providers); return numProviders; } ... // MyApp.pas type ERROR_LEVEL = ...; ERROR_CODE = ...; POS_PROTOCOL = ...; EFTPOS_ERROR = ^PEFTPOS_ERROR; EFTPOS_ERROR = record errorLevel: ERROR_LEVEL; errorCode: ERROR_CODE; errorMessage: PAnsiChar; end; PEFTPOS_DEVICE = ^EFTPOS_DEVICE; EFTPOS_DEVICE = record posId: PAnsiChar; terminalId: PAnsiChar; apiKey: PAnsiChar; host: PAnsiChar; port: LONG; posProtocol: POS_PROTOCOL; end; PSIGNATURE_PROVIDER = ^SIGNATURE_PROVIDER; SIGNATURE_PROVIDER = record ... end; PEFTPOS_CONTEXT = ^EFTPOS_CONTEXT; EFTPOS_CONTEXT = record end; // leave this empty! procedure InitSDKContext(contextId: PPAnsiChar; error: PEFTPOS_ERROR; var context: PEFTPOS_CONTEXT); cdecl; external 'my.dll'; procedure FreeSDKContext(context: PEFTPOS_CONTEXT); cdecl; external 'my.dll'; function GetSDKContextLicenseKey(context: PEFTPOS_CONTEXT): PAnsiChar; cdecl; external 'my.dll'; function GetSDKContextVatNumber(context: PEFTPOS_CONTEXT): PAnsiChar; cdecl; external 'my.dll'; function GetSDKContextDevices(context: PEFTPOS_CONTEXT; devices: PEFTPOS_DEVICE; maxDevices: UInt32): UInt32; cdecl; external 'my.dll'; function GetSDKContextProviders(context: PEFTPOS_CONTEXT; providers: PSIGNATURE_PROVIDER; maxProviders: UInt32): UInt32; cdecl; external 'my.dll'; ... var context: PEFTPOS_CONTEXT; devices: array of EFTPOS_DEVICE; providers: array of SIGNATURE_PROVIDER; InitSDKContext(..., context); ... ... := GetSDKContextLicenseKey(context); ... := GetSDKContextVatNumber(context); ... SetLength(devices, GetSDKContextDevices(context, nil, 0)); GetSDKContextDevices(context, PEFTPOS_DEVICE(devices), Length(devices)); ... SetLength(providers, GetSDKContextProviders(context, nil, 0)); GetSDKContextProviders(context, PSIGNATURE_PROVIDER(providers), Length(providers)); ... FreeSDKContext(context);
  14. Remy Lebeau

    C++ to Delphi conversion problem

    To translate it as-is, you would need to know the EXACT implementation details of the std::vector class, which varies from one STL library to another, and even from one C++ compiler to another. So, no, it is not (easily) translated to Delphi as-is. Whoever wrote this SDK clearly did not intend for it to be used outside of C++. On the other hand, they also apparently did not understand that STL containers are not safe to access across the DLL boundary, either. If the DLL code creates the vector and then the EXE code accesses it, or vice versa, then the DLL and the EXE must be written using the exact same compiler and STL implementation, otherwise it will cause undefined behavior. Since this struct holds context data, those details should have been hidden in the SDK interface to avoid this problem. That being said, if you are just trying to declare this context struct in Delphi and pass it to this SDK, and only the SDK will ever access the context members, then you might try simply declaring the std::vector members as fixed byte arrays with the same byte size as the std::vector class. But, you would still need a C++ compiler to determine the correct byte size (unless you decompile the SDK to see what size it is actually expecting). One common implementation of std::vector is to use 3 pointer members, thus sizeof(vector) equals sizeof(Pointer)*3, which would be 12 bytes in a 32bit build and 24 bytes in a 64bit build. But, another common implementation of std::vector is to use 1 pointer member and 2 integer members, so sizeof(vector) would depend on the size of the integer type being used (usually std::size_t, which can be either 4-8 bytes depending on the STL implementation), so sizeof(vector) could be either 12 bytes or 20 bytes in a 32bit build, and likely would be 24 bytes in a 64bit build. Also, another issue is that the SDK code you have shown has the InitContext() function taking in the context struct by value, which means the caller is responsible for allocating and later freeing the context, including the std::vector members. And Delphi simply can't allocate and free the memory block that std::vector uses internally. So, worst case, you would have to leak the memory or risk crashing your code. So, you are basically SOL, I think. Complain to the SDK author and see if they have an alternative solution for non-C++ languages.
  15. Remy Lebeau

    Avoid parameter evaluation

    Java introduced anonymous classes in Java 1.1 in 1997. Delphi introduced anonymous methods in 2009. Java introduced anonymous lambdas/closures in Java 8 in 2014 (but they were first proposed in 2006).
  16. Remy Lebeau

    Avoid parameter evaluation

    Not quite. You have implemented DoProcess() incorrectly, causing undefined behavior. The code compiles, but it will not behave correctly at runtime for certain types. For instance, try passing a single Char into it and your code will crash. That approach is wrong. You cannot treat all of those types the same way, as they are stored in different ways in the array. vtChar and ctWideChar are stored as individual values in the array itself (like integers are), whereas vtString is a pointer to a ShortString variable, whereas vtAnsiString and vtUnicodeString are copies of the data block pointer from inside of an AnsiString/UnicodeString instance, etc. You need to handle the various types individually and correctly, eg: procedure DoProcess(const Args: array of const); var i: Integer; begin for i := Low(Args) to High(Args) do begin case Args[i].VType of vtString: Writeln(Args[i].VString^); vtChar: Writeln(Args[i].VChar); vtPChar: Writeln(Args[i].VPChar); vtWideChar: Writeln(Args[i].VWideChar); vtPWideChar: Writeln(Args[i].VPWideChar); vtAnsiString: Writeln(AnsiString(Args[i].VAnsiString)); vtWideString: Writeln(WideString(Args[i].VWideString)); vtUnicodeString: Writeln(UnicodeString(Args[i].VUnicodeString)); vtInteger: Writeln(Args[i].VInteger); vtInt64: Writeln(Int64(Args[i].VInt64)); else Writeln('Type handling not implemented yet ' + IntToStr(Args[i].VType)); end; end; end; Also, see the MakeStr() example in this earlier post:
  17. Remy Lebeau

    Avoid parameter evaluation

    Since performance is in question, I would suggest taking this approach a step further and use 'array of const' instead of 'array of string'. That way, you can pass in integers and other fundamental types as-is and not invoke the overhead of converting them to strings unless you are sure you actually need to, eg: procedure Log(LogLevel: TLogLevel; const Args: array of const); overload; begin if CanLog(LogLevel) then begin // convert Args to a log message and write it out as needed... end; end; Log(llTrace, ['Test', GetLastError(), ...]);
  18. Remy Lebeau

    Avoid parameter evaluation

    FreePascal supports preprocessor macros; https://www.freepascal.org/docs-html/3.2.0/prog/progse5.html For Delphi, you would have to write/find an external tool to preprocess your code before compiling it.
  19. Remy Lebeau

    How to check for Delphi update?

    Because only 1 patch has been released so far. And that was for 12.0, not for 12.1. Correct. Point releases usually require a full uninstall and reinstall. You can't do that with Getit from within the IDE (but the installer uses GetIt technology). Patches can update an existing installation, so it makes sense that they can be distributed via GetIt. You can login to your account at https://my.embarcadero.com to download the 12.1 installer. Feel free to file a feature request at https://qp.embarcadero.com
  20. I'm not sure I understand. Are you saying that when you go to start a new project, there is no VCL option provided? If so, then maybe your installation is messed up, or maybe you are using Kylix instead of D7. Hard you say. Can you provide screenshots of what you are actually seeing?
  21. Remy Lebeau

    System.Net.Socket: TSocket.BeginReceive timeout

    If you don't want to ignore all socket exceptions, you can use breakpoints to tell the debugger to ignore the exceptions only for particular sections of code. And even use breakpoints to toggle when other breakpoints are enabled/disabled. Breakpoints have quite a few useful features beyond just stopping execution when they are hit. It is still available, it is just not installed by default. See https://docwiki.embarcadero.com/RADStudio/en/Installing_Socket_Components
  22. Remy Lebeau

    System.Net.Socket: TSocket.BeginReceive timeout

    Not to sound gloomy, but I have yet to see Borland/CodeGear/Embarcadero produce a good solid cross-platform socket library. There is always problems in their implementations. Back in the day, CLX's sockets were notoriously buggy. The Windows-only ScktComp unit is pretty solid (if you ignore its bad handling of Unicode text and TStream - the rest of it is good, though). The traditional approach is to use either 1) synchronous socket I/O, 2) non-blocking socket I/O with select() or (e)poll(), or 3) OS-specific notifications. WinSock on Windows provides notifications via window messages (which ScktComp handles). Most other platforms don't offer notifications, so that leaves blocking or polling. In this case, TSocket.BeginReceive() is using a combination of #1 and #2 - it uses a TTask thread to perform a blocking read, but it also uses select() to wait for new bytes to arrive before then reading them. The problem is that ReceiveTimeout is 0 by default, and comments in the implementation say both 0 and -1 are supposed to be infinite, but 0 is not actually treated as infinite. It is used for polling, but polling timeouts are being treated as failures like any other. If any timeout error occurs then the read operator is over, it doesn't keep polling. Unless you are handling the failure and calling BeginReceive() again? You could just tell the debugger to ignore the errors. Why would they not be allowed, in what? Possibly. I've never had much trouble with ScktComp (and things it does have problems with are easily worked around).
  23. In addition to what others said - it is unusual to get an AccessViolation due to Execution of an address, usually it is due to a Read or Write instead. If you are executing code at a bad memory address, that means you tried to call a function via a corrupted function pointer, or something like that, which will be much more difficult to find. If it were just a read/write error, the error message would also give you the memory address of the code that tried to access the bad memory, so you could more easily jump right to that code.
  24. Remy Lebeau

    Indyclient help

    Then you are using an outdated version of Indy and should upgrade. The ReadUInt16() method was added almost a decade ago, in Delphi XE8. In the meantime, you can use the older ReadWord() method instead. That calculation is wrong. You need to multiple by 256 ($100), not by 16 ($10). Better to just use ReadWord()/ReadUInt16() instead. True means it will append the read bytes to the end of the TIdBytes. False means it will store the read bytes at the beginning of the TIdBytes, and will grow the TIdBytes if it is too small.
  25. Remy Lebeau

    Stand alone EXE

    Not a whole lot, unfortunately. Trimming the debug info helps a lttle, but most of the size is just RTL bloat, Unicode, DFMs, RTTI, etc. In Delphi, it is possible to reduce some of the RTTI a little, but not in C++Builder, AFAIK.
×