Your First Delphi Plugin

From TrillWiki

Jump to: navigation, search

This page will get you started on your first plugin in Delphi. Once you've finished this page, you should be able to get your DLL to be recognized by Trillian as a plugin, and you should be ready to start looking through the API reference for commands and notifications to use in your plugin. The SDK is written in C, but it shouldn't be too tough to understand even if you're only familiar with Pascal. If at any time you get stuck, make a post in the Trillian Plugin Development Forum and someone should be able to help you.

Some of this is for beginning programmers, but there's also useful information in here for experienced programmers that are just starting off with Trillian plugins.

Contents

[edit] Getting started

Most of the sample plugins you will find were written in C or C++, as are most of the examples you will find in this wiki. However there is plenty of information out there for Delphi programmers, including on this site. The biggest problem you might face unique to Delphi programmers is dealing with the several types of strings that Delphi supports.

If you've never made a .dll in Delphi before you shouldn't be at a disadvantage. This is how you create a new library project in Delphi 7:

  1. Click File -> New -> Other.
  2. On the New tab, select "DLL Wizard" and click OK.
  3. Save the project immediately in a new folder, with a name that describes your plugin (e.g. something like "sample" in this case).

The wizard will put in a big comment about sharing strings. You can delete this straight away, since we won't be sharing any Delphi-style strings in this project.

[edit] The start of a plugin

The first thing you need to do is export two functions from your plugin: PluginMain and PluginVersion. Put this code just before the begin line:

 exports
   PluginVersion name 'plugin_version',
   PluginMain name 'plugin_main';

If you try to compile the plugin at this point you'll get two errors, because we haven't actually made the functions PluginMain or PluginVersion. To keep things neat let's make a new unit to put them in (File -> New -> Unit). Save this unit straight away in the same directory as the project, with a descriptive name e.g. TrillCalls.

In the interface section of this unit we will need to put the declarations for the functions that we're exporting. Use this code:

 function PluginVersion: Integer; cdecl;
 function PluginMain(Event: PChar; Data: Pointer): Integer; cdecl;

You might not have seen the keyword cdecl before; it simply tells Delphi that these functions will be called by a program written in C.

Let's move on the writing the function definitions now i.e. the actual code for the functions. These all need to go in the implementation section.

The PluginVersion needs to return the lowest version of the API that it supports. Trillian Pro 1.0 introduced version 1 of the API, Trillian Pro 2.0 version 2 and Trillian Pro 3.0 version 3. Example: If your plugin will work in both Trillian 2.0 and 3.0, you should return 2. Since you're just starting out, it's best to return 3 for now. If/when you want to worry about backwards compatibility, you can check that your plugin doesn't rely on any commands/notifications not supported by Trillian 2.0; then it can return 2 from PluginVersion. (Each command/notification page in the API docs will tell you in which Trillian version it was introduced.) Here's an example PluginVersion function for you to use:

 function PluginVersion: Integer; cdecl;
 begin
   Result := 3;
 end;

