Nachdem ich im ersten Teil des Tutorials das Einbinden und Aufrufen des GLPK-Solvers in ein C#-Programm beschrieben habe, möchte ich nun auf die weiteren Schwierigkeiten eingehen, denen man speziell bei der Nutzung des MIP-Solvers aus einem C#-Programm gegebenübersteht. Der GLPK-MIP-Solver arbeitet mit Branch-and-Cut-Algorithmen, deren Abarbeitung vom Anwendungsprogramm über eine callback-Methode gesteuert werden kann. An dieser Stelle sei wieder auf die GLPK-Webseite zu genauen Details über diese Verfahren verwiesen. Im Folgenden ist der Ausschnitt einer solchen Callback-Funktion zu sehen, die sich nicht von einer entsprechenden C-Funktion unterscheidet. Die Verwendung von Zeigern ist in C# jedoch nur in einem unsicheren Kontext möglich, die C#-Klasse müsste also mit dem Attribut unsafe versehen werden, um diese Funktion zu nutzen.
void callback(glp_tree *tree, void *info)
{
switch (glp_ios_reason(tree))
{
case GLP_IBINGO:
...
}
}
Wie man die Methode glp_ios_reason importiert, ist im ersten Teil dieses Tutorials beschrieben. Es muss noch ergänzt werden, dass alle verwendeten Konstanten, die in der Header-Datei glpk.h definiert sind, auch im C#-Programm definiert werden müssen, also z.B:
const int GLP_IBINGO = 0x02;
Ebenso muss die Struktur glp_tree definiert worden sein:
struct glp_tree{ double _opaque_tree; }
Die Callback-Routine ist in einer weiteren zu deklarierenden Struktur enthalten. In der glp_iocp-Struktur werden alle für den MIP-Solver relevanten Optimierungseinstellungen abgelegt, darunter auch der Zeiger auf die callback-Funktion cb_func.
struct glp_iocp
{
public int msg_lev;
const int GLP_MSG_OFF=0;
const int GLP_MSG_ERR=1;
const int GLP_MSG_ON=2;
const int GLP_MSG_ALL=3;
const int GLP_MSG_DBG=4;
...
public void* cb_func;
...
};
Damit GLPK die Struktur mit default-Werten belegen kann, empfiehlt es sich, die Funktion glp_init_iocp aufzurufen. Hier wird wieder die bereits im ersten Tutorial begründete fixed-Notation verwendet, um den benötigten Zeiger auf die glp_iocp-Struktur zu erhalten.
glp_iocp io = new glp_iocp();
fixed (glp_iocp* iocc = &this.io)
{
glp_init_iocp(iocc);
}
Im oberen Codeabschnitt wurde die Struktur glp_iocp initialisiert. Die Herausforderung besteht nun darin, einen Zeiger auf die definierte Callback-Funktion in der Struktur an der Stelle io.cb_func einzutragen. In C# können Funktionszeiger erstellt werden, indem zunächst ein passendes Delegate definiert wird:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void CallbackDelegate(glp_tree* tree, void* info);
Wenn das Attribut über dem delegate weggelassen wird, löst dies eine AccessViolationException aus, mit der Begründung dass protected Speicher beschädigt wurde. Die Angabe der C-CallingConvention ist daher zwingend erforderlich.
Aus dem Delegate lässt sich nun der Funktionszeiger wie im Folgenden gezeigt erzeugen und an die Struktur übergeben, die dann wiederum an den MIP-Solver glp_intopt weitergereicht wird:
...
glp_simplex(this.lp, null);
cd = new CallbackDelegate(this.callback);
IntPtr ptr = Marshal.GetFunctionPointerForDelegate(cd);
this.io.cb_func = ptr.ToPointer();
fixed (glp_iocp* iocc = &this.io)
{
glp_intopt(this.lp, iocc);
}
Die Klasse Marshal ist im namespace System.Runtime.InteropServices enthalten.