Integration des GLPK-Solvers in ein C#-Programm (Teil 2) Frameworks 

Integration des GLPK-Solvers in ein C#-Programm (Teil 2)

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.

Related posts