Archive

Archive for May, 2013

WinAPI temp filename

May 29, 2013 Leave a comment

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

May 27, 2013 Leave a comment

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

May 21, 2013 Leave a comment

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.

Easier on the eyes

Easier on the eyes

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

May 7, 2013 Leave a comment

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:

  1. 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.
  2. 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.
  3. 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

May 2, 2013 Leave a comment
SPARTA

SPARTA

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

NextSys

NextSys

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 😉