Delphi Programming
Register
(undo vandalism)
(18 intermediate revisions by 11 users not shown)
Line 1: Line 1:
 
== Introduction ==
 
== Introduction ==
   
[[Delphi]] allows not only to create applications but also DLLs. A DLL (short for dynamic link library) is a special kind of executable that cannot be started by itself but exports procedures and functions (called "Entry Points") for other executables to call.
+
[[Delphi]] allows not only to create applications but also DLL files. A DLL file (short for dynamic link library) is a special kind of executable that cannot be started by itself, but exports procedures and functions (called "Entry Points") for other executables to call.
   
== Creating a DLL with Delphi ==
+
== Creating a DLL file with Delphi ==
   
Creating a DLL with Delphi consists of two steps:
+
Creating a DLL file with Delphi consists of two steps:
   
 
1. Creating a library project
 
1. Creating a library project
Line 12: Line 12:
 
=== Creating a library project ===
 
=== Creating a library project ===
   
Delphi has got a special kind of project for a DLL. To create one you use the File -> New -> Other menu entry to open a dialog where you can select the project type you want to create. There is one called "DLL Wizard", that's the one we need here.
+
Delphi has got a special kind of project for a DLL file. To create one you use the File -> New -> Other menu entry to open a dialog where you can select the project type you want to create. There is one called "DLL Wizard"; that's the one we need here.
   
 
[[Image:File_New_Other_Dialog.PNG]]
 
[[Image:File_New_Other_Dialog.PNG]]
   
Select it and press OK and Delphi will automatically create a new DLL project for you. The source code looks like this:
+
Select it and press OK and Delphi will automatically create a new DLL file project for you. The source code looks like this:
   
 
library Project1;
 
library Project1;
Line 39: Line 39:
 
=== Exporting functions from a library ===
 
=== Exporting functions from a library ===
   
Now lets export some function from our new library. Lets start with something very simple that does not involve complex data structures or a GUI: A function for adding two integers and returning their sum:
+
Now let's export some function from our new library. Let's start with something very simple that does not involve complex data structures or a GUI: A function for adding two integers and returning their sum:
   
function AddIntegers(_a, _b: integer): integer;
+
function AddIntegers(const _a, _b: integer): integer;
 
begin
 
begin
 
Result := _a + _b;
 
Result := _a + _b;
 
end;
 
end;
   
Yes, that's not very exciting, is it? But it is a function that can be called from any windows program that wants to do it.
+
Yes, that's not very exciting, is it? But it is a function that can be called from any Windows program that wants to do it.
   
 
==== '''Side note about calling conventions''' ====
 
==== '''Side note about calling conventions''' ====
   
'''Now''', this is getting rather technical and confusing: Programming languages have evolved over the years to pass parameters and return values from and to function calls in different ways. Originally that was only inside one language and using only one compiler so the programmer just wrote the code and the compiler took care of how the functions were called. Then came DLLs and all of a sudden code written in one language using one compiler could be called from other code written in a different language or only using a different compiler. It turned out that this didn't work, so the compiler makers came up with the notion of ''calling conventions''. We will use one called ''StdCall'' here since it allows for most other languages and compilers to call our DLL.
+
'''Now''', this is getting rather technical and confusing: Programming languages have evolved over the years to pass parameters and return values from and to function calls in different ways. Originally that was only inside one language and using only one compiler so the programmer just wrote the code and the compiler took care of how the functions were called. Then came DLL files and all of a sudden code written in one language using one compiler could be called from other code written in a different language or only using a different compiler. It turned out that this didn't work, so the compiler makers came up with the notion of ''calling conventions''. We will use one called ''StdCall'' here since it allows for most other languages and compilers to call our DLL file.
(One particular example is Visual Basic (from before dotNET), which supports ''only'' the StdCall calling convention.)
+
(One particular example is Visual Basic (from before .NET), which supports ''only'' the StdCall calling convention.)
   
 
==== Exporting the function ====
 
==== Exporting the function ====
Line 57: Line 57:
 
So let's add a calling convention to our function:
 
So let's add a calling convention to our function:
   
