The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,862 other subscribers

Archive for the ‘Delphi’ Category

Delphi: TArray of T versus TArray; a function can return the latter, but not the former

Posted by jpluimers on 2021/07/05

I wish I had blogged about this a lot sooner, as then far less people would use var aFoo: array of TFoo as method parameters for results that are just out parameters and could be a function result.

You cannot have array of TFoo as function result, but you can have TArray<TFoo>. The former would be an open array, the latter is a proper array type.

I see many people use var aFoo: array of TFoo with all sorts of SetLength calls before calling and inside a method where a function returning a TArray<TFoo> would be far more appropriate, both in the sense of readability and maintainability.

–jeroen

Posted in Delphi, Development, Software Development | 2 Comments »

Tuple class and records in the Spring4D framework

Posted by jpluimers on 2021/07/01

The Spring Framework for Delphi has some cool tuple support.

The [WayBack] Tuple class has 3 create methods that build generic Tuple<> records with 2, 3 and 4 fields:

–jeroen

Posted in Delphi, Development, Software Development, Spring4D | Leave a Comment »

Delphi interface generic function – Is there a work around? – Stack Overflow

Posted by jpluimers on 2021/06/30

The documentation simply states “no”, without explaining why.

Luckily, there is [WayBack] David Heffernan at [WayBack] Delphi interface generic function – Is there a work around? – Stack Overflow

Interfaces do not support generic parameterized methods, as the compiler says.

There is no workaround because it’s a fundamental limitation. Parameterized methods in classes are implemented by adding one method per instantiation to the class. That works for classes since they are concrete, but is not viable for interfaces. That’s because interfaces are a table of functions and the size of that table cannot vary depending on which generic method instantiations happen to be present elsewhere in the code. For similar reasons, generic methods cannot be virtual or dynamic.

In any case, it’s not possible. One option is to use a class instead. I agree that this is a bind.

If you want to do it in an interface, the best you can do is:

function CastAs(const IID: TGUID): IInterface;

But you’d have to call it like this:

MyIntf := ProxyIntf.CastAs(IMyIntf) as IMyIntf;

which feels somewhat foul.

Choose your poison!

In the background, for every distinct use of generic method parameters, the compiler generates an overloaded concrete version of the method. That is possible for classes and records, but not for interfaces.

In practice, I either go for the last option, or have the interface expose a record or class type that can have the required generic method (so the compiler can generate the overloads at run-time).

All options feel somewhat ugly, so I like the comment by [WayBack] Jason:

Wish I could “Program into Your Language, Not in it” programmers.stackexchange.com/questions/2777/…

Which leads to Code Complete

[WayBack] What are the key points of Code Complete? – Software Engineering Stack Exchange answer by [WayBack] limist:

Code Complete is about software craftsmanship; it is an advanced-beginner/intermediate-level book, written for the working programmer, but it would still be very useful to someone who’s been programming for at least a year.

Thus the key points of Code Complete (2nd ed.) are nicely summarized in its Chapter 34, Themes in Software Craftsmanship. As paraphrased from my notes:

  1. Conquer Complexity: reduce the cognitive load on your mind via discipline, conventions, and abstraction.
  2. Pick Your Process: be conscious of quality from start (requirements) to finish (deployment) and beyond (maintenance).
  3. Write Programs for People First, Computers Second: code readability is hugely important for comprehensibility, review-ability, error-rate, error-correction, modifiability, and the consequent development time and quality.
  4. Program into Your Language, Not in it: think of the What? and Why? before the How?
  5. Focus Your Attention with the Help of Conventions: conventions manage complexity by providing structure where it’s needed, so that the ultimate resource – your attention – can be effectively used.
  6. Program in Terms of the Problem Domain: work at the highest level of abstraction possible; top-level code should describe the problem being solved. Distinguish OS level, programming language level, low-level implementation structures, low-level problem domain terms, and finally, high-level problem-domain terms that would make total sense to the (non-coder) user.
  7. Watch for Falling Rocks: as programming merges art and science, good judgement is vital, including heeding warning signs.
  8. Iterate, Repeatedly, Again and Again: iterate requirements, design, estimates, code, code tuning.
  9. Thou Shalt Render Software and Religion Asunder: be eclectic and willing to experiment. Don’t be an inflexible zealot, it precludes curiosity and learning. Go beyond having just a hammer in your toolbox.

