-
Notifications
You must be signed in to change notification settings - Fork 10
Callback concepts
PL/SQL Developer provides API, which can be called from your plug-in. Those procedures and functions are called callbacks. PL/SQL Developer passes pointers to all those callbacks to the plug-in. Then you call those procedures and functions from within your plug-in using these pointers.
In C# delegates are used to call methods by pointer. Delegates store reference to the C# managed memory area, but PL/SQL Developer's provides pointers to the unmanaged memory. Thus some additional "magic" is needed to use these pointers. This magic is called marshalling. Marshalling makes all things work: converts pointer to delegate, enables passing arguments from managed memory to unmanaged and vice versa. .NET documentation has got nice articles about marshalling:
- Default Marshaling Behavior — all the main things for quick understanding of the marshalling process.
- Interop Marshaling — large chapter with lots of articles and diagrams explaining various aspects of marshalling.
Feel free to explore other articles on related topics on .NET documentation site.
In order to call PL/SQL Developer's API (callbacks) from your plug-in:
- Declare a delegate.
- Declare a class member of that delegate type.
- Register the callback of interest. In other words, assign the pointer to the delegate variable.
- Use class member variable to call the callback.
The Example section at the bottom of the page depicts everything described in this article.
Declaring a delegate and a delegate variable for a callback is pretty straightforward:
- Find the callback of interest in the plug-in documentation, which is shipped with PL/SQL Developer.
- Declare the delegate with the right signature. See Mapping function signatures for details. Also you can refer to the official Using Delegates article for details on declaring the delegates.
- Declare the variable of the delegate type. It will be used later to assign the pointer and call the callback.
The example of delegate declaration:
delegate void IdeCreateWindow(int windowType, string text, [MarshalAs(UnmanagedType.Bool)] bool execute);The name for the delegate and its arguments are up to you, but it is strongly recommended to use delegate name, which can be easily mapped to the callback name, used in PL/SQL Developer documentation. Take a look at Recommended naming for some hints.
After delegate has been declared, it's time to declare a variable (class member):
private static IdeCreateWindow createWindowCallback;static keyword usage is up to you — it's optional. Also take a look at Recommended naming for some hints on naming delegate variables.
Registering a callback is where your plug-in is given the ability to call unmanaged API. Callbacks are registered within RegisterCallback method, which must be exported by your plug-in:
[DllExport("RegisterCallback", CallingConvention = CallingConvention.Cdecl)]
public static void RegisterCallback(int index, IntPtr function);PL/SQL Developer calls RegisterCallback and passes the unique callback index (int index) and the pointer (IntPtr function) for calling the callback. IntPtr is C# data type for unmanaged pointers.
RegisterCallback is called with all the possible index values (roughly 250 times, depending on the version of IDE). Your plug-in does not need all of them. Use index parameter and if or switch statement to distinguish only the callbacks you need. It is recommended to declare constants for those indices to improve the code readability. Take a look at Recommended naming for some hints.
private const int CREATE_WINDOW_CALLBACK = 20;
private const int SET_TEXT_CALLBACK = 34;
[DllExport("RegisterCallback", CallingConvention = CallingConvention.Cdecl)]
public static void RegisterCallback(int index, IntPtr function)
{
switch (index)
{
case CREATE_WINDOW_CALLBACK:
//register callback
break;
case SET_TEXT_CALLBACK:
//register callback
break;
}
}To know what index the callback is assigned, refer to "PL/SQL Developer Plug-In interface Documentation". The document contains the table (roughly at page 7) where all callbacks are listed. The first column, which is number, is the index of the callback.
Remember, that delegates are pointers to C# managed memory area, but PL/SQL Developer's provides pointers to the unmanaged memory. Thus marshalling is required. Use Marshal.GetDelegateForFunctionPointer to assign the unmanaged pointer to the delegate variable.
createWindowCallback = (IdeCreateWindow)Marshal.GetDelegateForFunctionPointer(function, typeof(IdeCreateWindow));This is pretty straightforward — use delegate variable you've declared earlier:
createWindowCallback(1, "", false);Here marshalling also comes in action when passing arguments to the unmanaged callback and receiving values back. See Passing and returning arguments for details.
The code fragment from the demo plug-in demonstrates the process of registering the callbacks of interest and calling them in CreateSqlWindow method. You can see the full code in DemoPlugin.cs.
using System;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
namespace DemoPluginNet
{
delegate void IdeCreateWindow(int windowType, string text, [MarshalAs(UnmanagedType.Bool)] bool execute);
[return: MarshalAs(UnmanagedType.Bool)]
delegate bool IdeSetText(string text);
public class DemoPlugin
{
private static IdeCreateWindow createWindowCallback;
private static IdeSetText setTextCallback;
[DllExport("RegisterCallback", CallingConvention = CallingConvention.Cdecl)]
public static void RegisterCallback(int index, IntPtr function)
{
switch (index)
{
case CREATE_WINDOW_CALLBACK:
createWindowCallback = (IdeCreateWindow)Marshal.GetDelegateForFunctionPointer(function, typeof(IdeCreateWindow));
break;
case SET_TEXT_CALLBACK:
setTextCallback = (IdeSetText)Marshal.GetDelegateForFunctionPointer(function, typeof(IdeSetText));
break;
}
}
public void CreateSqlWindow()
{
createWindowCallback(1, "", false);
setTextCallback("select 'Hello world!' from dual");
}
}
}Introduction
Main concepts
- Anatomy of the plug-in
- Exporting DLL functions
- Using callbacks
Misc
- Using menu items
- Accepting commands in the command window
- Validating DLL exports
- Working with color preferences
- Referencing external C# assemblies