function AddIntegers(_a, _b: integer): integer; stdcall;
+
function AddIntegers(const _a, _b: integer): integer; stdcall;
 
begin
 
begin
 
Result := _a + _b;
 
Result := _a + _b;
 
end;
 
end;
   
and export it:
+
And export it:
   
 
exports
 
exports
 
AddIntegers;
 
AddIntegers;
   
Of course in a more complex DLL we would not add all the code to the project source but to units, but we want to keep things simple here. So this is what our source code looks for now:
+
Of course in a more complex DLL file we would not add all the code to the project source but to units, but we want to keep things simple here. So this is what our source code looks for now:
   
 
library MyFirstLibrary;
 
library MyFirstLibrary;
Line 77: Line 77:
 
{$R *.res}
 
{$R *.res}
 
 
function AddIntegers(_a, _b: integer): integer; stdcall;
+
function AddIntegers(const _a, _b: integer): integer; stdcall;
 
begin
 
begin
 
Result := _a + _b;
 
Result := _a + _b;
Line 88: Line 88:
 
end.
 
end.
   
== Building the DLL ==
+
== Building the DLL file ==
   
To create a DLL from this library sourcecode is as simple as creating an executable for a normal delphi application: Just build it. You end up with a file called MyFirstLibrary.dll.
+
To create a '''DLL file '''from this library, sourcecode is as simple as creating an executable for a normal Delphi application: Just build it. You end up with a file called MyFirstLibrary.dll.
   
== Looking inside the DLL ==
+
== Looking inside the DLL file ==
   
