Skip to content

Passing and returning arguments

Pavel Anisko edited this page Mar 24, 2018 · 3 revisions

When calling PL/SQL Developer's callbacks you need to pass arguments' values from the managed C# code to the unmanaged, and then receive the result from the unmanaged code — either through out arguments or return value. Marshalling makes this happen. You can refer to Callback concepts for the links on articles about marshalling.

According to "PL/SQL Developer Plug-In interface Documentation", the number of data types is limited to 3:

...Boolean (32 bit), Integer (32 bit) and zero terminated strings.

So these need to be marshalled and this article explains how.

Passing and receiving integers

No custom marshalling is required to pass and receive a 32-bit integer to and from unmanaged code. Using int datatype and default marshalling does all the job.

This example briefly demonstrates the case of passing an integer:

// C++ signature:    void IDE_SplashCreate(int ProgressMax)
// Delphi signature: Delphi procedure IDE_SplashCreate(ProgressMax: Integer)

// Declaration
public delegate void IdeSplashCreate(int progressMax);
private IdeSplashCreate splashCreateCallback;

// Usage
splashCreateCallback(100);
int maxProgress = 100;
splashCreateCallback(maxProgress);

And this one the case of returning an integer:

// C++ signature:    int IDE_GetWindowConnection();
// Delphi signature: IDE_GetWindowConnection: Integer;

// Declaration
public delegate int IdeGetWindowConnection();
private IdeGetWindowConnection getWindowConnectionCallback;

// Usage
int connectionIndex = getWindowConnectionCallback();

Passing and receiving booleans

Booleans need to be marshalled as UnmanagedType.Bool.

This example briefly demonstrates the case of passing and returning a boolean:

// C++ signature:    BOOL IDE_WindowHasEditor(BOOL CodeEditor);
// Delphi signature: procedure IDE_WindowHasEditor(CodeEditor: Bool);

// Declaration
[return: MarshalAs(UnmanagedType.Bool)]
public delegate void IdeWindowHasEditor([MarshalAs(UnmanagedType.Bool)] bool codeEditor);

private IdeWindowHasEditor windowHasEditorCallback;

// Usage
bool canSave = windowHasEditorCallback(true);
// or
bool notCode = false;
canSave = windowHasEditorCallback(notCode);

Passing and receiving strings

Strings are tricky. Method to use depends on whether string is an argument or a function return value:

  • Passing a string as an argument either in or out requires no custom marshalling — string data type and default marshalling works OK.
  • Receiving a string as a function return value, IntPtr and custom marshalling is required.

Using a string data type for a function return value works only in some cases, is unstable — it may work on one machine, but the same call may crash the IDE on another machine. Thus IntPtr is the recommended data type for function return values.

Passing a string as an incoming argument:

// C++ signature:    int SQL_Execute(char *SQL);
// Delphi signature: function SQL_Execute(SQL: PChar): Integer;

// Declaration
public delegate int SqlExecute(string sql);
private SqlExecute sqlExecuteCallback;

// Usage
int error = sqlExecuteCallback("select 'x' from dual");

Receiving a string as an out argument:

// C++ signature:    void IDE_GetConnectionInfo(char **Username, char **Password, char **Database);
// Delphi signature: procedure IDE_GetConnectionInfo(var Username, Password, Database: PChar);

// Declaration
public delegate void IdeGetConnectionInfo(out string username, out string password, out string db);
private IdeGetConnectionInfo getConnectionInfoCallback;

// Usage
// IMPORTANT: Allocate out string before call.
string user = "";
string password = "";
string db = "";
getConnectionInfoCallback(out user, out password, out db);

Receiving a string as return value:

// C++ signature:    char* SYS_RootDir();
// Delphi signature: function SYS_RootDir: PChar;

// Declaration
public delegate IntPtr SysRootDir();

// Usage
IntPtr dirPathPtr = rootDirCallback();
string rootDir = Marshal.PtrToStringAnsi(dirPathPtr);

Note that instead of string a pointer to a string is used and then explicit custom marshalling of IntPtr with Marshal.PtrToStringAnsi.

Know issue: there might be issues with this approach when passing national characters. For instance, cyrillic "абвгд" or baltic "ąšų" or whatever non-latin. Latin letters work OK.

Clone this wiki locally