I'm trying to use the 'opaque pointer' style of FFI where the C# (Unity) code only sees my Rust type as an IntPtr that it has to pass around to various Rust functions. However I am getting EntryPointNotFound exceptions as soon as the function that I'm referencing refers to an enum.
The two functions which don't refer to an enum work fine, but the function that does fails to bind apparently and throws a EntryPointNotFoundException. I've included the symbols in the dynlib (bundle) file to show that the symbol is in the file.
I've tried without the "C" in extern "C" in Rust, and CallingConvention=CDecl and CallingConvention=StdCall in C# but that didn't change the situation - the int and int pointer function will run (their respective print functions indeed print), but the enum pointer function just throws EntryPointNotFoundException and "enum pointer fine" is never printed.
ptr_test.rs
pub enum TestEnum{Test(i32)}
#[no_mangle]
pub extern "C" fn ptr_test(arg: *mut i32) {}
#[no_mangle]
pub extern "C" fn int_test(arg: i32) {}
#[no_mangle]
pub extern "C" fn consume_ptr(arg: *mut TestEnum) {}
RustTest.cs
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;
public class RustTest : MonoBehaviour {
[DllImport("libptr_test")]
private static extern void ptr_test(IntPtr x);
[DllImport("libptr_test")]
private static extern void int_test(int x);
[DllImport("libptr_test")]
private static extern void consume_ptr (IntPtr x);
// Use this for initialization
void Start () {
print ("Hello World!");
ptr_test (new IntPtr (0));
print ("pointer fine");
int_test (0);
print ("int fine");
consume_ptr (new IntPtr (0));
print ("enum pointer fine");
print ("Done!");
}
// Update is called once per frame
void Update () {
}
}
nm -gU carved-unity/carved/Assets/libptr_test.bundle
...
00000000000009c0 T _consume_ptr
0000000000000990 T _int_test
0000000000000960 T _ptr_test
...
I'm not able to reproduce your behavior. Here is my Rust source:
pub enum TestEnum{Test(i32)}
#[no_mangle]
pub extern "C" fn consume_ptr(arg: *mut TestEnum) {
println!("{:?}", arg);
}
I compile it with rustc --crate-type=dylib example.rs
I then copied the resulting example.dll into a .NET Console application, and using this P/Invoke signature:
[DllImport("example.dll", EntryPoint = "consume_ptr", CallingConvention = CallingConvention.Cdecl)]
private static extern void consume_ptr(int arg);
static void Main(string[] args)
{
consume_ptr(0);
}
And 0x0 is printed to the console. I don't know how to properly create and Marshal a Rust enumeration from .NET, but it doesn't produce an "EntryPointNotFoundException", and have confirmed that it is properly exported by looking at the exports with Dependency Walker.
Related
Currently I am working on a project where I have to get some Julia scripts written by other people to be called from C# within Unity. I've been trying to do some basic examples just to see what works and what doesn't. On the Julia documentation, it says to use the function: jl_get_function to grab a pointer to a function withing a julia module. However, I get an EntryPointNotFound in the libjulia.dll, and when I open up the dll on my computer with DependencyWalker I can't find a function called that. Am I crazy or did I install something oddly? other things like jl_eval_string and jl_unbox_float64 work fine.
Also, I'm not entirely sure how to get a pointer to the module for jl_get_function. I've thought of grabbing a pointer from a Memory Mapped file object, or from grabbing the IntPtr from jl_eval_string(include([module name in directory]));, but I'm not sure.
Here's my code in Julia for this test.
module TestModule
export calculate
function calculate(a::Float64,b::Float64)::Float64
return 3a+b^2
end
function calcMore(a,b)
return ones(a,b)::Array{Float64,2};
end
function moreTest(a::Float64, b::Float64)
return (a+b)::Float64;
end
end
and here's my code in C#, that's been snipped a bit
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace TestCInCSharp
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
[DllImport("libjulia.dll", SetLastError = true)]
public static extern void jl_init(string path);
[DllImport("libjulia.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr jl_eval_string(string input);
[DllImport("libjulia.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr jl_box_float64(float value);
[DllImport("libjulia.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern double jl_unbox_float64(IntPtr value);
[DllImport("libjulia.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr jl_get_function(IntPtr func, string name);
[DllImport("libjulia.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr jl_call2(IntPtr func, IntPtr v1, IntPtr v2);
[DllImport("libjulia.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void jl_atexit_hook(int a);
static void Main(string[] args)
{
string p = #"C:\Users\schulk4\Documents\Programming\TestJuliaSim\Assets\test_julia.jl";
string julia_path = #"C:\Users\schulk4\AppData\Local\Julia-0.5.2\bin";
IntPtr module, module2;
IntPtr a, b, c;
SetDllDirectory(julia_path);
jl_init(julia_path);
p = #"C:\\Users\\schulk4\\Documents\\Programming\\TestJuliaSim\\Assets\\test_julia.jl";
p = "include(\"" + p + "\")";
module = jl_eval_string(p); //holds module pointer?
a = jl_eval_string("TestModule.calculate(3.0,4.0)");
double d = jl_unbox_float64(a);
Console.WriteLine(d);
a = jl_eval_string("TestModule.calculate");
b = jl_box_float64(3.0f);
c = jl_box_float64(4.0f);
module2 = jl_call2(a, b, c);
d = jl_unbox_float64(module2);
Console.WriteLine(d);
a = jl_eval_string("TestModule.moreTest");
b = jl_box_float64(12.0f);
c = jl_box_float64(13.0f);
module2 = jl_call2(a, b, c);
d = jl_unbox_float64(module2);
Console.WriteLine(d);
IntPtr f = jl_get_function(module, "calculate"); //EntryPointNotFoundException
jl_atexit_hook(0);
Console.ReadLine();
}
}
}
You can see my attempts at getting a pointer to a function with jl_eval_string in the code. This is an example run before the exception:
25
1.5977136277678E-314
1.08223857600744E-314
I've been running into all sorts of problems, I was just wondering if anybody would be able to help me. I am not very familiar with this topic, I learned about P/Invoke about a week ago.
jl_get_function is an inline function. You can use jl_get_global.
Also note that your code can crash at any time. All jl_value_t* that needs to be used across a call to julia runtime/functions must be rooted. See memory managing section in the embedding doc. I don't know how you can translate that to C#.
my c++ dll:
int test2::CallMe(int y) {
return y;
}
c# code:
[DllImport("test2.dll",CharSet = CharSet.Anci)]
private static extern int CallMe(int y);
Console.WriteLine(CallMe(7));
if the dll and the test program are compiled in x86 i get a print:
7
but if i compile them at X64 for c++ and X64 or any CPU for c# the print is:
0
Any suggestion?
edit: the problem is the call, because in debugger i see that the CPP receives 0 , or null in case of struct.
edit 2: the functions are exported using a def file. if i export a function using extern "C" it works fine but i cant export a function of a calss, or i dont know how
edit 3: apparently the arguments are not actually zero, only the last argument is zero, all arguments are shifted, the second param is set to the first one and so on
Calling a C++ function from C# isn't exactly/completely/transparently supported. You can try using the CallingConvention = CallingConvention.ThisCall in the DllImport, but it isn't an exact science. Let's say that it should work for simple cases...
[DllImport("test2.dll", CallingConvention = CallingConvention.ThisCall)]
private static extern int CallMe(IntPtr obj, int y);
where obj is a reference to the C++ object (what in C++ is called this). The difference you get between 32 and 64 bits happens because the place where the this pointer is placed changes between 32 and 64 bits.
For very simple cases, when in truth the this isn't used by the C++ method (and it doesn't use virtual functions), you can pass IntPtr.Zero as the pointer. Normally you would have an extern C method that creates the C++ object "C++ side" and returns an IntPtr (a void*) to C#, and then C# calls the C++ methods passing this IntPtr.
The "brittle" point is that the C++ compiler mangles the name of C++ methods (methods that aren't in an extern "C" block), so you have to "manually" discover them (for example using the DUMPBIN /exports)... And to make things "easier", the mangled names change between 32 and 64 bits :-)
An example:
C++-side:
class Store
{
private:
int value;
public:
__declspec(dllexport) void Put(int value)
{
this->value = value;
}
__declspec(dllexport) int Get()
{
return this->value;
}
__declspec(dllexport) void Increment()
{
this->value++;
}
};
extern "C"
{
__declspec(dllexport) int PlusOne(int x)
{
return x + 1;
}
__declspec(dllexport) Store* NewStore()
{
return new Store;
}
__declspec(dllexport) void DeleteStore(Store* store)
{
delete store;
}
}
class Program
{
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
extern static int PlusOne(int x);
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
extern static IntPtr NewStore();
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
extern static void DeleteStore(IntPtr store);
// EntryPoint generated with DUMPBIN /exports dllname.dll
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?Put#Store##QAEXH#Z")]
extern static void Put32(IntPtr store, int value);
// EntryPoint generated with DUMPBIN /exports dllname.dll
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?Get#Store##QAEHXZ")]
extern static int Get32(IntPtr store);
// EntryPoint generated with DUMPBIN /exports dllname.dll
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?Increment#Store##QAEXXZ")]
extern static void Increment32(IntPtr store);
// EntryPoint generated with DUMPBIN /exports dllname.dll
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?Put#Store##QEAAXH#Z")]
extern static void Put64(IntPtr store, int value);
// EntryPoint generated with DUMPBIN /exports dllname.dll
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?Get#Store##QEAAHXZ")]
extern static int Get64(IntPtr store);
// EntryPoint generated with DUMPBIN /exports dllname.dll
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?Increment#Store##QEAAXXZ")]
extern static void Increment64(IntPtr store);
static void Main(string[] args)
{
int x = PlusOne(1);
Console.WriteLine(x);
IntPtr store = NewStore();
int ret;
if (IntPtr.Size == 8)
{
Put64(store, 5);
Increment64(store);
ret = Get64(store);
}
else
{
Put32(store, 5);
Increment32(store);
ret = Get32(store);
}
Console.WriteLine(ret);
DeleteStore(store);
}
}
i have a simple function in c++ (not a method of a class)
__declspec(dllexport) extern "C" void __stdcall TestFunc();
i try to call it from c#:
[DllImport("ImportTest.dll")]
public static extern void TestFunc();
...
TestFunc();
It throws an "entry point could't be found" exception.
Whats wrong?
Thank you for helping me :)
Try (guessing, that DLL is written in VS)
extern "C" __declspec(dllexport) void __stdcall TestFunc();
That's:
__declspec(dllexport) to notify compiler, that this function is to be exported from the DLL;
extern "C" mainly to prevent function name decorations;
__stdcall, because this is default calling convention if you specify none in [DllImport] directive.
In the future, you can check if your function is exported from DLL using Dll export viewer.
In C++ function , at header(if your function is declared in header) add
extern "C" _declspec(dllexport) void TestFunc();
at the function definition use
_declspec(dllexport) void TestFunc()
{
}
At C# side,you need to declare a function like
[DllImport(#"ImportTest.dll",
EntryPoint = "TestFunc",
ExactSpelling = false,
CallingConvention = CallingConvention.Cdecl)]
static extern void NewTestFunc()
Now use , NewTestFunc()
I am trying to make the absolute simplest minimal example of how to pass strings to and from a C++ DLL in C#.
My C++ looks like this:
using std::string;
extern "C" {
string concat(string a, string b){
return a + b;
}
}
With a header like
using std::string;
extern "C" {
// Returns a + b
__declspec(dllexport) string concat(string a, string b);
}
My C# is
[DllImport("*****.dll", CallingConvention = CallingConvention.Cdecl)]
static extern string concat(string a, string b);
}
And I am calling it with:
Console.WriteLine(concat("a", "b"));
But this gives a System.AccessViolationException. This seems like it out to be the most trivial thing to deal with, but I am completely stuck on it. When I tried to do a similar experiment with a function "Add" that took two doubles and returned a double I had no problems.
You cannot pass a C++ std::string across an interop boundary. You cannot create one of those in your C# code. So your code can never work.
You need to use interop friendly types at the interop boundary. For instance, null-terminated arrays of characters. That works well when you allocate and deallocate the memory in the same module. So, it's simple enough when passing data from C# to C++.
C++
void foo(const char *str)
{
// do something with str
}
C#
[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(string str);
....
foo("bar");
In the other direction you would typically expect the caller to allocate the buffer, into which the callee can write:
C++
void foo(char *str, int len)
{
// write no more than len characters into str
}
C#
[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(StringBuilder str, int len);
....
StringBuilder sb = new StringBuilder(10);
foo(sb, sb.Capacity);
This is the simplest way I like - pass a string in, and use a lambda to get the response
C#
public delegate void ResponseDelegate(string s);
[DllImport(#"MyDLL.dll", EntryPoint ="Foo", CallingConvention = CallingConvention.StdCall)]
public static extern void Foo(string str, ResponseDelegate response);
...
Foo("Input", s =>
{
// response is returned in s - do what you want with it
});
C++
typedef void(_stdcall *LPEXTFUNCRESPOND) (LPCSTR s);
extern "C"
{
__declspec(dllexport) void __stdcall Foo(const char *str, LPEXTFUNCRESPOND respond)
{
// Input is in str
// Put your response in respond()
respond("HELLO");
}
}
I'm trying to use a C++ unmanaged dll in a C# project and I'm getting an error when trying to call a function that says that entry point cannot be found.
public class Program
{
static void Main(string[] args)
{
IntPtr testIntPtr = aaeonAPIOpen(0);
Console.WriteLine(testIntPtr.ToString());
}
[DllImport("aonAPI.dll")]
public static extern unsafe IntPtr aaeonAPIOpen(uint reserved);
}
Here is the dumpbin for the function:
5 4 00001020 ?aaeonAPIOpen##YAPAXK#Z
I changed the dll import to [DllImport("aonAPI.dll", EntryPoint="?aaeonAPIOpen")] and [DllImport("aonAPI.dll", EntryPoint="_aaeonAPIOpen")] and no luck.
Using the undname.exe utility, that symbol demangles to
void * __cdecl aaeonAPIOpen(unsigned long)
Which makes the proper declaration:
[DllImport("aonAPI.dll", EntryPoint="?aaeonAPIOpen##YAPAXK#Z",
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr aaeonAPIOpen(uint reserved);
It looks like the function you're trying to call is compiled as a C++ function and hence has it's name mangled. PInvoke does not support mangled name. You need to add an extern "C" block around the function definition to prevent name mangling
extern "C" {
void* aaeonAPIOpen(uint reserved);
}