C# and .NET,  Geeks,  Programming

Calling Delphi 6 DLL from c#.NET and blasted rounding issues

Recently I had the need to call a Delphi DLL from a .NET application. The delphi DLL would be performing a number of floating point arithmetic operations and returning results in as a string. In order to debug the delphi DLL another delphi application was written that uses this same interface and outputs the results.

The problem I started experiencing was that the results produced when calling from my c# application varied from those called from the delphi application. Both used the same DLL and both produced the same inputs.

I don’t have much in-depth knowledge about floating point numbers but I have read enough to know that most of the time you cannot display an exact number i.e. .3 is not actually .3. However I would have thought even with this where I called the DLL from would not matter.

Well apparently it does and it comes down to the control word. It appears that delphi and c# potentially use different control words which means that their method for handling floating point numbers may vary. So when calling the DLL from delphi, I used the delphi default control word. When calling it from c# I was using the c# control word. This was enough to cause variations in the outputs such that I produced the wrong answers.

I couldn’t end up figuring this out, but a helpful chap on StackOverflow – Calling Delphi 6 DLL kindly explained it as such:

“The source of your differing behaviour is presumably down to the 8087 control word that controls the floating point register. When your DLL executes, called from C#, the control word will differ from the default Delphi setting. Call Set8087CW($1372) at the entry point to your DLL to use the default Delphi control word. Remember to restore it before you return from the DLL.”

So, to further expand here is the code I used that ended up resolving my head-aches and ending hours of frustration.

First I define the Delphi exposed API method in my c# code.

[DllImport("OvrFileImport.dll",
               CallingConvention = CallingConvention.StdCall,
               CharSet = CharSet.Ansi)]

public static extern bool
        ConvertString(
        string fileContents, 
        string fileExt, 
        ref string refputStringBuffer,
        ref int outputStringBufferSize, 
        ref string errorMsgBuffer, 
        ref int errorMsgBufferSize);

In order to use this you have to call the method twice. The first time is to determine the buffer size of the output string. Of course if the string is always the same length then you do not have to call it twice and you can just make the one call ensuring you allocate the buffer correctly before hand.

retCode = ConvertString(
                _inputData,
                fileExt,
                ref outputStringBuffer,
                ref outputStringBufferSize,
                ref errorMsgBuffer,
                ref errorMsgBufferSize);

outputStringBuffer = new String('\x00', outputStringBufferSize);
errorMsgBuffer = new String('\x00', errorMsgBufferSize);

retCode = ConvertString(
                _inputData,
                fileExt,
                ref outputStringBuffer,
                ref outputStringBufferSize,
                ref errorMsgBuffer,
                ref errorMsgBufferSize);

Now for the important part. The Delphi code. Here’s just a snippet as the actual method is quite large in and of itself.

interface
  uses
   SysUtils,

  function ConvertString(fileContents: PChar; fileExt: PChar; var outputStringBuffer:
      PChar; var outputStringBufferSize: Integer; var errorMsgBuffer: PChar; var
      errorMsgBufferSize: Integer): WordBool; stdcall; export;

......

function ConvertString(fileContents: PChar; fileExt: PChar; var outputStringBuffer:
    PChar; var outputStringBufferSize: Integer; var errorMsgBuffer: PChar; var
    errorMsgBufferSize: Integer): WordBool;
var
  cw : Word;
begin
  cw := Get8087CW;   // get the current control word so we can set it back
  Set8087CW($1372);  // make sure it's the delphi default being used

  // a whole bunch of stuff

  finally
    // Important!  Make sure we set it back to the previous control word that our caller is using
    Set8087CW(cw);
  end;
end;

Hope this helps anyone else who ever had these issues. It sure did cause me some issues!!!

Leave a Reply