But the most important take-aways are in Chapter 33, Personal Character: once you consciously seek to improve as a coder, you can and will. The fastest way to do so is to take on the the attitudes of master coders(humility, curiosity, intellectual honesty, discipline, creativity), while also practicing their habits (many good habits are listed in the book, e.g. choosing good variable/value names).

Also, the book makes clear that the gap between average and excellent in software is immense; that fact alone should drive the conscientious coder to better himself.

That’s the short of it; the long version is in the book. :) I can also send you my not-so-long, not-so-short notes if you want more details. But the book is certainly money and time well-spent, even if the writing style is tiresome at times.

Beyond Code Complete, I’d highly recommend The Pragmatic Programmer. It’s for intermediate-level programmers, nicely-written and a great mix of high, medium, and low-level advice.

There are more limitations on generics in Delphi

The documentation on limitations has not changed much since Delphi 2009.

Delphi 2009

[WayBack] Overview of Generics: Platform Requirements and Differences

Generics are supported by the Delphi for Win32 compiler.

Runtime type identification (RTTI)

In Win32, generics and methods do not have RTTI, but instantiated types do have RTTI. An instantiated type is the combination of a generic with a set of parameters.

Interface GUID

In Win32, an instantiated interface type does not have an interface GUID.

Parameterized method in interface

A parameterized method (method declared with type parameters) cannot be declared in an interface.

Instantiation timing

Instantiation is processed by the compiler. All instantiated objects are emitted into .obj files.

Dynamic instantiation

Dynamic instantiation at runtime is not supported.

Interface constraints

The Win32 interface is not a “light” interface. This means all type parameters with interface constraints always support the COM IUnknown methods _AddRef_Release and QueryInterface or inherit from TInterfacedObject. Record types cannot specify an interface constraint parameter.

Delphi 10.3 Rio

[WayBack] Overview of Generics – RAD Studio: Platform Requirements and Differences

Generics are supported by the Delphi compilers.

Run-time type identification

In Win32, generics and methods do not have run-time type information (RTTI), but instantiated types do have RTTI. An instantiated type is the combination of a generic with a set of parameters. The RTTI for methods of a class is a subset of the RTTI for that class as a whole. If a non-generic class has a generic method, that method will not be included in the RTTI generated for the class because generics are instantiated at compile time, not at run time.

Interface GUID

In Win32, an instantiated interface type does not have an interface GUID.

Parameterized method in interface

A parameterized method (method declared with type parameters) cannot be declared in an interface.

Instantiation timing

Generic types are instantiated at compile time and emitted into executables and relocatable files. Instance variables of a generic type are instantiated at run time for classes and at compile time for generic records. The RTTI for generic classes is only generated when the classes are instantiated. RTTI for instantiated classes follows just as for non-generic classes. If the generic class has a generic method, then the instantiated generic class will not have RTTI for that generic method.

Dynamic instantiation

Dynamic instantiation at run time is not supported.

Interface constraints

The Win32 interface is not a “light” interface. This means all type parameters with interface constraints always support the COM IUnknown methods _AddRef_Release, and QueryInterface or inherit from TInterfacedObject. Record types cannot specify an interface constraint parameter.

–jeroen

Posted in Conference Topics, Conferences, Delphi, Development, Event, Software Development | Leave a Comment »

DCOM calls from thread pool threads: CoInitialize/CoUnitialize location and expensiveness?

Posted by jpluimers on 2021/06/24

Interesting takeaway from [WayBack] DCOM calls from thread pool threads

call CoInitialize* at the start, and call CoUninitialize before returning. Expensive, but necessary

Related:

–jeroen

Posted in .NET, C, C++, COM/DCOM/COM+, Delphi, Development, Software Development, Windows Development | Leave a Comment »

“No mapping for the Unicode character exists in the target multi-byte code page”

Posted by jpluimers on 2021/06/24

Usually when I see this error [Wayback] “No mapping for the Unicode character exists in the target multi-byte code page” – Google Search, it is in legacy code that uses string buffers where decoding or decompressing data into.

This is almost always wrong no matter what kind of data you use, as it will depend in your string encoding.

