Use XGBoost DLL from c# via p/invoke

2019-03-04 10:55发布

问题:

I'm trying to use XGBoost's dll (libxgboost.dll) to create a DMatrix (which is like a 2D array) and get how many columns it has. It runs fine until it throws a System.AccessViolationException at the int cols = ... line in the code below:

using System;
using System.Runtime.InteropServices;

namespace basicXgboost
{
  class Program
  {
    [DllImport("../../libs/libxgboost.dll", CharSet = CharSet.Auto)]
    public static extern int XGDMatrixCreateFromFile([MarshalAs(UnmanagedType.LPStr)] string file, int silent, IntPtr outputPtr);

    [DllImport("../../libs/libxgboost.dll", CharSet = CharSet.Auto)]
    public static extern int XGDMatrixNumCol(IntPtr dmatrixPtr, IntPtr dmatrixColumnsPtr);

    static void Main(string[] args)
    {
      IntPtr dmatrixPtr = Marshal.AllocHGlobal(1000000);
      IntPtr dmatrixColumnsPtr = Marshal.AllocHGlobal(10);

      int result = XGDMatrixCreateFromFile("../../libs/test.txt", 0, dmatrixPtr);
      int cols = XGDMatrixNumCol(dmatrixPtr, dmatrixColumnsPtr);

      Marshal.FreeHGlobal(dmatrixPtr);
      Marshal.FreeHGlobal(dmatrixColumnsPtr);
    }
  }
}

Why does accessing unmanaged memory allocated with XGDMatrixNumCol(dmatrixPtr, dmatrixColumnsPtr) cause a System.AccessViolationException?

One possibility might be that I'm using pinvoke incorrectly for these functions. Below are the definitions for each dll function I use:

XGDMatrixCreateFromFile()

/*!
 * \brief load a data matrix
 * \param fname the name of the file
 * \param silent whether print messages during loading
 * \param out a loaded data matrix
 * \return 0 when success, -1 when failure happens
 */
XGB_DLL int XGDMatrixCreateFromFile(const char *fname,
                                    int silent,
                                    DMatrixHandle *out);

XGDMatrixNumCol()

/*!
 * \brief get number of columns
 * \param handle the handle to the DMatrix
 * \param out The output of number of columns
 * \return 0 when success, -1 when failure happens
 */
XGB_DLL int XGDMatrixNumCol(DMatrixHandle handle,
                            bst_ulong *out);

Here is the repo for my project. I'm using Visual Studio Enterprise 2015 . It's built in "Debug" mode (targeting x64) on Windows 10 Pro (64-bit). x64 binaries for libxgboost.dll can be found here. Although the linked repo does contain a copy of libxgboost.dll.

回答1:

Try to use the calling convention Cdecl which seems to be used by the DLL.

Also, the signature of the XGDMatrixCreateFromFile function is wrong. The parameter expected is not a pointer to some memory allocated by you, but the function will allocate memory itself and then return the pointer as an output parameter.

Try the following code. Note the use of the the out keyword on the outputPtr parameter in the XGDMatrixCreateFromFile function.

[DllImport("C:\\dev\\libs\\xgboost\\build\\Release\\libxgboost.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int XGDMatrixCreateFromFile([MarshalAs(UnmanagedType.LPStr)] string file, int silent, out IntPtr outputPtr);

[DllImport("C:\\dev\\libs\\xgboost\\build\\Release\\libxgboost.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int XGDMatrixNumCol(IntPtr dmatrixPtr, IntPtr dmatrixColumnsPtr);

static void Main(string[] args)
{
    IntPtr dmatrixPtr;
    IntPtr dmatrixColumnsPtr = Marshal.AllocHGlobal(10);

    int result = XGDMatrixCreateFromFile("C:\\dev\\libs\\xgboost\\demo\\data\\agaricus.txt.test", 0, out dmatrixPtr);
    int cols = XGDMatrixNumCol(dmatrixPtr, dmatrixColumnsPtr);

    Marshal.FreeHGlobal(dmatrixColumnsPtr);
}

When this works, you can then also simplify the call to get the number of columns by using the ulong datatype:

[DllImport("C:\\dev\\libs\\xgboost\\build\\Release\\libxgboost.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int XGDMatrixCreateFromFile([MarshalAs(UnmanagedType.LPStr)] string file, int silent, out IntPtr outputPtr);

[DllImport("C:\\dev\\libs\\xgboost\\build\\Release\\libxgboost.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int XGDMatrixNumCol(IntPtr dmatrixPtr, out ulong dmatrixColumnsPtr);

static void Main(string[] args)
{
    IntPtr dmatrixPtr;
    ulong dmatrixColumns;

    int result = XGDMatrixCreateFromFile("C:\\dev\\libs\\xgboost\\demo\\data\\agaricus.txt.test", 0, out dmatrixPtr);
    int cols = XGDMatrixNumCol(dmatrixPtr, out dmatrixColumns);
}