C# Marshaling a C++ DLL with C interface. Problem passing Objects - c#

I'm trying to Marshal this C++ test DLL with C interface which works when calling from Python using CFFI:
#define AS_TYPE(Type, Obj) reinterpret_cast<Type *>(Obj)
#define AS_CTYPE(Type, Obj) reinterpret_cast<const Type *>(Obj)
struct mytype_s;
typedef struct mytype_s mytype_t;
namespace TestDLL {
class Test {
public:
Test() { return; }
~Test() { return; }
int test_function() const { return 314159; }
};
}
mytype_t *test_new() {
return AS_TYPE(mytype_t, new TestDLL::Test());
}
int test_function(const mytype_t *mytype) {
return AS_CTYPE(TestDLL::Test, mytype)->test_function();
}
From C# I tried using an IntPtr for mytype_t which had worked for me with structures in the past. The C# code:
[DllImport(dllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr test_new();
[DllImport(dllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int test_function(IntPtr mytype);
IntPtr mytype;
mytype = test_new();
Console.WriteLine("Alloc complete");
int result;
result = test_function(mytype);
Console.WriteLine("test: {0}", result);
But I get the error:
Alloc complete
Unhandled Exception: System.EntryPointNotFoundException: Unable to find an entry point named 'test_function' in DLL
Is this related to how I marshalled the mytype_t or a more complex issue with passing the object from C++ to C#?
Thanks

Related

Calling c dll method in c#

I am getting error as "'Cannot marshal 'parameter #1': Non-blittable generic types cannot be marshaled.'"
in C code I have
#include <stdio.h>
#include "pch.h"
typedef void(*RECV_CALLBACK)();
RECV_CALLBACK pfRecvCallBack;
typedef void(*RECV_CALLBACKINT_PARA)(int);
RECV_CALLBACKINT_PARA pfRecvCallBackIntPara;
extern "C"
{
__declspec(dllexport) void MethodWith_INT_Para(RECV_CALLBACKINT_PARA pfIntPara)
{
if (pfIntPara)
{
pfRecvCallBackIntPara = pfIntPara;
}
}
__declspec(dllexport) void MethodWithNo_Int_Para(RECV_CALLBACK pfRecv)
{
if (pfRecv)
{
pfRecvCallBack = pfRecv;
}
}
__declspec(dllexport) void Calling_INT_PARA(int a, int b)
{
pfRecvCallBackIntPara(a + b);
}
__declspec(dllexport) void Calling_without_Para(int a, int b)
{
pfRecvCallBack();
}
}
When i try to call this in C# call back without int parameter is working fine, but the callback with int parameter(line ->MethodWith_INT_Para(testWithIntPara);) is giving error as mention above.
C# code
using System.Runtime.InteropServices;
[DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "MethodWithNo_Int_Para")]
static extern void MethodWithNo_Int_Para(Action ptr);
[DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "MethodWith_INT_Para")]
static extern void MethodWith_INT_Para(Action<Int32> ptr);
[DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "Calling_INT_PARA")]
static extern void Calling_INT_PARA(int x, int y);
[DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "Calling_without_Para")]
static extern void Calling_without_Para();
Console.WriteLine("After calling Declare");
MethodWithNo_Int_Para(testWithoutIntPara);
Calling_without_Para();
void testWithoutIntPara()
{
Console.WriteLine("Without int para : called by Calling_without_Para");
}
MethodWith_INT_Para(testWithIntPara);
Calling_INT_PARA(1, 3);
void testWithIntPara(int x)
{
Console.WriteLine("With Int Para {x} : called by Calling_INT_PARA");
}
Console.ReadLine();
I am using Action to define callback function, please let me know if there is any other way to do this ?
Action<int> is generic, which cannot be marshalled. You need to create a custom delegate. Another problem is that C is expecting a function with the CDecl calling convention, bu the default is StdCall.
You need to use the following types, instead of Action and Action<int>
[UnmanagedFunctionPointer(CallingConvention.CDecl)]
public delegate void ActionCDecl();
[UnmanagedFunctionPointer(CallingConvention.CDecl)]
public delegate void ActionIntCDecl(int p1);

Passing string from Fortran dll to C#

