Search code examples
c#delphidllpinvokedllimport

Catch exception thrown from Delphi DLL in C#


A problem I recently encountered is following.

I've a .DLL written in Delphi this DLL has a function Divide (which accepts two integers as parameter) and returns its value as it should.

function Divide( aFirstValue, aSecondValue : Integer ) : Double; stdcall;
begin
  result := aFirstValue / aSecondValue;
end;

Now if I use the following parameters '5, 0' then it throws a DivideByZeroException (which is correct :))

But when I call the same .DLL from C# it doesn't catch any exception at all.

[DllImport("DelphiDLL.DLL", EntryPoint = "Divide", SetLastError = true, CharSet = CharSet.Auto, ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
public static extern float Divide(Int32 a, Int32 b);

private void Button_Click_2(object sender, System.EventArgs e)
{
    try
    {
       TB.Text += "Divide(a,b) = ";
       float temp;
       temp = Divide(Convert.ToInt32(aTB.Text), Convert.ToInt32(bTB.Text));

       Console.WriteLine(Marshal.GetLastWin32Error());

       TB.Text += Convert.ToString(temp) + "\r\n";
    }
    catch (DivideByZeroException eMsg)
    {

    }
}

Solution

  • You cannot hope to catch that exception outside the DLL. One of the rules of this form of binary interop is that exceptions cannot be thrown across module boundaries.

    The solution is to fix the DLL. Catch the exception in the DLL and return an error code to indicate failure. Indeed you should protect all entry points against throwing an exception. Don't just catch zero divide exceptions, catch them all and convert them into error code return values.

    function CalcQuotient(a, b: Integer; out quotient: Double): Integer; stdcall;
    begin
      try
        quotient := a / b;
        Result := 0;// you'd use a constant with a sensible name rather than a magic value
      except
        on E: Exception do begin
          Result := GetErrorCode(E);
          // where GetErrorCode is your function that converts exceptions into error codes
        end;
      end;
    end;
    

    Some asides regarding your p/invoke usage:

    1. There's no need to specify CharSet when none of the arguments contains text.
    2. SetLastError = true is incorrect and should be removed. The function doesn't call SetLastError. Consequently the call to Marshal.GetLastWin32Error() is erroneous and should be removed.