Now, a DLL is a binary file and if you e.g. load it into an editor, you will see lots of gibberish. But there are tools that can extract some more information about it like DependencyWalker (from Microsoft?) or the ''PE-Information Wizard'' in [[GExperts]]. If you open your DLL with this wizard, it will tell you the following:
+
Now, a DLL file is a binary file and if you e.g. load it into an editor, you will see lots of gibberish. But there are tools that can extract some more information about it, like [https://en.wikipedia.org/wiki/Dependency_Walker Dependency Walker] or the ''PE-Information Wizard'' in [[GExperts]]. If you open your DLL file with this wizard, it will tell you the following:
   
 
[[Image:PE_Info_for_MyFirstLibrary.PNG]]
 
[[Image:PE_Info_for_MyFirstLibrary.PNG]]
Line 100: Line 100:
 
(And lots of other things that we don't currently care about.)
 
(And lots of other things that we don't currently care about.)
   
So it worked, you now have got a DLL that exports the function AddIntegers.
+
So it worked, you now have got a DLL file that exports the function AddIntegers.
   
== Using the DLL ==
+
== Using the DLL file ==
   
So, now how to call that DLL? Simple you just tell the compiler what DLL to load and which function to call with which parameters.
+
So, now how do we call that DLL file? Simple: You just tell the compiler what DLL file to load and which function to call with which parameters.
   
 
=== Creating a test project ===
 
=== Creating a test project ===
Line 156: Line 156:
 
[[Image:Output_of_MyLibrary_test.png]]
 
[[Image:Output_of_MyLibrary_test.png]]
   
Isn't that exciting? You just wrote your first DLL and called it from a test program! ;-)
+
Isn't that exciting? You just wrote your first DLL file and called it from a test program! ;-)
   
 
== Notes about memory management ==
 
== Notes about memory management ==
Line 162: Line 162:
 
Now, do you remember that longish comment that Delphi automatically added to the library project? It said something about memory management and strings and ShareMem and stuff.
 
Now, do you remember that longish comment that Delphi automatically added to the library project? It said something about memory management and strings and ShareMem and stuff.
   
Usually you don't care much about memory management in your applications. That's what the compiler and runtime libraries are for, right? But in the case of DLLs you are interfacing potentially with other compilers and runtime libraries, so you have to think about it.
+
Usually you don't care much about memory management in your applications. That's what the compiler and runtime libraries are for, right? But in the case of DLL files you are interfacing potentially with other compilers and runtime libraries, so you have to think about it.
   
For example, Delphi automatically allocates and frees the memory for storing your strings, it knows when they are no longer needed etc. The same applies for e.g. Visual Basic, but both do it in different ways. So, if you were to pass a string that was allocated by Visual Basic to a DLL written in Delphi you would end up in big trouble, because both would now try to manage the string and would get into each other's hair.
+
For example, Delphi automatically allocates and frees the memory for storing your strings, it knows when they are no longer needed, etc. The same applies for e.g. Visual Basic, but both do it in different ways. So, if you were to pass a string that was allocated by Visual Basic to a DLL file written in Delphi you would end up in big trouble, because both would now try to manage the string and would get into each other's hair.
   
 
=== Passing strings ===
 
=== Passing strings ===
   
So, in order to allow strings to be passed between an application and a DLL you must take extra care. Actually, the easiest way is just to not allow it at all! Now I can hear you thinking: "WTF is this guy talking about? Of course I want to pass strings from my application to the dll!"
+
So, in order to allow strings to be passed between an application and a DLL you must take extra care. Actually, the easiest way is just to not allow it at all! Now I can hear you thinking: "WTF is this guy talking about? Of course I want to pass strings from my application to the DLL file!"
   
Yes, you would want to, wouldn't you? But you usually don't want to pass strings but you want to pass the series of characters that are stored in a string. To do that you use something that comes from the ancient times when people where using primitive programming languages like C that didn't know such sophisticated data structures as strings (Yes, I know, you C fanatics. C can do everything given the right libraries. This is meant to be a joke.). Back then programmers were stuck with something called zero terminated strings or rather "a pointer to a array of characters which ends with the special character NUL". In Delphi that's called a PChar.
+
Yes, you would want to, wouldn't you? But you usually don't want to pass strings, but you want to pass the series of characters that are stored in a string. To do that you use something that comes from the ancient times when people where using primitive programming languages like C that didn't know such sophisticated data structures as strings (yes, I know, you C fanatics. C can do everything given the right libraries. This is meant to be a joke.). Back then programmers were stuck with something called zero-terminated strings or rather "a pointer to a array of characters which ends with the special character NUL". In Delphi that's called a PChar.
   
 
==== Exporting a string function ====
 
==== Exporting a string function ====
   
Now lets load our library project again and add a new function to it:
+
Now let's load our library project again and add a new function to it:
   
 
function CountChars(_s: Pchar): integer; StdCall;
 
function CountChars(_s: Pchar): integer; StdCall;
Line 185: Line 185:
 
Welcome to the conveniences of Delphi: It automatically converts a PChar to a string when assigning it to a string or passing it to a function expecting a string parameter.
 
Welcome to the conveniences of Delphi: It automatically converts a PChar to a string when assigning it to a string or passing it to a function expecting a string parameter.
   
I hope you also noticed that we declared our new function as StdCall again. You should never forget to explicitly add a calling convention. If you run into access violations when calling a DLL function, that's the first thing to check: Do your program and your DLL both use the same calling conventions? If not, you found the problem.
+
I hope you also noticed that we declared our new function as StdCall again. You should never forget to explicitly add a calling convention. If you run into access violations when calling a DLL function, that's the first thing to check: Do your program and your DLL file both use the same calling conventions? If not, you found the problem.
   
 
We export the new function just the same way we exported the other one. You end up with a project source like this:
 
We export the new function just the same way we exported the other one. You end up with a project source like this:
Line 247: Line 247:
 
end.
 
end.
   
Again, notice something? Not? Look closer! What type of parameter to we pass to CountChars? Yes, that's a string constant! Welcome to the conveniences of Delphi again: Delphi automatically converts string constants to PChars if you pass them to a function that expect PChars.
+
Again, notice something? Not? Look closer! What type of parameter do we pass to CountChars? Yes, that's a string constant! Welcome to the conveniences of Delphi again: Delphi automatically converts string constants to PChars if you pass them to a function that expect PChars.
   
 
Compile and run your program. You should now get another output line saying "5", which incidentally is the number of characters in the string 'hello'.
 
Compile and run your program. You should now get another output line saying "5", which incidentally is the number of characters in the string 'hello'.
Line 253: Line 253:
 
Now, was that painfull? You didn't technically pass a string, but for all practical purposes you did.
 
Now, was that painfull? You didn't technically pass a string, but for all practical purposes you did.
   
Now lets try to pass an actual string, not a string constant. Declare a vaiable ''s'' and assign your name to it:
+
Now let's try to pass an actual string, not a string constant. Declare a vaiable ''s'' and assign your name to it:
   
 
var
 
var
Line 272: Line 272:
 
Press Enter
 
Press Enter
   
=== Other Pitfalls ===
+
=== Other pitfalls ===
   
In Delphi you are probably used to returning all sorts of data types as function results, e.g. strings or classes. When using DLLs, you should not do that. Only ever return simple data types like integer, double or single, otherwise you will most likely end up with a DLL that you can only use from a Delphi application (and even that will be a major pain in the lower back to get to work). Again, this is a matter of memory management and different compilers / runtime libraries.
+
In Delphi you are probably used to returning all sorts of data types as function results, e.g. strings or classes. When using DLL files, you should not do that. Only ever return simple data types like integer, double or single, otherwise you will most likely end up with a DLL file that you can only use from a Delphi application (and even that will be a major pain in the lower back to get to work). Again, this is a matter of memory management and different compilers / runtime libraries.
   
Array and Record alignments are another annoyance when passing parameters over DLL borders. For performance reasons modern compilers arrange all data types to word- double-word- or even quad-word addresses. But if you don't know what the compilers on both sides actually use, you may get very peculiar effects. Delphi allows you to declare arrays and records as packed, meaning that no alignment takes place, but other programming languages or compilers may not have that concept. Delphi also allows you to force some alignment using the $A or $Align compiler directive, again, other programming languages or compilers may not give you that option. It's sometimes up to trial and error to find out how a structure is being passed to your program. Your best bet is usually to use data types on both sides that are multiples of 4 bytes long.
+
Array and Record alignments are another annoyance when passing parameters over DLL file boundaries. For performance reasons modern compilers arrange all data types to word- double-word- or even quad-word addresses. But if you don't know what the compilers on both sides actually use, you may get very peculiar effects. Delphi allows you to declare arrays and records as packed, meaning that no alignment takes place, but other programming languages or compilers may not have that concept. Delphi also allows you to force some alignment using the $A or $Align compiler directive, again, other programming languages or compilers may not give you that option. It's sometimes up to trial and error to find out how a structure is being passed to your program. Your best bet is usually to use data types on both sides that are multiples of 4 bytes long.
   
 
=== ShareMem ===
 
=== ShareMem ===
   
Just going back to the longish comment that Delphi automatically places into a new DLL project. If your DLL and your main program will both be Delphi programs (but are you sure that will always be the case?), you can take that advice and include the unit ShareMem as the first unit in the uses clause of both, the program and the DLL. If you do that you can happily pass strings and other automatically memory managed data types between program and DLL. But never ever should you pass objects! The reason is that even though you might use the same class declaration in both, the program and the DLL, these objects are not the the same and they might divert from each other as your code evolves.
+
Just going back to the longish comment that Delphi automatically places into a new DLL file project. If your DLL file and your main program will both be Delphi programs (but are you sure that will always be the case?), you can take that advice and include the unit ShareMem as the first unit in the uses clause of both, the program and the DLL file. If you do that you can happily pass strings and other automatically memory managed data types between the program and the DLL file. But never ever should you pass objects! The reason is that even though you might use the same class declaration in both, the program and the DLL file, these objects are not the the same and they might divert from each other as your code evolves.
   
A recommendation for advanced users is to use a 3rd party memory manager called [http://fastmm.sourceforge.net FastMM]. It's good enough that Borland actually use it as the primary memory manager in Delphi 2006 and onwards. If you're using a version before that, then you can download it for free and use it on a project by project basis. A big benefit for DLLs is that when you're using FastMM, you can use standard String variables in your exported procedures and functions without needing to worry about ShareMem, or distributing anything else with your project or DLLs.
+
A recommendation for advanced users is to use a third-party memory manager called [http://fastmm.sourceforge.net FastMM]. It's good enough that Borland actually use it as the primary memory manager in Delphi 2006 and onwards. If you're using a version before that, then you can download it for free and use it on a project by project basis. A big benefit for DLL files is that when you're using FastMM, you can use standard String variables in your exported procedures and functions without needing to worry about ShareMem, or distributing anything else with your project or DLL files.
   
 
== Conclusion ==
 
== Conclusion ==
   
Writing DLLs in Delphi is simple and also powerful. If you are going to call these DLLs only from other Delphi applications, you might want to look into [[Creating_Packages|packages]] first. If you want to call Delphi code from other programming languages, you may want to look into [[COM]] servers (but be warned: While using COM servers from e.g. Visual Basic is very simple, it can be complex from other languages and can be a pain to have to register them every time before being able to call them).
+
Writing DLL files in Delphi is simple and also powerful. If you are going to call these DLL files only from other Delphi applications, you might want to look into [[Creating_Packages|packages]] first. If you want to call Delphi code from other programming languages, you may want to look into [[COM]] servers (but be warned: While using COM servers from e.g. Visual Basic is very simple, it can be complex from other languages and can be a pain to have to register them every time before being able to call them).
 
[[Category:Examples]]
 
[[Category:Examples]]
 
[[Category:Source Code]]
 
[[Category:Source Code]]

Revision as of 13:13, 18 November 2018

Introduction

Delphi allows not only to create applications but also DLL files. A DLL file (short for dynamic link library) is a special kind of executable that cannot be started by itself, but exports procedures and functions (called "Entry Points") for other executables to call.

Creating a DLL file with Delphi

Creating a DLL file with Delphi consists of two steps:

1. Creating a library project
2. Exporting some functions from that library

Creating a library project

Delphi has got a special kind of project for a DLL file. To create one you use the File -> New -> Other menu entry to open a dialog where you can select the project type you want to create. There is one called "DLL Wizard"; that's the one we need here.

File New Other Dialog

Select it and press OK and Delphi will automatically create a new DLL file project for you. The source code looks like this:

library Project1;

uses
  SysUtils,
  Classes;

{$R *.res}

begin
end.

There is also a comment about using strings and ShareMem, but we can ignore that for now.

At first glance, this looks like the project source for a normal console program. There is only one difference: It starts with the keyword library rather than program.

Another difference is how it is listed in the project explorer. Where an application has got the extension ".exe" a library has got ".dll". So the new project is called "Project1.dll" rather than the usual "Project1.exe".

Go ahead and save the new project giving it a name like "MyFirstLibrary".

Exporting functions from a library

Now let's export some function from our new library. Let's start with something very simple that does not involve complex data structures or a GUI: A function for adding two integers and returning their sum:

function AddIntegers(const _a, _b: integer): integer;
begin
  Result := _a + _b;
end;

Yes, that's not very exciting, is it? But it is a function that can be called from any Windows program that wants to do it.

Side note about calling conventions

Now, this is getting rather technical and confusing: Programming languages have evolved over the years to pass parameters and return values from and to function calls in different ways. Originally that was only inside one language and using only one compiler so the programmer just wrote the code and the compiler took care of how the functions were called. Then came DLL files and all of a sudden code written in one language using one compiler could be called from other code written in a different language or only using a different compiler. It turned out that this didn't work, so the compiler makers came up with the notion of calling conventions. We will use one called StdCall here since it allows for most other languages and compilers to call our DLL file. (One particular example is Visual Basic (from before .NET), which supports only the StdCall calling convention.)

Exporting the function

So let's add a calling convention to our function:

function AddIntegers(const _a, _b: integer): integer; stdcall;
begin
  Result := _a + _b;
end;

And export it:

exports
   AddIntegers;

Of course in a more complex DLL file we would not add all the code to the project source but to units, but we want to keep things simple here. So this is what our source code looks for now:

library MyFirstLibrary;

uses
  SysUtils,
  Classes;

{$R *.res}

function AddIntegers(const _a, _b: integer): integer; stdcall;
begin
  Result := _a + _b;
end;

exports
   AddIntegers;
 
begin
end.

Building the DLL file

To create a DLL file from this library, sourcecode is as simple as creating an executable for a normal Delphi application: Just build it. You end up with a file called MyFirstLibrary.dll.

Looking inside the DLL file

Now, a DLL file is a binary file and if you e.g. load it into an editor, you will see lots of gibberish. But there are tools that can extract some more information about it, like Dependency Walker or the PE-Information Wizard in GExperts. If you open your DLL file with this wizard, it will tell you the following:

PE Info for MyFirstLibrary

(And lots of other things that we don't currently care about.)

So it worked, you now have got a DLL file that exports the function AddIntegers.

Using the DLL file

So, now how do we call that DLL file? Simple: You just tell the compiler what DLL file to load and which function to call with which parameters.

Creating a test project

Close the library project (File -> Close All) and create a new console application (File -> New -> Other + Select "Console Application").

You will end up with an empty console application project like this:

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils;

begin
  { TODO -oUser -cConsole Main : Insert code here }
end.

Save it as "MyLibraryTest"

Calling the DLL function

Add the following function declaration:

function AddIntegers(_a, _b: integer): integer; stdcall; external 'MyFirstLibrary.dll';

And call that function from within the main program:

WriteLn(AddIntegers(1, 2));
Write('Press Enter'); ReadLn;

Now your project should look like this:

program MyLibraryTest;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function AddIntegers(_a, _b: integer): integer; stdcall; external 'MyFirstLibrary.dll';

begin
  WriteLn(AddIntegers(1, 2));
  Write('Press Enter'); ReadLn;
end.

Build your project and run it. You should now see a console window like this:

Output of MyLibrary test

Isn't that exciting? You just wrote your first DLL file and called it from a test program! ;-)

Notes about memory management

Now, do you remember that longish comment that Delphi automatically added to the library project? It said something about memory management and strings and ShareMem and stuff.

Usually you don't care much about memory management in your applications. That's what the compiler and runtime libraries are for, right? But in the case of DLL files you are interfacing potentially with other compilers and runtime libraries, so you have to think about it.

For example, Delphi automatically allocates and frees the memory for storing your strings, it knows when they are no longer needed, etc. The same applies for e.g. Visual Basic, but both do it in different ways. So, if you were to pass a string that was allocated by Visual Basic to a DLL file written in Delphi you would end up in big trouble, because both would now try to manage the string and would get into each other's hair.

Passing strings

So, in order to allow strings to be passed between an application and a DLL you must take extra care. Actually, the easiest way is just to not allow it at all! Now I can hear you thinking: "WTF is this guy talking about? Of course I want to pass strings from my application to the DLL file!"

Yes, you would want to, wouldn't you? But you usually don't want to pass strings, but you want to pass the series of characters that are stored in a string. To do that you use something that comes from the ancient times when people where using primitive programming languages like C that didn't know such sophisticated data structures as strings (yes, I know, you C fanatics. C can do everything given the right libraries. This is meant to be a joke.). Back then programmers were stuck with something called zero-terminated strings or rather "a pointer to a array of characters which ends with the special character NUL". In Delphi that's called a PChar.

Exporting a string function

Now let's load our library project again and add a new function to it:

function CountChars(_s: Pchar): integer; StdCall;
begin
  Result := Length(_s);
end;

Notice something? No? Look closer? Still nothing? OK, I'll tell you: You know what the Length function does, don't you? It returns the number of characters stored in a string. But what do we pass to it? What is the parameter _s declared type? It's PChar, not String!

Welcome to the conveniences of Delphi: It automatically converts a PChar to a string when assigning it to a string or passing it to a function expecting a string parameter.

I hope you also noticed that we declared our new function as StdCall again. You should never forget to explicitly add a calling convention. If you run into access violations when calling a DLL function, that's the first thing to check: Do your program and your DLL file both use the same calling conventions? If not, you found the problem.

We export the new function just the same way we exported the other one. You end up with a project source like this:

library MyFirstLibrary;

uses
  SysUtils,
  Classes;

{$R *.res}

function AddIntegers(_a, _b: integer): integer; stdcall;
begin
  Result := _a + _b;
end;

function CountChars(_s: Pchar): integer; StdCall;
begin
  Result := Length(_s);
end;

exports
  AddIntegers,
  CountChars;
 
begin
end.

Save your changes and compile the project.

Calling a string function

Now we want to call that new function. So close the library project and open your test project again.

Add the following function declaration:

function CountChars(_s: Pchar): integer; StdCall; external 'MyFirstLibrary.dll';

and the following code to the main program:

WriteLn(CountChars('hello'));

Now your program should look like this:

program MyLibraryTest;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function AddIntegers(_a, _b: integer): integer; stdcall; external 'MyFirstLibrary.dll';

function CountChars(_s: Pchar): integer; StdCall; external 'MyFirstLibrary.dll';

begin
  WriteLn(AddIntegers(1, 2));
  WriteLn(CountChars('hello'));
  Write('Press Enter'); ReadLn;
end.

Again, notice something? Not? Look closer! What type of parameter do we pass to CountChars? Yes, that's a string constant! Welcome to the conveniences of Delphi again: Delphi automatically converts string constants to PChars if you pass them to a function that expect PChars.

Compile and run your program. You should now get another output line saying "5", which incidentally is the number of characters in the string 'hello'.

Now, was that painfull? You didn't technically pass a string, but for all practical purposes you did.

Now let's try to pass an actual string, not a string constant. Declare a vaiable s and assign your name to it:

var
  s: string;
begin
  s := 'your name';
  WriteLn('"', s, '" contains ', CountChars(s));
  Write('Press Enter'); ReadLn;
end.

Compile and run it ... Oh? WTF? It doesn't compile. Welcome to the inconveniences of using PChars! Delphi does not automatically convert strings to PChars (don't ask me, why). You have to explicitly tell it to do that by typecasting. So change your code to:

WriteLn('"', s, '" contains ', CountChars(PChar(s)));

Compile and run it. You should now get an output like this:

"your name" contains 9 characters
Press Enter

Other pitfalls

In Delphi you are probably used to returning all sorts of data types as function results, e.g. strings or classes. When using DLL files, you should not do that. Only ever return simple data types like integer, double or single, otherwise you will most likely end up with a DLL file that you can only use from a Delphi application (and even that will be a major pain in the lower back to get to work). Again, this is a matter of memory management and different compilers / runtime libraries.

Array and Record alignments are another annoyance when passing parameters over DLL file boundaries. For performance reasons modern compilers arrange all data types to word- double-word- or even quad-word addresses. But if you don't know what the compilers on both sides actually use, you may get very peculiar effects. Delphi allows you to declare arrays and records as packed, meaning that no alignment takes place, but other programming languages or compilers may not have that concept. Delphi also allows you to force some alignment using the $A or $Align compiler directive, again, other programming languages or compilers may not give you that option. It's sometimes up to trial and error to find out how a structure is being passed to your program. Your best bet is usually to use data types on both sides that are multiples of 4 bytes long.

ShareMem

Just going back to the longish comment that Delphi automatically places into a new DLL file project. If your DLL file and your main program will both be Delphi programs (but are you sure that will always be the case?), you can take that advice and include the unit ShareMem as the first unit in the uses clause of both, the program and the DLL file. If you do that you can happily pass strings and other automatically memory managed data types between the program and the DLL file. But never ever should you pass objects! The reason is that even though you might use the same class declaration in both, the program and the DLL file, these objects are not the the same and they might divert from each other as your code evolves.

A recommendation for advanced users is to use a third-party memory manager called FastMM. It's good enough that Borland actually use it as the primary memory manager in Delphi 2006 and onwards. If you're using a version before that, then you can download it for free and use it on a project by project basis. A big benefit for DLL files is that when you're using FastMM, you can use standard String variables in your exported procedures and functions without needing to worry about ShareMem, or distributing anything else with your project or DLL files.

Conclusion

Writing DLL files in Delphi is simple and also powerful. If you are going to call these DLL files only from other Delphi applications, you might want to look into packages first. If you want to call Delphi code from other programming languages, you may want to look into COM servers (but be warned: While using COM servers from e.g. Visual Basic is very simple, it can be complex from other languages and can be a pain to have to register them every time before being able to call them).