I am trying to load a Fortran dll dynamically and pass a string back from fortran to C#. Everything looks fine when inside the fortran code, but when returning to C# the value of the string is lost. Instead the initial value, set in C#, is back. I have tried to use the 'ref' keyword to get the string to be passed by reference, but then I get error like below. What am I doing wrong?
The runtime has encountered a fatal error. The address of the error was at 0x709ce248, on thread 0x2ac4. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
Fortran code:
module FortLibInterface
implicit none
integer, parameter :: STR_LENGTH = 256
contains
subroutine GetString(Str)
!DIR$ ATTRIBUTES DLLEXPORT::GetString
!DIR$ ATTRIBUTES ALIAS: 'GetString' :: GetString
!DIR$ ATTRIBUTES REFERENCE:: Str
character(len=STR_LENGTH), intent(inout) :: Str
Str = 'bcdef...'
end subroutine
end module
C# code:
using System;
using System.Runtime.InteropServices;
namespace FortranCSTest
{
class Program
{
static void Main(string[] args)
{
string dllPath = "C:\\Temp\\FortLib.dll";
FortLibTest lib = new FortLibTest(dllPath);
lib.MakeTestCall();
}
}
public class FortLibTest
{
public const int STR_LENGTH = 256;
public const string FortranFuncName = "GetString";
private string pathToDll = null;
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadLibrary(String DllName);
[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
private static extern bool FreeLibrary(IntPtr hModule);
public FortLibTest(string FullPathToDll)
{
pathToDll = FullPathToDll;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void TypeGetStrInfo(char[] str);
void GetStrInfo(char[] str)
{
IntPtr pDll = LoadLibrary(pathToDll);
if (pDll != IntPtr.Zero)
{
IntPtr pFunc = GetProcAddress(pDll, FortranFuncName);
if (pFunc != IntPtr.Zero)
{
TypeGetStrInfo func = (TypeGetStrInfo)Marshal.GetDelegateForFunctionPointer(pFunc, typeof(TypeGetStrInfo));
func(str);
}
else
{
//Something
}
FreeLibrary(pDll);
}
else
{
//Something
}
}
public void MakeTestCall()
{
char[] str = new char[STR_LENGTH];
str[0] = 'a';
GetStrInfo(str);
}
}
}
For future reference. I added [In, Out] and everything works.
[DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetString([In, Out] char[] str);

How to return two different variable in c++?

dll C++
extern "C"
{
__declspec(dllexport) int mainfun()
{
return x;
}
}
In C#
[DllImport("example.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mainfun();
I only know how to return and call one variable from C++ to C#. I am writing a program where i need to call two different varables in C# from c++ dll
(like return x,y;). Please i need help.
EDIT1:
In C++
struct Point
{
int count_err;
int statement;
} obj;
extern "C"
{
__declspec(dllexport) Point mainfun()
{
return obj;
}
}
In C#
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int errsize;
public int statmnt;
}
[DllImport("example.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Point mainfun();
errsize = mainfun();
statmnt = mainfun();
Here errsize is giving an error-"the name 'errsize' does not exist in the current context".. What to do?
EDIT2:
In C#
total_errors.Text = p.errsize.ToString();
giving same error-"the name 'p' does not exist in the current." context"
Define new struct or array of data. Something like this:
C++:
struct Point
{
int count_err;
int statement;
} obj;
extern "C"
{
__declspec(dllexport) Point mainfun()
{
return obj;
}
}
C#:
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int errsize;
public int statmnt;
}
[DllImport("example.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Point mainfun();
Point p = mainfun();
var errsize = p.errsize;
var statmnt = p.statmnt;
First understand that if you want to return more than one value from any function, then you will need an object which can hold multiple values like struct, class object, list etc. But in your case you cannot use LIST or KeyValuePairList of C# because you have direct dependency with C++ code.
So use structure which is same in both the platforms. Now first you need to create a suitable data structure and change the return type of mainfun() when you are calling it as follows..
public struct abc {
public int a;
public int b;
}
[DllImport("example.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern abc mainfun();
Now in your C++ library, add the data structure and change the function definition.
typedef struct {
int a;
int b;
} abc;
extern "C"
{
__declspec(dllexport) abc mainfun()
{
abc obj;
obj.x = 1;
obj.y = 2;
return obj;
}
}

how to display in messagebox a C function from C# with a WCHAR*

I'm building a C# application that loads a C++ library.
I call functions from that C++ DLL. I use below function to display input string.
c++ dll:
wchar_t* Test_EchoString( wchar_t *InputStr )
{
String HWStr = String( InputStr );
return HWStr.c_str();
}
c# code:
[DllImport("testDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int _Test_EchoString([MarshalAs(UnmanagedType.LPWStr)] string s);
private void echo_string_Click(object sender, RoutedEventArgs e)
{
string upn = "aaaaa";
_Test_EchoString(upn);
MessageBox.Show(_Test_EchoString(upn).ToString());
}
I get in messagebox number 18666252 but i want to get a string from _Test_EchoString().
You have a pair of problems in your code:
in your C# you defined _Test_EchoString as public static extern int _Test_EchoString, so when you execute it, the returned value will be the address of the first character of the string HWStr.c_str().
And here it shows another problem, as anderas said, you are returning an invalid pointer, because HWStr.c_str() returns the pointer to the current value of the std::wstring object, so it is valid as long that the wstring is valid, so when the method Test_EchoString ends its execution it is no more valid(because HWStr is destroyed).
There are different ways to fix this problems, I'm going to show you two of these:
1) The first is to allocate the memory you want to return in the Heap and free it later with another call:
static wchar_t *Test_EchoStringResult;
extern "C" __declspec(dllexport) const wchar_t * Test_EchoStringNew(const wchar_t *InputStr)
{
std::wstring HWStr(InputStr);
HWStr += L" something";
Test_EchoStringResult = new wchar_t[HWStr.length() + sizeof(wchar_t)];
HWStr.copy(Test_EchoStringResult, HWStr.length());
Test_EchoStringResult[HWStr.length()] = L'\0';
return Test_EchoStringResult;
}
extern "C" __declspec(dllexport) void Test_EchoStringDelete()
{
delete[] Test_EchoStringResult;
}
And this is the usage in C#:
[DllImport("testDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern IntPtr Test_EchoStringNew(string foo);
[DllImport("testDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void Test_EchoStringDelete();
public void foo()
{
string result = Marshal.PtrToStringAuto(Test_EchoStringNew("test"));
MessageBox.Show(result.ToString());
Test_EchoStringDelete();
}
To me, this looks pretty ugly, so I'd prefer to use another pattern
2) Passing a callback to the C method and pass to this method HWStr.c_str() when HWStr is still valid:
extern "C" __declspec(dllexport) void Test_EchoString(const wchar_t *InputStr, void (*callback)(const wchar_t*))
{
std::wstring HWStr(InputStr);
HWStr += L" something";
callback(HWStr.c_str());
}
And here is the C# usage:
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public delegate void myCallback(string toShow);
[DllImport("testDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void Test_EchoString(string foo, myCallback callback);
public void foo()
{
Test_EchoString("test", callback);
}
void callback(string toShow)
{
MessageBox.Show(toShow);
}

C# Error Finding Method in DLLImport

I have a C++ assembly that I am importing using DLLImport.
I am attempting to call its method:
namespace Testing
{
class Test{
int Run(char* filePath, bool bEntry, double duration){//code}
};
}
by
[DllImport(dllName, CharSet = CharSet.Auto)]
public static extern int Run(string filePath, bool bEntry, double duration)
);
When I call its method, I get the error message:
Unable to find an entry point named Run in dll
The "Run" looks to be a non-static class method. Although, it's possible to call such methods from C# this is not the primary use-case. It would be way easier to consume it from .NET if you expose it via COM, or at-least as a plain C interface:
extern "C" __declspec(dllexport) void* Testing_Test_Create();
extern "C" __declspec(dllexport) void Testing_Test_Destroy(void* self);
extern "C" __declspec(dllexport) int Testing_Test_Run(void* self, char* filePath, bool bEntry, double duration);
And here is a sample how to call C++ class methods from C#:
// Test.cpp in NativeLib.dll
namespace Testing
{
class __declspec(dllexport) Test
{
public:
explicit Test(int data)
: data(data)
{
}
int Run(char const * path)
{
return this->data + strlen(path);
}
private:
int data;
};
}
// Program.cs in CSharpClient.exe
class Program
{
[DllImport(
"NativeLib.dll",
EntryPoint = "??0Test#Testing##QAE#H#Z",
CallingConvention = CallingConvention.ThisCall,
CharSet = CharSet.Ansi)]
public static extern void TestingTestCtor(IntPtr self, int data);
[DllImport(
"NativeLib.dll",
EntryPoint = "?Run#Test#Testing##QAEHPBD#Z",
CallingConvention = CallingConvention.ThisCall,
CharSet = CharSet.Ansi)]
public static extern int TestingTestRun(IntPtr self, string path);
static void Main(string[] args)
{
var test = Marshal.AllocCoTaskMem(4);
TestingTestCtor(test, 10);
var result = TestingTestRun(test, "path");
Console.WriteLine(result);
Marshal.FreeCoTaskMem(test);
}
}
Entry point names might be different for your build configuration/compiler, so use dumpbin utility to obtain them. Again, this is just a proof of concept, in real code it would be better to use COM.
See here: http://dotnetperls.com/dllimport
I'm not sure this will help if the function is a member of a class, but to locate the entry point by name, not ordinal, you'll need a .def file in your dll..
LIBRARY mylib
Run #1

Categories