Archive
WinAPI temp filename
IIK! Found a couple of really, really nasty temp-file procedures in a customer app. Here is one that does it “by the book” so to speak. I havent tested it under Delphi-7 or below but it should compile nicely under freepascal as well (give or take a @ prefix).
unit jlTempFile; interface //########################################################################## // Class: TTempFile // Author: Jon Lennart Aasenden // Purpose: General purpose class for obtaining filenames for temporary use // through the Windows API. // // Note: Dont create instances of this class. Simply use the class // functions directly, e.g: TTempfile.getTempStream(); // // Sources: http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992%28v=vs.85%29.aspx // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364991%28v=vs.85%29.aspx // //########################################################################## uses Winapi.Windows, System.SysUtils, System.Classes; type TTempFile = Class public class function getTempFileName:String; class function getTempDirectory:String; class function getTempStream(out aStream:TFileStream):Boolean; End; implementation class function TTempFile.getTempStream(out aStream:TFileStream):Boolean; var mName: String; Begin result:=False; aStream:=NIL; mName:=getTempFileName; try aStream:=TFileStream.Create(mName,fmCreate); except on e: exception do Raise Exception.CreateFmt( 'Failed to create temporary file [%s]' + #13 + 'System error: "%s"',[mName,e.Message]); end; result:=True; end; class function TTempFile.getTempDirectory:String; var mtempPath: String; mtempFile: String; mBufLen: Integer; mLen: Integer; mMoniker: String; Begin (* Init result *) setlength(result,0); (* Allocate normal size path buffer *) SetLength(mtempPath, MAX_PATH); mBufLen:=MAX_PATH; (* obtain temp path from WINAPI *) mLen:=WinApi.Windows.GetTempPath(mBufLen, PChar(@mtempPath[1])); if mLen>0 then Begin (* MSDN: If the return value is greater than nBufferLength, the return value is the length, in TCHARs, of the buffer required to hold the path. http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992%28v=vs.85%29.aspx *) If (mLen>mBufLen) then Begin (* re-allocate & get the path *) SetLength(mtempPath,mLen); mLen:=WinApi.Windows.GetTempPath(mLen, PChar(@mtempPath[1])); end; result:=Copy(mTempPath,1,mLen); end else Raise Exception.Create(SysErrorMessage(getLastError)); end; class Function TTempFile.GetTempFileName:String; var mtempPath: String; mtempFile: String; mBufLen: Integer; mLen: Integer; mMoniker: String; Begin (* Init result *) setlength(result,0); (* Allocate normal size path buffer *) SetLength(mtempPath, MAX_PATH); mBufLen:=MAX_PATH; (* obtain temp path from WINAPI *) mLen:=WinApi.Windows.GetTempPath(mBufLen, PChar(@mtempPath[1])); if mLen>0 then Begin (* MSDN: If the return value is greater than nBufferLength, the return value is the length, in TCHARs, of the buffer required to hold the path. http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992%28v=vs.85%29.aspx *) If (mLen>mBufLen) then Begin SetLength(mtempPath,mLen); mLen:=WinApi.Windows.GetTempPath(mLen, PChar(@mtempPath[1])); end; (* setup prefix and memory for filename, which is not the same as the path we obtained above *) mMoniker:='JL_'; SetLength(mtempFile, MAX_PATH + mLen); (* Obtain the filename, include the path. http://msdn.microsoft.com/en-us/library/windows/desktop/aa364991%28v=vs.85%29.aspx *) mLen:=WinApi.Windows.GetTempFileName(PChar(@mtempPath[1]), PChar(@mMoniker[1]), 0, PChar(@mtempFile[1])); (* Function returns 0 (zero) if it fails, check and return *) if mLen<>0 then Result:=trim(mtempFile) else Raise Exception.Create(SysErrorMessage(getLastError)); end else Raise Exception.Create(SysErrorMessage(getLastError)); end; end.
Bit management under Delphi
I absolutely hate bit management under Delphi. So I wrote this class to solve the problem once and for all. Not just fidling with bits inside a fixed datatype (like a byte or a word) but for memory allocations of any size. It should work fine under Lazarus/freepascal as well.
Here you go
unit bitbuffer; interface //########################################################################## // Class: TBitBuffer // Author: Jon Lennart Aasenden // Purpose: // -General purpose class for manupulating large buffers on bit level // -Possible to store bitbuffer to stream // -Class methods for general bit manipulation on memory segements // larger than fixed datatypes // -Possible to search buffer for unset bits // -Keeps track of unset bits (perfect for database record mapping) //########################################################################## uses sysutils, classes; type EBitBuffer = Class(Exception); TBitOffsetArray = packed array of system.NativeUInt; TBitBuffer = Class(TObject) Private FData: PByte; FDataLng: NativeInt; FDataLen: NativeInt; FBitsMax: NativeUInt; FReadyByte: NativeUInt; FAddr: PByte; BitOfs: 0..255; FByte: Byte; Function GetByte(Const Index:NativeInt):Byte; Procedure SetByte(Const Index:NativeInt;Const Value:Byte); Function GetBit(Const Index:NativeUInt):Boolean; Procedure SetBit(Const Index:NativeUInt;Const Value:Boolean); Public Property Data:PByte read FData; Property Size:NativeInt read FDataLen; Property Count:NativeUInt read FBitsMax; Property Bytes[Const Index:NativeInt]:Byte Read GetByte write SetByte; Property Bits[Const Index:NativeUInt]:Boolean Read GetBit write SetBit;default; Procedure Allocate(MaxBits:NativeUInt); Procedure Release; Function Empty:Boolean; Procedure Zero; class function BitsOf(Const aBytes:NativeInt):NativeUInt; class function BytesOf(aBits:NativeUInt):NativeInt; class function BitsSetInByte(Const Value:Byte):NativeInt; class Function BitGet(Const Index:NativeInt;Const Buffer):Boolean; class procedure BitSet(Const Index:NativeInt;var Buffer; Const Value:Boolean); procedure SaveToStream(Const stream:TStream);virtual; Procedure LoadFromStream(Const stream:TStream);virtual; Procedure SetBitRange(First,Last:NativeUInt; Const Bitvalue:Boolean); Procedure SetBits(Const Value:TBitOffsetArray; Const BitValue:Boolean); Function FindIdleBit(var Value:NativeUInt; Const FromStart:Boolean=False):Boolean; Destructor Destroy;Override; End; implementation const ERR_BitBuffer_InvalidBitIndex = 'Invalid bit index, expected 0..%d not %d'; ERR_BitBuffer_InvalidByteIndex = 'Invalid byte index, expected 0..%d not %d'; ERR_BitBuffer_BitBufferEmpty = 'Bitbuffer is empty error'; ERR_ERR_BitBuffer_INVALIDOFFSET = 'Invalid bit offset, expected 0..%d, not %d'; CNT_BitBuffer_ByteTable: array [0..255] of NativeInt = (0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8); //########################################################################## // TBitBuffer //########################################################################## Destructor TBitBuffer.Destroy; Begin If not Empty then Release; inherited; end; class function TBitBuffer.BitsOf(Const aBytes:NativeInt):NativeUInt; Begin result:=aBytes shl 3; end; class function TBitBuffer.BytesOf(aBits:NativeUInt):NativeInt; Function QToNearest(Const Value,Factor:NativeInt):NativeInt; var FTemp: Integer; Begin Result:=Value; FTemp:=Value mod Factor; If FTemp>0 then inc(Result,Factor - FTemp); end; Begin aBits:=QToNearest(aBits,8); result:=aBits shr 3; if (result shl 3) < aBits then inc(result); end; class function TBitBuffer.BitsSetInByte(Const Value:Byte):NativeInt; begin result:=CNT_BitBuffer_ByteTable[Value]; end; class Function TBitBuffer.BitGet(Const Index:NativeInt;Const Buffer):Boolean; var FValue: Byte; FAddr: PByte; BitOfs: 0..255; Begin If Index>=0 then Begin BitOfs:=Index mod 8; FAddr:=PTR(NativeInt(@Buffer) + Index shr 3); FValue:=FAddr^; Result:=(FValue and (1 shl (BitOfs mod 8))) <> 0; end else Raise EBitBuffer.CreateFmt (ERR_ERR_BitBuffer_INVALIDOFFSET,[maxint-1,index]); end; class Procedure TBitBuffer.BitSet(Const Index:NativeInt; var Buffer;Const Value:Boolean); var FByte: Byte; FAddr: PByte; BitOfs: 0..255; FCurrent: Boolean; Begin If Index>=0 then Begin FAddr:=PTR(NativeInt(@Buffer) + Index shr 3); FByte:=FAddr^; BitOfs := Index mod 8; FCurrent:=(FByte and (1 shl (BitOfs mod 8))) <> 0; case value of true: begin (* set bit if not already set *) If FCurrent=False then FByte:=(FByte or (1 shl (BitOfs mod 8))); FAddr^:=FByte; end; false: begin (* clear bit if already set *) If FCurrent then FByte:=(FByte and not (1 shl (BitOfs mod 8))); FAddr^:=FByte; end; end; end else Raise EBitBuffer.CreateFmt (ERR_ERR_BitBuffer_INVALIDOFFSET,[maxint-1,index]); end; procedure TBitBuffer.SaveToStream(Const stream:TStream); var mWriter: TWriter; begin mWriter:=TWriter.Create(stream,1024); try mWriter.WriteInteger(FDataLen); mWriter.Write(FData^,FDataLen); finally mwriter.FlushBuffer; mWriter.Free; end; end; Procedure TBitBuffer.LoadFromStream(Const stream:TStream); var mReader: TReader; mlen: NativeInt; Begin Release; mReader:=TReader.Create(stream,1024); try mLen:=mReader.ReadInteger; if mLen>0 then begin Allocate(BitsOf(mLen)); mReader.Read(FData^,mLen); end; finally mReader.Free; end; end; Function TBitBuffer.Empty:Boolean; Begin result:=FData=NIL; end; Function TBitBuffer.GetByte(Const Index:NativeInt):Byte; Begin If FData<>NIL then Begin If (index>=0) and (Index<FDataLen) then result:=PByte(PTR(FDataLng + index))^ else Raise EBitBuffer.CreateFmt (ERR_BitBuffer_InvalidByteIndex,[FDataLen-1,index]); end else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; Procedure TBitBuffer.SetByte(Const Index:NativeInt;Const Value:Byte); Begin If FData<>NIL then Begin If (index>=0) and (Index<FDataLen) then PByte(PTR(FDataLng + index))^:=Value else Raise EBitBuffer.CreateFmt (ERR_BitBuffer_InvalidByteIndex,[FDataLen-1,index]); end else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; Procedure TBitBuffer.SetBitRange(First,Last:NativeUInt; Const Bitvalue:Boolean); var x: NativeUInt; FLongs: NativeInt; FSingles: NativeInt; FCount: NativeUInt; Procedure QSwap(Var Primary,Secondary:NativeUInt); var FTemp: NativeUInt; Begin FTemp:=Primary; Primary:=Secondary; Secondary:=FTemp; end; Function QDiff(Const Primary,Secondary:NativeUInt; Const Exclusive:Boolean=False):NativeUInt; Begin If Primary<>Secondary then Begin If Primary>Secondary then result:=Primary-Secondary else result:=Secondary-Primary; If Exclusive then If (Primary<1) or (Secondary<1) then inc(result); If result<0 then result:=abs(result); end else result:=0; end; Begin If FData<>NIL then Begin If First<FBitsMax then Begin If Last<FBitsMax then Begin (* Conditional swap *) If First>Last then QSwap(First,Last); (* get totals, take ZERO into account *) FCount:=QDiff(First,Last,True); (* use refactoring & loop reduction *) FLongs:=NativeInt(FCount shr 3); x:=First; while FLongs>0 do Begin SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); SetBit(x,Bitvalue);inc(x); dec(FLongs); end; (* process singles *) FSingles:=NativeInt(FCount mod 8); while FSingles>0 do Begin SetBit(x,Bitvalue);inc(x); dec(FSingles); end; end else Begin If First=Last then SetBit(First,True) else Raise EBitBuffer.CreateFmt (ERR_BitBuffer_InvalidBitIndex,[FBitsMax,Last]); end; end else Raise EBitBuffer.CreateFmt(ERR_BitBuffer_InvalidBitIndex, [FBitsMax,First]); end else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; Procedure TBitBuffer.SetBits(Const Value:TBitOffsetArray; Const BitValue:Boolean); var x: NativeInt; FCount: NativeInt; Begin If FData<>NIL then Begin FCount:=length(Value); If FCount>0 then Begin for x:=low(Value) to High(Value) do SetBit(Value[x],BitValue); end; end else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; Function TBitBuffer.FindIdleBit(var Value:NativeUInt; Const FromStart:Boolean=False):Boolean; var FOffset: NativeUInt; FBit: NativeUInt; FAddr: PByte; x: NativeInt; Begin result:=FData<>NIL; if result then Begin (* Initialize *) FAddr:=FData; FOffset:=0; If FromStart then FReadyByte:=0; If FReadyByte<1 then Begin (* find byte with idle bit *) While FOffset<NativeUInt(FDataLen) do Begin If BitsSetInByte(FAddr^)=8 then Begin inc(FOffset); inc(FAddr); end else break; end; end else inc(FOffset,FReadyByte); (* Last byte exhausted? *) result:=FOffset<NativeUInt(FDataLen); If result then Begin (* convert to bit index *) FBit:=FOffset shl 3; (* scan byte with free bit in it *) for x:=1 to 8 do Begin If not GetBit(FBit) then Begin Value:=FBit; (* more than 1 bit available in byte? remember that *) FAddr:=FData; inc(FAddr,FOffset); If BitsSetInByte(FAddr^)>7 then FReadyByte:=0 else FReadyByte:=FOffset; Break; end; inc(FBit); end; end; end; end; Function TBitBuffer.GetBit(Const Index:NativeUInt):Boolean; begin If FData<>NIL then Begin If index<FBitsMax then Begin FAddr:=PTR(FDataLng + NativeInt(index shr 3)); BitOfs:=Index mod 8; Result:=(FAddr^ and (1 shl (BitOfs mod 8))) <> 0; end else Raise EBitBuffer.CreateFmt (ERR_BitBuffer_InvalidBitIndex,[Count-1,index]); end else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; Procedure TBitBuffer.SetBit(Const Index:NativeUInt;Const Value:Boolean); begin If FData<>NIL then Begin If index<FBitsMax then Begin FByte:=PByte(FDataLng + NativeInt(index shr 3))^; BitOfs:=Index mod 8; If Value then Begin (* set bit if not already set *) If (FByte and (1 shl (BitOfs mod 8)))=0 then Begin FByte:=(FByte or (1 shl (BitOfs mod 8))); PByte(FDataLng + NativeInt(index shr 3))^:=FByte; (* if this was the "ready" byte, then reset it to zero *) If (Index shr 3=FReadyByte) and (FReadyByte>0) then Begin If BitsSetInByte(FByte)>7 then FReadyByte:=0; end; end; end else Begin (* clear bit if not already clear *) If (FByte and (1 shl (BitOfs mod 8)))<>0 then Begin FByte:=(FByte and not (1 shl (BitOfs mod 8))); PByte(FDataLng + NativeInt(index shr 3))^:=FByte; (* remember this byte pos *) FReadyByte:=Index shr 3; end; end; end else Raise EBitBuffer.CreateFmt (ERR_BitBuffer_InvalidBitIndex,[Count-1,index]); end else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; Procedure TBitBuffer.Allocate(MaxBits:NativeUInt); Begin (* release buffer if not empty *) If FData<>NIL then Release; If Maxbits>0 then Begin (* Allocate new buffer *) try FReadyByte:=0; FDataLen:=BytesOf(maxBits); FData:=AllocMem(FDataLen); FDataLng:=NativeUInt(FData); FBitsMax:=BitsOf(FDataLen); except on e: exception do Begin FData:=NIL; FDataLen:=0; FBitsMax:=0; FDataLng:=0; Raise; end; end; end; end; Procedure TBitBuffer.Release; Begin If FData<>NIL then Begin try FreeMem(FData); finally FReadyByte:=0; FData:=NIL; FDataLen:=0; FBitsMax:=0; FDataLng:=0; end; end; end; Procedure TBitBuffer.Zero; Begin If FData<>NIL then Fillchar(FData^,FDataLen,byte(0)) else Raise EBitBuffer.Create(ERR_BitBuffer_BitBufferEmpty); end; end.
Better status
If you are like me, then you probably use a memo when you want to have some onscreen feedback or logging. This works fine but it can be made to look prettier using a richEdit control instead. Here is a small procedure i slapped together which allows you to have Bold formated sections and tab-indent for text. Small but effective!
The code should be easy enough to expand. You may want to do “live” formating of words by hooking into the onKeyPress event, or add support for better colors.
procedure TForm1.Log(aText:string; Const Bold:Boolean=False; const aTab:Integer=0); var mLen: Integer; mStart: Integer; TextPos: lResult; Begin redtStatus.Lines.BeginUpdate; try mStart:=Length(redtStatus.Text); mLen:=Length(aText); redtStatus.SelStart:=mStart; redtStatus.SelLength:=0; redtStatus.Paragraph.FirstIndent:=aTab; case Bold of False: Begin redtStatus.SelAttributes.Color:=redtStatus.Font.Color; redtStatus.SelAttributes.Style:=[]; redtStatus.SelAttributes.Name:='Courier new'; redtStatus.SelAttributes.Size:=8; end; true: Begin redtStatus.SelAttributes.Color:=clBlue; redtStatus.SelAttributes.Style:=[fsBold,fsUnderline]; redtStatus.SelAttributes.Name:=redtStatus.Font.Name; redtStatus.SelAttributes.Size:=11; end; end; redtStatus.SelText:=aText; redtStatus.Lines.Add(''); redtStatus.selstart:= length(redtStatus.Text); redtStatus.sellength:= 0; SendMessage(redtStatus.Handle, WM_VSCROLL, MakeWParam(SB_THUMBPOSITION, $FFFF), 0); SendMessage(redtStatus.Handle, EM_SETSEL, TextPos, TextPos); finally redtStatus.Lines.EndUpdate; end; end;
Using .net binaries from Delphi
Yesterday I had an interesting challenge at work, namely to figure out how to use .net binaries from Delphi. To make a long story short – the company I work for interfaces with several third-party systems, and one of them just happens to be written for the .net framework. The docs told us that the binaries are exposed as COM objects and can thus be used via scripting (powershell), visual basic script – and well, anything that can handle IDispatch more or less. This should be a breeze right?
Typelibraries?
Having installed the server and the client libraries on my test machine I started to look for a type library. I have never imported or invoked .net from Delphi (but I have used “native” dll’s from C#) so naturally I was hoping we would find something in the binaries folder. But no such luck
In short: I could create the COM objects via createOleObject(), but that sort of leaves you blind. And the docs didn’t really explain the methods as much as it focused on the XML parameter syntax. But getting a response via com was a start. Even an error message is positive when creating a bridge. I pieced together a small wrapper class that simply created an instance of the COM object in the constructor, with X number of methods that just used IDispatch to invoke the methods we knew about. But what I really wanted was a typelibrary and better control over how Delphi deals with scenarios like this; scenarios that will no doubt become more and more frequent in the years to come.
Some work later and it turns out you can export a typelibrary manually providing the classes and interfaces you need have been signed as “visible” to COM. This is done by adding: using System.Runtime.InteropServices; to the .net uses section. You also have to mark the class you wish to export as such:
[ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] public class SomeClass : ISomeclass { // }
The ISomeClass is just an interface that you define above the class, which just mirrors the methods you want to export. It is recommended that you mark each method you want to expose with the [comvisible(true)] signature. Hence the “classinterfacetype.none” in the header (as opposed to exposing the whole shabam).
Generating a typelibrary
The solution came in the form of the dark side of the… eh, I mean that wonderful utility that is Visual Studio. Once we knew the binaries did indeed export the methods/interfaces we wanted – we started up the visual studio command-prompt (remember to run as admin). This shell has a few extra commands, one of them being: tlbexp, which takes a .net binary and generates a Delphi compatible type library. You can read more about this utility here: http://msdn.microsoft.com/nb-no/library/hfzzah2c%28v=vs.80%29.aspx
With a typelibrary now in place I fired up Delphi Xp4, imported the typelibrary (the import wizard crashed 3 times before it worked, so be warned) and generated a unit.
Et voila! We finally had a clean and direct way of speaking to .net 🙂
XML mapping
A lot of .net libraries are based on object mapping. In short, all parameters are strings and you are expected to push in a huge wad of XML – which on the .net side is turned into an object. Well Delphi can do that to! Once I had a full overview of the XML saved to disk, I clicked on file -> new -> other -> XML. Then imported the XML files and generated the object mapping classes.
Delphi really have a lot of hidden gems. The XML mapping alone is a huge timesaver and it’s a shame so few people use it – and even worse that Embarcadero dont promote it more in the IDE (and perhaps give it a facelift and tlbexp support?).
Stuff to keep in mind when working with .net systems:
- Object mapping is a must, play around with the Delphi XML mapper, it really is a fantastic once you get the hang of it. A lot of .net systems (especially database services) use object mapping, so you better get used to it.
- Each build is unique (!) Even if the .net code has not changed, the interfaces can have new GUID signatures. So you must remember to export and re-generate the typelibrary whenever you update. Unless of course the coder have manually set the GUID, in which case it requires less maintainance.
- Install visual studio, either the free version or a trial (or buy it, it’s a bargain compared to Delphi). It will make your life so much easier.
Leaving Optimale Systemer
As of today I am no longer employed by Optimale Systemer AS. After much thinking I decided it was time to leave, although leaving Smart Mobile Studio behind was the hardest thing I have ever done. It was my baby and something I loved working on. I will sorely miss everyone at the company and everyone on the OP4JS council, but I feel my work is done. I set out to bring object pascal to the new world of HTML5 – and despite all the “it can’t be done” bullshit, well.. me and Eric did it. An especially long nose (with assorted french ornaments from Ludvig the fifth) to Embarcadero.
This really was your job Embarcadero. I cant believe you people are actually selling html5 builder and getting away with it. I know there is a lot of work running a company with your customer base – but not delivering a Delphi to html5 compiler in all these years, and with 10000 times our resources is just.. well plain ridicules if you ask me.
On with the show
Today was my first day at NextSys AS, a dedicated Delphi and object pascal company situated in Lier, outside of Oslo, Norway. Our flagship product is NextSys Dental Office which is being used by the national health service to manage every aspect of insurance and dental plans.
It’s been a very cool day over at NextSys. We visited the local officials house for a meeting, which incidentally took place in my old school (Næringsakademiet, Tønsberg). This was the place where I used to play around with Interbase/dBase waaaay back in 1997. So imagine the deja vu when we started the meeting with… firebird (!)
The same building, the same (well, sort of) software, but still awesome!
Also got my taste of the latest Delphi; although I did spend an hour checking if we could bridge firebird to Smart Mobile Studio with a little binary help. I’ll get back to you on that one 😉
Last words: If you want extraordinary stuff, you need extraordinary people. If you want ordinary solutions to everyday problems, get someone else. But to parallel a bad movie – once Achilles have taken the beach – it’s up to the rest to build on it. I’m thankful for my role in bringing object pascal to the browser and html5 – now it’s up to you to make it stay there.
If you build it, they will come 😉
You must be logged in to post a comment.