0

I have the need to write a DLL but this is my first time (there's always one) and I have found a solution reading the documentaion. I ended up with this code:

library DLLFrazioni;

uses
  System.SysUtils,
  System.Classes,
  Fractions in 'Fractions.pas';

{$R *.res}

function getFraction(a: integer; b: integer): PChar; stdcall; overload;
var f: TFraction;
begin

 f := TFraction.Create(a, b);
 try
  Result := PChar(f.getFraction);
 except
  Result := PChar('NaN');
 end;

end;

function getFraction(a: PChar): PChar; stdcall; overload;
var f: TFraction;
begin

 f := TFraction.Create(a);
 try
  Result := PChar(f.getFraction);
 except
  Result := PChar('NaN');
 end;

end;

exports
 getFraction(a: integer; b: integer),
 getFraction(a: Pchar);

begin
end.

There is a class called TFraction in Fraction.pas and it has this implementation (if needed):

type
 TFraction = class
  private
   number: double;
   num, den: integer;
   fraction: string;
   function hcf(x: integer; y: integer): integer;
  public
   //input num+den -> result is a fraction num/den
   constructor Create(numerator: integer; denominator: integer); overload;
   //input string 'num/den' -> result is a reduced num/den
   constructor Create(value: PChar); overload;
   function getFraction: string;
 end;

Everything here is pretty easy.


I have to be able to load this dll with Delphi and C++ (Visual Studio) but I have a doubt that I haven't solved with google. As you can see I have declared another unit that contains the class so I can have the two things separated.

I am using stdcall as usual in delphi DLLs. I have the following questions:

  1. I have to create an object (f: TFraction) because I need to get the return result from getFraction. Do I have to surround it with the usual try-finally statement? I thought that a try-except fits better because I want to avoid exceptions at runtime.
  2. If I removed the try-except of course an exception can occur. In this case when I will call the function from my Delphi/C++ program I can handle it. But is that safe? Can I allow that a dll raises an exception?

2 Answers 2

5

An except block serves an entirely different purpose from a finally block. It's never a matter of choosing one instead of the other. Use whichever one meets the needs. If you need both, then use both. An except block is for handling errors. A finally block is for protecting resources.

Your usage of an except block is correct to handle the case of getFraction failing.

You should include a finally block to protect the resource that you allocated, namely the TFraction object. You're not freeing the objects at all now, so you have memory leaks.

Do not allow an exception to escape a DLL function. You cannot assume that the caller knows how to deal with a thrown Delphi object.

Writing DLLs is a case where it's really helpful for you to have experience writing C and using the Windows API. If you write your DLLs to follow the same patterns that you see in the Windows API, then you're on a good foundation. You'll notice that Windows API functions never raise exceptions. They always return a status value, possibly with an error code.


You have other problems in your code. In particular, you're returning a pointer to a string that gets released as soon as your DLL function terminates, so the pointer is stale. Again, following the model of the Windows API is helpful. The API almost never returns a string. Rather, API functions receive a buffer pointer and a length, and then fill the caller-provided buffer. APIs that return a string will usually allocate the memory using a documented API, and then specify what memory-management function the caller should use to release the memory later.

In your case, you're returning a pointer to memory that you don't manage. The compiler manages it for you, and the compiler cannot see that your function's caller still wants to use that memory, so the compiler inserts code to free your string.

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you super Rob, now it's clear. Yes I think that I will return codes like -1, -2, -3 and I'll put a readme.txt, so the code in the dll will never raise exceptions.
so shouldn't I return a PChar? Should I return instead just the address of the first item of the char array? Thank you
If you're returning codes like -1, -2, -3, then you cannot also return a PChar because you can only return one thing. Consider a function like GetWindowText. It receives a buffer pointer and then writes into that buffer. It returns a number indicating success or failure.
I guess I've understood. I will study this more now, but from what you say in general it's good practise to return an integer as status. The function result will be "loaded" into a variable (= a function parameter) that is a buffer. Am I correct?
Basically yes. Keep things easy and use primitive types. If you return complex objects you risk to lose the compatibility and make a delphi-only dll. Return a int or a bool as status and operate on the parameters (see my answer). Oh an use properly stdcall/cdecl.
|
0

Your code compiles and runs but as Rob said you should fill a buffer with the result. In general when you deal with DLLs (like in your case) they must be loaded with different languages so it's better if you return simple types like integers or doubles.

This code will give you an idea:

//STATUS: 0 = ok | 1 = error
function getFraction(a, b: integer; Buffer: PChar): integer; stdcall;
var f: TFraction;
    l: PChar;
begin

 f := TFraction.Create(a, b);
 try

  l := PChar(f.getFraction);
  try
   StrLCopy(Buffer, l, SizeOf(l));
   Result := 0; //ok
  except
   //something to report an error
   Result := 1;
  end;

 finally
  f.Free;
 end;

end; 

Here I return an integer (but also a bool could have been good) that indicates the success of the operation. In this case you see that having the try ... except is useful.

The result is stored in a buffer and I have used the apposite function StrLCopy. Here I have used the SizeOf operator to get the value of the MaxLen. Instead of this, you could have used a parameter in the function called bufLen for example.

function getFraction(a, b: integer; Buffer: PChar; bufLen: integer): integer; stdcall;
var f: TFraction;
    l: PChar;
begin

 f := TFraction.Create(a, b);
 try

  l := PChar(f.getFraction);
  try
   StrLCopy(Buffer, l, bufLen);
   Result := 0; //ok
  except
   //something to report an error
   Result := 1;
  end;

 finally
  f.Free;
 end;

end;

In this second version you have more control and you can determine the dimension of the buffer. Please note that the StrLCopy will use only the amount of space indicated by bufLen. If the size of the l: PChar is bigger than bufLen, the part in excess won't be copied. In the main program you can use the function as follows:

  SetLength(A, 10);
  k := getFraction(10,25, PChar(A));

  if k = 0 then
   Writeln(a)
  else
   Writeln('fail');

This code is the console application and A is a string. You must declare a string variable and convert it to a PChar because SetLength accepts as first parameter passed by reference (var keyword) a string or dynamic array.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.