I have seen it happen especially in these cases:

  • base64 decoding from string to string (solution: decode from a string stream into a binary stream, then post-process from there)
  • zip or zlib decompress from binary stream to string stream, then reading the string stream (solution: decompress from binary stream to binary stream, then post-process from there)

Most cases I encountered were in Delphi and C code, but surprisingly I also bumped into C# exhibiting this behaviour.

I’m not alone, just see these examples from the above Google search:

–jeroen

Posted in .NET, base64, C, C#, C++, Delphi, Development, Encoding, Software Development, Unicode | Leave a Comment »

delphi – Is it possible to define {$IFDEF} for more than one directive at once? – Stack Overflow

Posted by jpluimers on 2021/06/24

[WayBack] delphi – Is it possible to define {$IFDEF} for more than one directive at once? – Stack Overflow:

–jeroen

Posted in Delphi, Delphi 6, Development, Software Development | Leave a Comment »

XMLDoc Delphi source code documentation generation – some links

Posted by jpluimers on 2021/06/23

There is very little information on how to use the XMLDoc documentation formatting in your Delphi source code.

So here are some links for me to get started:

XmlDoc comments can get verbose because of the lengthy XML syntax.

Hiding them in regions can help, for instance with the plugin at [WayBack] Fast-Forward »: XML Documentation in Delphi 2006.

I still should try NDoc – Wikipedia for post processing of the Delphi generated XML file, but since I almost exclusively use the internal IDE viewer, that is good enough for me now.

Syntax

Most of the above links talk about tooling, but little about syntax. Luckily, it is very similar to the C# XML Documentation syntax documented by Microsoft:

DevJet has a nice document describing all Delphi supported tags in [WayBack] Delphi-Documentation-Guidelines.pdf (via [WayBack] DevJet Software » Delphi Documentation Guidelines) including the tags mentioned in [WayBack] Dr.Bob Examines… #100: Generating Documentation.

For comparison:

One Delphi specific thing on the see tag.

The see tag accepted a syntax like UnitName|IdentifierName (see for instance [WayBack] How/under which circumstances does the tag in Delphi xml comments actually work? – Stack Overflow).

In Delphi 10.1 Berlin, sometimes that did not work and I had to use the UnitName.IdentifierName syntax.

The difference is how it is displayed: UnitName|IdentifierName shows as IdentifierName, whereas UnitName.IdentifierName is shown in full.

Sometimes one or the other is unclickable.

In the Delphi IDE, href references do not work

Similar to C# and the Visual Studio IDE, any href reference will not work in the IDE itself. See [WayBack] C# XML Documentation Website Link – Stack Overflow.

In Delphi, the same para element for paragraphs is used as in C#

Documented in the DevJet documentation, the para element works for paragraphs just like it does in the C# example at [WayBack] How to add a line break in C# .NET documentation – Stack Overflow.

Example

A few important tags:

   TParentedTest = class;
   IParentedTest = ISmartPointer<TParentedTest>;
   /// <summary>
   /// <para><see cref="Data.DataRecord|TJoinableDataRecord" /> that can be parented.</para>
   /// <para>The first time you obtain <see cref="UnitTest.Query.JoinHelper|TParentedTest.Parent" />, it will create one for you (so be careful not to recursively call <c>Parent</c>).</para>
   /// </summary>
   TParentedTest = class(TTest)
   strict private
     /// <summary>
     /// <para>Backing field of <see cref="UnitTest.Query.JoinHelper|TParentedTest.Parent" />.</para>
     /// <para>Referenced by <c>interface</c> <see cref="System|IInterface" /> instead of <c>class</c> <see cref="UnitTest.Query.JoinHelper|TParentedTest.Parent" /> as that prevents use-after-free access violations.</para>
     /// </summary>
     FParentInterface: IInterface;
   public
     class function CreateI(const Name: string): IParentedTest;
     /// <summary>
     /// <para>Ensures there is a parent by creating a new <see cref="UnitTest.Query.JoinHelper|TParentedTest" /> if there is none yet.that can be parented.</para>
     /// <para>Do not call recursively, as it will keep creating parents in an endless loop.</para>
     /// </summary>
     function Parent: TParentedTest;
     destructor Destroy; override;
   end;

–jeroen

Posted in Delphi, Development, Software Development | Leave a Comment »

