I try to make a DLL out of a C/C++ Program that was written for Linux. I got it running in the Cygwin shell and made an interface for the DLL. I also used this to call the function but now the DLL hangs when it wants to allocate memory in a c file.
I broke the problem down to this simple program:
The malloctest.cpp locks like this
#include <stdlib.h>
#include "test.h"
extern "C" __declspec(dllexport) int malloctest();
int malloctest(){
int check = test();
return check;
}
test.h
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
int test();
#ifdef __cplusplus
}
#endif
test.c
#include "test.h"
int test(){
int * array = malloc(42 * sizeof(int));
free(array);
return 42;
}
I compiled it like this in the Cygwin shell
gcc -c malloctest.cpp test.c
gcc -shared -o malloctest.dll malloctest.o test.o
and I called it in C# like this:
class Program
{
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr LoadLibrary(string lpFileName);
public delegate int test();
static void Main(string[] args)
{
IntPtr pcygwin = LoadLibrary("cygwin1.dll");
IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
init();
IntPtr libptr = LoadLibrary("malloctest.dll");
IntPtr funcptr = GetProcAddress(libptr, "malloctest");
test malloctest = (test)Marshal.GetDelegateForFunctionPointer(funcptr, typeof(test));
int check = malloctest(); //hold
Console.WriteLine(check);
}
}
Now it just hangs without an Error.
It works if I use malloc in malloctest.cpp but as soon as I call a C file that uses malloc the dll just hangs.
Edit:
this is my real interface.h
extern "C" {
typedef struct det det_t;
struct det{
int id;
int hamming;
float goodness;
float decision_margin;
double c[2];
double lu[2];
double ld[2];
double ru[2];
double rd[2];
};
__declspec(dllexport) int scanJPGfromBUF(det_t * dets, uint8_t *, char *);
__declspec(dllexport) int test ();
}
Try changing the delegate to:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int test();
The default for .NET should be Stdcall, but the default in C is Cdecl.
I would translate the C struct to:
struct det
{
int id;
int hamming;
float goodness;
float decision_margin;
double c1;
double c2;
double lu1;
double lu2;
double ld1;
double ld2;
double ru1;
double ru2;
double rd1;
double rd2;
}
So I would remove all the arrays and transform them to multiple elements. The signature of the C method can be translated in multiple ways in C#.
Each parameter with a * could be a ref, a out or a [] (but not a ref [])... So it could be:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int scanJPGfromBUF(ref det_t dets, byte[] bytes1, byte[] chars1);
Related
i wanted to run a c++ function in c# via dll and pass few doubles.
The easiest way i found to pass multiple values is to predefine pointers and safe the values there.
However, here comes the question do i have to free the memory in c# and if yes, how can i delete the pointers?
Thanks!
[DllImport(CppPokerOddsCalculatorDLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static unsafe extern void CalculateOdds_2Hands_Test(string myHand1_param, string myHand2_param, double* res1, double* res2, double* res3);
static void Main(string[] args)
{
unsafe
{
double res1 = 0;
double res2 = 0;
double res3 = 0;
double* p_res1 = &res1;
double* p_res2 = &res2;
double* p_res3 = &res3;
CalculateOdds_2Hands_Test("AhAc", "3h3c", p_res1, p_res2, p_res3);
Console.WriteLine(res1);
Console.WriteLine(res2);
Console.WriteLine(res3);
}
}
Am I doing it right? I have two projects in parallel, the first is code that was made in C ++ and the second project (Console made in AspNetCore v3.1) is the attempt to call the method that is in C ++ code.
I need to call the C ++ method "Decrypt" in the C # project. How do I do that?
C++ code
#include <stdlib.h>
#include <string.h>
#include<windows.h>
#include "bascript.hpp"
extern "C"
int FAR PASCAL _export
Decript( const LPSTR name, const LPSTR passwordWithCript,
LPSTR passwordWithoutCript, unsigned int sizeSpaceRetorn ) {
LPSTR result = lpDecript( name, passwordWithCript);
if ( sizeSpaceRetorn < strlen(result) )
return 0;
strcpy( passwordWithoutCript, result );
delete result;
return 1;
}
C#
class Program
{
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Auto, EntryPoint = "Decript")]
private static extern string Decript(string name, string passwordWithCript, string passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
string retorno = Decript("<user>", "<cript_password>", "", 0);
Console.WriteLine(retorno);
Console.ReadLine();
}
}
You can return a pointer from native world (C/C++, etc.) as long as you use a .NET compatible memory allocator. On Windows, that would be the COM Allocator.
So here are 3 ways to return a string: Ansi, Unicode and BSTR (unicode). Note: you should avoid using Ansi on Windows.
C++ side:
extern "C" __declspec(dllexport) void* DecryptA(const char* name, const char* password)
{
char str[] = "hello ansi world";
int size = (lstrlenA(str) + 1) * sizeof(char); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) void* DecryptW(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello unicode world";
int size = (lstrlenW(str) + 1) * sizeof(wchar_t); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) BSTR DecryptBSTR(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello BSTR world";
// use .NET compatible allocator and COM coolness
return SysAllocString(str);
}
C# side:
[DllImport("mydll", CharSet = CharSet.Ansi)]
private static extern string DecryptA(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
private static extern string DecryptW(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string DecryptBSTR(string name, string password);
...
static void Main()
{
Console.WriteLine(DecryptA("name", "password"));
Console.WriteLine(DecryptW("name", "password"));
Console.WriteLine(DecryptBSTR("name", "password"));
}
Your C++ function does not return a string or equivalent. It returns an int result (success or failure), and the actual result goes in the passwordWithoutCript buffer.
So you need to create the buffer and pass it in.
Because you are using LPSTR on the C++ side, you need CharSet.Ansi:
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Ansi, EntryPoint = "Decript")]
private static extern int Decript(string name, string passwordWithCript, StringBuilder passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
var sb = new StringBuilder(1000); // or whatever size
if(Decript("<user>", "<cript_password>", sb, sb.Length) == 1)
Console.WriteLine(retorno);
Console.ReadLine();
}
I am trying to use catboost C API in C#. Below is the working code in C:
#include <stdio.h>
#include "c_api.h"
int main(int argc, char** argv){
float floatFeatures[3] = {96.215, 1.595655e+09, 3000};
char* catFeatures[0];
double result[1];
ModelCalcerHandle* modelHandle = ModelCalcerCreate();
// LoadFullModelFromFile is time consuming
if (!LoadFullModelFromFile(modelHandle, "../../test_catboost_model.cbm")) {
printf("LoadFullModelFromFile error message: %s\n", GetErrorString());
}
// CalcModelPredictionSingle is fast
if (!CalcModelPredictionSingle(modelHandle,
&floatFeatures, 3,
&catFeatures, 0,
&result, 1
)) {
printf("CalcModelPrediction error message: %s\n", GetErrorString());
}
ModelCalcerDelete(modelHandle);
printf("model score is %.20f", result[0]);
return 0;
}
And below is my attempt to do the same thing in C# (dotnet core on Linux), but it does not work... When I run "dotnet run" just no output no error message.
class Program
{
static void Main(string[] args)
{
var floatFeatures = new float[] { 96.215f, 1.595655e+09f, 3000 };
var catFeatures = new string[0];
var results = new double[1];
var modelHandle = ModelCalcerCreate();
if (!LoadFullModelFromFile(modelHandle, "{absolute path to the same model}/test_catboost_model.cbm"))
{
Console.WriteLine($"Load model error: {GetErrorString()}");
}
if (!CalcModelPredictionSingle(modelHandle, floatFeatures, 3, catFeatures, 0, out results, 1))
{
Console.WriteLine($"Predict error : {GetErrorString()}");
}
Console.WriteLine($"Model score is {results[0]}");
}
[DllImport("catboostmodel", EntryPoint = "ModelCalcerCreate")]
private static extern IntPtr ModelCalcerCreate();
[DllImport("catboostmodel", EntryPoint = "GetErrorString")]
private static extern string GetErrorString();
[DllImport("catboostmodel", EntryPoint = "LoadFullModelFromFile")]
private static extern bool LoadFullModelFromFile(IntPtr modelHandle, string fileName);
[DllImport("catboostmodel", EntryPoint = "CalcModelPredictionSingle")]
private static extern bool CalcModelPredictionSingle(
IntPtr modelHandle,
float[] floatFeatures, ulong floatFeaturesSize,
string[] catFeatures, ulong catFeaturesSize,
out double[] result, ulong resultSize
);
}
The relevant C header file is like below. The entire file is available on github.
#if defined(_WIN32) && !defined(CATBOOST_API_STATIC_LIB)
#ifdef _WINDLL
#define CATBOOST_API __declspec(dllexport)
#else
#define CATBOOST_API __declspec(dllimport)
#endif
#else
#define
CATBOOST_API
#endif
typedef void ModelCalcerHandle;
CATBOOST_API ModelCalcerHandle* ModelCalcerCreate();
CATBOOST_API const char* GetErrorString();
CATBOOST_API bool LoadFullModelFromFile(
ModelCalcerHandle* modelHandle,
const char* filename);
CATBOOST_API bool CalcModelPredictionSingle(
ModelCalcerHandle* modelHandle,
const float* floatFeatures, size_t floatFeaturesSize,
const char** catFeatures, size_t catFeaturesSize,
double* result, size_t resultSize);
Any suggestions are appreciated. Thank you!
It turns out I should not use the "out" keyword before "double[] result" in CalcModelPredictionSingle's signature, removing that fix the problem.
Below works.
[DllImport("catboostmodel")]
private static extern bool CalcModelPredictionSingle(
IntPtr modelHandle,
float[] floatFeatures, ulong floatFeaturesSize,
string[] catFeatures, ulong catFeaturesSize,
double[] result, ulong resultSize
);
EDIT: I've updated the code given the suggestions in #Hans Passant's comment and #David Heffernan's answer.
The argument c is no longer null, but both x and c still have length one when they are passed back to CallbackFunction.
I am trying to write C# code that passes a function pointer (using a delegate) to a C++ function, which calls the function pointer.
Code is below.
The problem I'm having is that when the C++ function f calls fnPtr(x,c), in the C# function CallbackFunction, x has one element (with the correct value of 1.0), and c is null. I have no idea what the problem is.
I can't change the signature of MyCallback.
C# code:
using System.Runtime.InteropServices;
namespace PInvokeTest
{
class Program
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate double MyCallback(
[In] double[] x,
[Out] double[] c);
private static double CallbackFunction(
[In] double[] x,
[Out] double[] c)
{
c[0] = x[0] + x[1] + x[2];
c[1] = x[0] * x[1] * x[2];
return c[0] + c[1];
}
private static MyCallback _myCallback;
[DllImport("NativeLib", CallingConvention = CallingConvention.StdCall)]
private static extern int f(MyCallback cf);
private static void Main()
{
_myCallback = new MyCallback(CallbackFunction);
f(_myCallback);
}
}
}
NativeLib.h:
#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MYAPI
#define MYAPI
#endif
#ifdef __cplusplus
extern "C"
{
#endif
#ifdef _WIN32
#ifdef MAKE_MY_DLL
#define MYAPI __declspec(dllexport) __stdcall
#else
#define MYAPI __stdcall
#endif
#else
#if __GNUC__ >= 4
#define MYAPI __attribute__ ((visibility ("default")))
#else
#define MYAPI
#endif
#endif
typedef int MyCallback(const double * x,
double * c);
MYAPI int f(MyCallback * fnPtr);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_
NativeLib.cpp:
#include "NativeLib.h"
#include <stdio.h>
#include <malloc.h>
MYAPI int f(MyCallback * fnPtr)
{
double x[] = { 1.0, 2.0, 3.0 };
double c[] = { 0.0, 0.0, 0.0 };
printf("%e\n", fnPtr(x, c));
return 0;
}
Using ref on your array parameter is wrong. That's a spurious extra level of indirection. You also need to pass the array lengths as parameters and let the marshaller know these lengths.
The delegate should be:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate double MyCallback(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
[In] double[] x,
[In] int lenx,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3)]
[Out] double[] c,
[In] int lenc
);
Change CallbackFunction to match.
The "duh" assumption I was making was that C would somehow pass some information on the size of x and c arrays, when it's just passing pointers. The solution that I found was to mimic the code found in a related SO question. (I don't know if it's a good way to solve my problem, but it works).
I changed the callback function (and the delegate definition to match) to use an IntPtr. In general, I obviously need to pass information on the sizes of c and x.
unsafe private static double CallbackFunction(
[In] IntPtr xp,
[Out] IntPtr cp)
{
Double* c = (Double*) cp.ToPointer();
Double* x = (Double*) xp.ToPointer();
c[0] = x[0] + x[1] + x[2];
c[1] = x[0] * x[1] * x[2];
return c[0] + c[1];
}
I'm programming C# code that imports a C++ function similar to this:
//C++ code
extern "C" _declspec(dllexport) int foo(double *var1, double *var2){
double dubs[10];
RandomFunction(dubs);
*var1 = dubs[5];
*var2 = dubs[7];
return 0;
}
To import this function to C# I've tried 2 methods and both have resulted in a System.AccessViolationException. That means I'm trying to access protected memory...
//C# import code
//method 1
[DllImport(example.dll,CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int foo(ref double var1, ref double var2);
//method 2
[DllImport(example.dll,CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int foo(double *var1, double *var2);
//C# calling code
//method 1
public unsafe int call_foo(){
....Stuff
double var1 = 0;
double var2 = 0;
foo(ref var1, ref var2);
....More Stuff
}
//method 2
public unsafe int call_foo(){
....Stuff
double var1 = 0;
double var2 = 0;
double *v1 = &var1;
double *v2 = &var2;
foo(v1, v2);
....More Stuff
}
Up until now, I've thought it was a problem with my C# code, but would the C++ code's use of an array cause a problem? Am I missing something really obvious here?
This isn't a problem with your C# p/invokes or the signature of the native function foo. Aside from your C++ array syntax and what exactly RandomFunction does, everything works fine. I've just tested with my own little demo:
C++:
void RandomFunction( double dubs[] );
extern "C" __declspec(dllexport) int foo ( double* var1, double* var2 )
{
double dubs[10];
RandomFunction( dubs );
*var1 = dubs[5];
*var2 = dubs[7];
return 0;
}
void RandomFunction( double dubs[] ) {
for ( int i = 0; i < 10; i++ ) {
dubs[i] = i;
}
}
C#:
[DllImport( "Native.dll", CallingConvention = CallingConvention.Cdecl )]
private static extern int foo( ref double var1, ref double var2 );
public static void Main( string[] args )
{
FooTest();
}
private static void FooTest()
{
double var1 = 0;
double var2 = 0;
foo( ref var1, ref var2 );
Console.Out.WriteLine( "Var1: {0:0.0}; Var2: {1:0.0}", var1, var2 );
// Prints "Var1: 5.0; Var2: 7.0"
}
I only have a few differences from what you have:
My C++ arrays are declared using the standard syntax double dubs [10].
My compiler requires two underscores in the __declspec syntax
I'm compiling everything using Visual Studio 2012, in 64-bit, on Windows 7 SP 1.