The PluginMain function is where the magic starts (you'll see C programmers calling it the plugin_main function). Once Trillian successfully loads your plugin and verifies its API version, it will send you your first notifications via the PluginMain function. Here's the bare minimum you'll need to be able to load in Trillian:

 function PluginMain(Event: PChar; Data: Pointer): Integer; cdecl;
 begin
   Result := 0;
 end;

The default return value for PluginMain and all notification callbacks is 0.

Once you have those two functions defined you should be able to compile your plugin and load it up in Trillian. (If you don't know how load it in Trillian, I'm not going to help you -- you should learn how to use a plugin before learning how to write one!)


Before you can really start communicating with Trillian, you're going to need the conversion of plugin.h to Delphi. You can view it here or download it. You should save this into the same directory as your plugin project and put in the uses clause of any unit that will be communicating with the Trillian API.

[edit] The language of Trillian

I'm going to take a break from the code and the compiler for this section, and just explain the basics of what you'll need to know to communicate with Trillian. If you get stuck while reading this section, try to ignore the parts you don't understand for now and continue reading. If you get hopelessly stuck, just continue with the next section and come back to this later. It can take a little while to get the hang of all this, but once you've had some hands-on experience, it should start becoming a lot easier for you to understand.

Now, a little intro on how things work in the Trillian API. First, there are the commands that you send to Trillian to ask it for information or to tell it what to do. Then, there are the notifications that it sends back to you in response; sometimes immediately, and sometimes when triggered by some event, depending on the command you sent. Of course, not all commands will receive notifications; only those that can set a callback will receive notifications. But more on that later. (Note that there are a few notifications that Trillian will send to you without your having to ask for them. These are the plugin_main Notifications, and they are sent to your PluginMain function.)

Both commands and notifications pass almost all of their data inside structs. One of the simplest such structs is:

 type
   PTtkSkinBitmap = ^TTtkSkinBitmap;
   skin_bitmap_t = record
     struct_size: Cardinal;
     
     name       : PAnsiChar;
     _file      : PAnsiChar;
   end;
   TTtkSkinBitmap = skin_bitmap_t;


skin_bitmap_t is the C-style name of the structure. It's the name that will be used in most documents, including its API reference and is what C programmers will use in discussion about it. You're free to use it in your programs, but the more Delphi-like name TTtkSkinBitmap provided will probably look cleaner. If a variable is a pointer to this structure then you would use the PTtkSkinBitmap type. At this point you may be thinking have two names for everything is nothing but confusing, but when you have a large plugin consistently using Delphi-style names you'll realise that any confusion is worth it for the clean looking code.

Any time you create a struct to send to Trillian as part of a command, the first thing you want to do is call trillianInitialize(myStruct); on it. This will initialize all members of the struct to 0 (or nil, as the case may be), and set myStruct.struct_size to SizeOf(myStruct). The one exception to this is the TTtkListEntry structure, for which you will need to call trillianListInitialize(myStruct); instead.

(In case you're insterested, Trillian will use the struct_size parameter for versioning purposes, so it knows how big of a struct you're passing it, and therefore how much memory it can safely access. With newer API versions, many structs will grow in size, with new parameters being added to the end. If it weren't for the struct_size parameter, Trillian would have to assume your struct was the new size, and it would end up accessing bad memory.)

To send commands to Trillian, you will need to call the PluginSend function, which Trillian will give to you as part of the load notification (which I'll explain in the next section). All notifications other than the PluginSend Notifications will be sent to a callback that you give to Trillian. You can choose to give Trillian one common callback for all commands, or even a different callback for every command if you want, but most likely it'll be somewhere in between. The callbacks you send to Trillian must all have the same parameters and return value, namely:

 function MyCallback(WindowID: Integer; SubWindow: PAnsiChar; Event: PAnsiChar;
   Data: Pointer; UserData: Pointer): Integer; cdecl;

Note that you will likely never use the first two parameters.


Don't worry if you didn't get all that. There'll be plenty of time for it to sink in. In the meantime, let's get back to the code.

[edit] Making first contact

Now that your plugin is loading in Trillian, you've probably noticed that Trillian doesn't show any info for your plugin. That's what we'll tackle next. The code to add this will go in your PluginMain call, and will be handled as part of your response to the load notification. Let's go ahead and look at a simple example:

 // Unit-wide variable (in this case, a function pointer).
 var
   PluginSend: TFNTtkPluginFunctionSend;
 
 function PluginMain(Event: PChar; Data: Pointer): Integer; cdecl;
 var
   PluginInfoPtr: PTtkPluginInfo;
 begin
   Result := 0;
   if Event = 'load' then
   begin
     PluginInfoPtr := Data;
 
     // We'll need this a lot, so store it for later
     PluginSend := PluginInfoPtr.plugin_send;
 
     // Tell Trillian about the plugin
     PluginInfoPtr.GUID := MyGUID;
     PluginInfoPtr.name := 'My first plugin';
     PluginInfoPtr.company := 'John Smith';
     PluginInfoPtr.version := '1.0';
     PluginInfoPtr.description := 'This is my first plugin attempt';
   end;
 end;

For this to work, you'll need to have the Plugin unit in your uses clause in TrillCalls, and have defined a constant MyGUID (see Generating GUIDs). For now it's fine to put this constant just before PluginMain, but when you're writing a real plugin you'll want to make a separate Globals unit move it and PluginSend there (along with any other global variables/constants/types that you make). You'll probably also want to copy out some of the other members of TTtkPluginInfo (see the load notification for a description of each one) and put them in that unit too. When you do this be sure to put Globals in the uses clause of all your other units.

Now you should be able to load your plugin in Trillian and see the info about your plugin.

Of course, you'll want to practice good coding, so it's not a bad idea to move that code into its own function. Here's what I mean:

 procedure PluginLoad(Data: PTtkPluginInfo); forward;
 
 function PluginMain(Event: PChar; Data: Pointer): Integer; cdecl;
 begin
   Result := 0;
   if Event = 'load' then PluginLoad(Data);
 end;
 
 procedure PluginLoad(Data: PTtkPluginInfo);
 begin
   PluginSend := Data.plugin_send;
 
   Data.GUID := MyGUID;
   Data.name := 'My first plugin';
   Data.company := 'John Smith';
   Data.version := '1.0';
   Data.description := 'This is my first plugin attempt';
 end;

That doesn't leave much in plugin_main, but don't worry, we'll add more to it in a bit. In fact, we'll add to it right now.

[edit] Hello, world!

You now have the tools you need to actually do something with your plugin. Let's start simple, with a classic "Hello, world!" plugin. You'll want to add this new code into the start notification, since as its description says, your plugin should start any action it performs in the start notification (plus, that's the easiest way to trigger it right now :-)).

To actually display our "Hello, world!", we're going to use the systraySetAlert command. You're in luck! The systraySetAlert also uses a callback, so you're going to get to see some notifications in action, too. The notifications that can be received for this command are the Alert Notifications, which are linked to from the systraySetAlert command. Bear in mind that all of this code should be in the implementation section of the unit.

 // Forward declarations of procedures we'll define later
 procedure PluginLoad(Data: PTtkPluginInfo); forward;
 procedure PluginStart; forward;
 function SystrayCallback(WindowID: Integer; SubWindow: PAnsiChar; Event: PAnsiChar;
   Data: Pointer; UserData: Pointer): Integer; cdecl; forward;
 
 function PluginMain(Event: PChar; Data: Pointer): Integer; cdecl;
 begin
   Result := 0;
   if Event = 'load' then PluginLoad(Data) else
   if Event = 'start' then PluginStart;
 end;
 
 procedure PluginLoad(Data: PTtkPluginInfo);
 begin
   PluginSend := Data.plugin_send;
 
   Data.GUID := MyGUID;
   Data.name := 'My first plugin';
   Data.company := 'John Smith';
   Data.version := '1.0';
   Data.description := 'This is my first plugin attempt';
 end;
 
 procedure PluginStart;
 var
   Alert: TTtkAlert;
 begin
   trillianInitialize(Alert);
 
   Alert._type := 'default';
   Alert.text := 'Hello world!';
   Alert.callback := SystrayCallback;
 
   PluginSend(MyGUID, 'systraySetAlert', @Alert);
 end;
 
 function SystrayCallback(WindowID: Integer; SubWindow: PAnsiChar; Event: PAnsiChar;
   Data: Pointer; UserData: Pointer): Integer; cdecl;
 begin
   Result := 0;
   if Event = 'alert_linkClick' then Beep;
 end;

Before the plugin will compile you'll need to add SysUtils to the uses clause (because of the Beep procedure).

Now when you start up your plugin, you should get a systray alert in the corner of your screen that says, you guessed it, "Hello, world!" If you click on the alert it'll beep.

If you've made it this far, you're well on your way to creating your own plugin. Feel free to dig around in the table of contents to find what's available for you. If you have any questions or comments, head on over to the Plugin Development Forums and make a post. We'll be waiting to hear from you. :-)

This guide has originally been created by TrillHunter .