“Delphi” “Data provider or other service returned an E_FAIL status” “NVARCHAR” – Google Search

Posted by jpluimers on 2021/06/22

For my link archive: [Archive.is] “Delphi” “Data provider or other service returned an E_FAIL status” “NVARCHAR” – Google Search

In this case it was while reading data. Cause yet unknown, as over time the error disappeared before it could be investigated further.

My initial thoughts:

  • local field size too small for actual content
  • character set mapping issue for certain locales

I recommended checking with [WayBack/Archive.is] bitbucket.org/jeroenp/wiert.me/src/default/Native/Delphi/Library/RTL/i18n/FormatSettingsHelperUnit.pas to see which locale ID and name the process was running under.

Related (but not the cause as this occurred while writing data, not reading): [WayBack] sql server 2008 – Why am I getting Data provider or other service returned an E_FAIL status? SQL Native Client – Stack Overflow

–jeroen

Posted in Delphi, Development, Software Development | Leave a Comment »

Too bad the Delphi IDE does not find the formatter settings when searching for “Margin” or “Right”, despite the setting is named “Right margin”

Posted by jpluimers on 2021/06/17

IDEs drive me nuts, including the Delphi IDE. Searching for Margin or Setting only reveals the margin and gutter setting, not the formatter settings.

So you have to remember this is in the “Formatter”, especially the “Line Breaks” node:

–jeroen

Posted in Delphi, Development, Software Development | Leave a Comment »

ODAC: calling SYS.DBMS_SESSION.SET_NLS failing with ORA-06550/PLS-00103

Posted by jpluimers on 2021/06/16

Do not call SYS.DBMS_SESSION.SET_NLS with an instance of the [Archive.is] TOraStoredProc Class, as under the hood, it will translate the call to this:

Class:Ora.TOraStoredProc;Component:sprSetNls;Flag:tfQPrepare,Text:Prepare: begin
  SYS.DBMS_SESSION.SET_NLS(:PARAM, :VALUE);
end;
:PARAM(VARCHAR[22],IN)='NLS_NUMERIC_CHARACTERS' 
:VALUE(VARCHAR[4],IN)=''.,''

The above is a translation of the bold portions in this call (note it contains the an instantiation of an [Archive.is] TOraSession Class as you need one; examples further down assume this session instance to exist):

var
  MainOraSession: TOraSession;
  DbmsSessionSetNlsOraStoredProc: TOraStoredProc;
begin
  MainOraSession := TOraSession.Create(Self);
  try
    MainOraSession.Name := 'MainOraSession';
    MainOraSession.Username := 'FOO';
    MainOraSession.Server := 'BAR';
    MainOraSession.LoginPrompt := False;
    MainOraSession.Options.UseOCI7 := True;
    MainOraSession.Open();
    DbmsSessionSetNlsOraStoredProc := TOraStoredProc.Create(Self);
    try
      DbmsSessionSetNlsOraStoredProc.Name := 'DbmsSessionSetNlsOraStoredProc';
      DbmsSessionSetNlsOraStoredProc.StoredProcName := 'SYS.DBMS_SESSION.SET_NLS';
      DbmsSessionSetNlsOraStoredProc.Session := MainOraSession;
      DbmsSessionSetNlsOraStoredProc.Debug := True;
      with DbmsSessionSetNlsOraStoredProc.ParamData.Add do 
      begin
        DataType := ftString;
        Name := 'PARAM';
        ParamType := ptInput;
        Value := nil;
      end;
      with DbmsSessionSetNlsOraStoredProc.ParamData.Add do
      begin
        DataType := ftString;
        Name := 'VALUE';
        ParamType := ptInput;
        Value := nil;
      end;
      DbmsSessionSetNlsOraStoredProc.ParamByName('PARAM').AsString := sParam;
      DbmsSessionSetNlsOraStoredProc.ParamByName('VALUE').AsString := sValue;
      DbmsSessionSetNlsOraStoredProc.Prepare();
      DbmsSessionSetNlsOraStoredProc.ExecProc();
    finally
      DbmsSessionSetNlsOraStoredProc.Free();
    end;
  finally
    MainOraSession();
  end;
end;

It will result in an Oracle error during the Prepare of the statement:

ORA-06550: line 2, column 36:
PLS-00103: Encountered the symbol ":" when expecting one of the following:

   ( - + case mod new not null 
   
   continue avg count current exists max min prior sql stddev
   sum variance execute forall merge time timestamp interval
   date  pipe
   

In stead, take your TOraPackage object and make a call like this:

var
  DbmsSessionOraPackage: TOraPackage;
begin
  DbmsSessionOraPackage := TOraPackage.Create(Self);
  try
    DbmsSessionOraPackage.Name := 'DbmsSessionOraPackage';
    DbmsSessionOraPackage.Debug := True;
    DbmsSessionOraPackage.Session := dbSession;
    DbmsSessionOraPackage.PackageName := 'SYS.DBMS_SESSION';

    DbmsSessionOraPackage.ExecProcEx('SET_NLS', ['PARAM', 'NLS_NUMERIC_CHARACTERS', 'VALUE', '''.,''']);
  finally
    DbmsSessionOraPackage.Free();
  end;
end;

This then results in this in the SQL monitoring (note quoting quotes is different in SQL than Delphi):

Class:Ora.TOraSQL;Component:;Flag:tfQExecute,Text:begin
  SYS.DBMS_SESSION.SET_NLS(:PARAM, :VALUE);
end;
:PARAM(VARCHAR[22],IN)='NLS_NUMERIC_CHARACTERS' 
:VALUE(VARCHAR[4],IN)=''.,''

instead of this:

Class:Ora.TOraStoredProc;Component:sprSetNls;Flag:tfQPrepare,Text:Prepare: begin
  SYS.DBMS_SESSION.SET_NLS(:PARAM, :VALUE);
end;
:PARAM(VARCHAR[22],IN)='NLS_NUMERIC_CHARACTERS' 
:VALUE(VARCHAR[4],IN)=''.,''

I am still a sort of baffled why this is a problem. But using the TOraPackage works.

One thing to remember is that an TOraSession instance does not allow you to get to the underlying TOCIConnection instance, which does allow setting NLS information directly; see for instance the old code at [WayBack] OraClasses.pas in xinhaining-dianjianyiqi-tongxunchengxu | source code search engine.

This is because the underlying connection can be both OCI and Direct depending on the TOraSession.Options.Direct value: [WayBack] About Connection.Ping method when Direct Mode – Devart Forums.

Other calls on SYS.DBMS_SESSION succeed

The odd thing is that single-parameter calls on SYS.DBMS_SESSION.SET_ROLE (which can be tricky, see [WayBack] Introducing Database Security for Application Developers) work fine, so no alternative (like a plain [WayBack] SET ROLE) is needed:

var
  DbmsSessionSetRoleOraStoredProc: TOraStoredProc;
begin
  DbmsSessionSetRoleOraStoredProc := TOraStoredProc.Create(Self);
  try
    DbmsSessionSetRoleOraStoredProc.Name := 'DbmsSessionSetRoleOraStoredProc';
    DbmsSessionSetRoleOraStoredProc.StoredProcName := 'SYS.DBMS_SESSION.SET_ROLE';
    DbmsSessionSetRoleOraStoredProc.Session := dbSession;
    with DbmsSessionSetRoleOraStoredProc.ParamData.Add do begin 
      DataType := ftString;
      Name := 'ROLE_CMD';
      ParamType := ptInput;
      Value := 'EXAMPLE';
    end;
    DbmsSessionSetRoleOraStoredProc.ParamByName('ROLE_CMD').AsString := 'EXAMPLEROLE';
    DbmsSessionSetRoleOraStoredProc.Prepare;
    DbmsSessionSetRoleOraStoredProc.ExecProc;
  finally
    DbmsSessionSetRoleOraStoredProc.Free();
  end;
end;

results in this log:

Class:Ora.TOraStoredProc;Component:StrdPSetRole;Flag:tfQExecute,Text:begin
  SYS.DBMS_SESSION.SET_ROLE(:ROLE_CMD);
end;
:ROLE_CMD(VARCHAR[10],IN)='EXAMPLEROLE'

Similar for SYS.DBMS_APPLICATION_INFO

Calling anything on DBMS_APPLICATION_INFO gives you an exception.

On the .NET side of DevArt, you can use

–jeroen

Posted in Database Development, Delphi, Development, OracleDB, Software Development | Leave a Comment »