I am working on a project and currently have the following structure:
C# WPF project containing the User Interface as well as calls to external methods.
C++ DLL project containing an algorithm.
ASM DLL project containing an algorithm.
For simplicity, let's assume the algorithm simply takes no parameters and returns the sum of two, predefined numbers.
Here's the function signature and implementation in the C++ (second) project:
int Add(int x, int y)
{
return x + y;
}
extern "C" __declspec(dllexport) int RunCpp()
{
int x = 1, y = 2;
int z = Add(x, y);
return z;
}
And here's how I call the function in C#:
[DllImport("Algorithm.Cpp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int RunCpp();
This works just fine - calling the function in C# returns the value 3, everything is working proplerly, no exceptions thrown.
However, I am now struggling to call the ASM procedure in C# code.
I have seen (and tested myself to an extent) that it's impossible to call a MASM DLL directly in C# code. However, I've heard that it's possible to call ASM in C++ and call that function in C#.
1. My first question is - is calling ASM code actually possible directly in C#? When I try that, I get an exception that basically says the binary code is incompatible.
2. I have tried to use C++ to indirectly call the ASM DLL, and while I get no exception, the returned value is "random", as in, it feels like a remainder left in memory, for example: -7514271. Is this something I'm doing wrong, or is there another way to achieve this?
Here's the code for calling ASM in C++:
typedef int(__stdcall* f_MyProc1)(DWORD, DWORD);
extern "C" __declspec(dllexport) int RunAsm()
{
HINSTANCE hGetProcIDDLL = LoadLibrary(L"Algorithm.Asm.dll");
if (hGetProcIDDLL == NULL)
{
return 0;
}
f_MyProc1 MyProc1 = (f_MyProc1)GetProcAddress(hGetProcIDDLL, "MyProc1");
if (!MyProc1)
{
return 0;
}
int x = 1, y = 2;
int z = MyProc1(x, y);
FreeLibrary(hGetProcIDDLL);
return z;
}
Here, the code for calling C++ in C#:
[DllImport("Algorithm.Cpp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int RunAsm();
And here's the ASM code of MyProc1, if needed:
Main.asm:
MyProc1 proc x: DWORD, y: DWORD
mov EAX, x
mov ECX, y
add EAX, ECX
ret
MyProc1 endp
Main.def:
LIBRARY Main
EXPORTS MyProc1
is calling ASM code actually possible directly in C#?
Example of this with two projects, C# and assembly based DLL. Looks like you already know how to get a C++ based DLL working. The project names are the same as the directory names, xcs for C# and xcadll for the dll. I started with empty directories and created empty projects, then moved source files into the directories and then added existing items to each project.
xcadll properties:
Configuration Type: Dynamic Library (.dll)
Linker | Input: xcadll.def
xcadll\xcadll.def:
LIBRARY xcadll
EXPORTS DllMain
EXPORTS Example
xcadll\xa.asm properties (for release build, /Zi is not needed):
General | Excluded From Build: No
General | Item Type: Custom Build Tool
Custom Build Tool | General | Command Line: ml64 /c /Zi /Fo$(OutDir)\xa.obj xa.asm
Custom Build Tool | General | Outputs: $(OutDir)\xa.obj
xcadll\xa.asm:
includelib msvcrtd
includelib oldnames ;optional
.data
.data?
.code
public DllMain
public Example
DllMain proc ;return true
mov rax, 1
ret 0
DllMain endp
Example proc ;[rcx] = 0123456789abcdefh
mov rax, 0123456789abcdefh
mov [rcx],rax
ret 0
Example endp
end
xcs\Program.cs:
using System;
using System.Runtime.InteropServices;
namespace xcadll
{
class Program
{
[DllImport("c:\\xcadll\\x64\\release\\xcadll.dll")]
static extern void Example(ulong[] data);
static void Main(string[] args)
{
ulong[] data = new ulong[4] {0,0,0,0};
Console.WriteLine("{0:X16}", data[0]);
Example(data);
Console.WriteLine("{0:X16}", data[0]);
return;
}
}
}
For debug, use
[DllImport("c:\\xcadll\\x64\\debug\\xcadll.dll")]
xcs properties | debug | enable native mode debugging (check the box)
Related
I am trying to handle a C++ DLL which in turn connects to a C# DLL.
My problem is when I want to send or receive arrays between C++ and C# DLLs.
I have created the C# DLL and C++ DLL, so I can modified all the files. Within the C# DLL, I must necessarily have an array of string an another of double because that is where I make use of other functions unrelated to this question. (The declaration of the function that I have to use in C# DLL is Error SetMultipleChannelValues(string[] names,double[] values).
I am using CLI in Visual Studio 2015 to compile and generate all DLLs and projects.
Here it is the C# code that I have:
public static Error SetMultipleSignals(string[] signals, double[] values)
{
Error error = null;
error = workspace.SetMultipleChannelValues(signals, values);
if (error.IsError)
Console.WriteLine("[DLL] Error in SetMultipleChannelValues(). Code: " + error.Code + ". Message: " + error.Message);
return error;
}
Here it is the C++ code that I have:
bool setMultipleSignals(double* setSignal_values)
{
array<double, DEFAULT_SETSIGNAL_SIZE> values;
for (int index = 0 ; index < DEFAULT_SETSIGNAL_SIZE ;index++)
{
values[index] = *(setSignal_values + index);
}
if (!veristand_wrapper_cs::VeriStand_dll::SetMultipleSignals(setSignals_path, values)) // This is the call to C# function
return true;
else
return false;
}
Here it is the C++ header that I have:
#pragma once
#define VERISTAND_WRAPPER_CPP __declspec(dllexport)
#include <string>
#include <iostream>
#include <windows.h>
#include <array>
using namespace std;
using namespace System;
using System::Runtime::InteropServices::Marshal;
#using "veristand_wrapper_cs.dll" // This is the C# DLL
#define DEFAULT_SETSIGNAL_SIZE 100
extern "C" VERISTAND_WRAPPER_CPP bool setMultipleSignals(double* setSignal_values); // This function is called from API C++
I pass the double array with a pointer to double as a parameter from my C++ application, but I have to pass to C# DLL an array, so I have tried to build a whole array of double before pass it.
The array setSignals_path is created as a global array in this C++ DLL as array<String^, DEFAULT_SETSIGNAL_SIZE> setSignals_path;.
The problem is that the call of C# function provides me an error that says that I can not call the function with these arguments. It expects array<System::String^> ^signals, array<double> ^values and I am passing std::array<System::String^, 100Ui64>, std::array<double, 100Ui64>
The concept of my idea is simple. From the C++ API, pass a pointer to the array of doubles, so that the function of my DLL in C++ passes the array of doubles and strings to the DLL of C#, and it returns the array of modified doubles to be able to indicate to the API from C++ that the values have been modified from that memory address.
Would anyone know how to approach this problem or how to do it in some other way to make it work?
It looks like you are using c++/cli. So you can actually use .Net types in native code.
In C++/cli you need to explicitly declare if a variable is a reference, that is what the ^ is for. Your arrays is declared without this, but your method needs a reference. You will also need to allocate your array on the managed heap. So your array should probably be declared as
array<double>^ values = gcnew array< double>(DEFAULT_SETSIGNAL_SIZE);
But it was a while ago I coded c++/cli, so I may have missed something. See How to use arrays in c++/cli. It also looks like you may be able to use ref new instead of gcnew
Use following. The size of array must be 100, or add another parameter before the string array indicating length. The string in code is a byte array terminating with '\0'. c++ must use a method that will read the null terminated string. A double is 8 bytes so the 100 double can be consecutive memory locations (800 bytes).
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct StringArray
{
public IntPtr[] unmanagedArray;
}
public struct DoubleArray
{
public double[] values;
}
public static Error SetMultipleSignals(IntPtr signals, IntPtr values)
{
const int DEFAULT_SETSIGNAL_SIZE = 100;
StringArray stringStruct = new StringArray();
stringStruct.unmanagedArray = new IntPtr[DEFAULT_SETSIGNAL_SIZE];
Marshal.PtrToStructure(signals, stringStruct);
string[] strArray = new string[DEFAULT_SETSIGNAL_SIZE];
for(int i = 0; i < DEFAULT_SETSIGNAL_SIZE; i++)
{
strArray[i] = Marshal.PtrToStringAnsi(stringStruct.unmanagedArray[i]);
}
DoubleArray dArray = new DoubleArray();
dArray.values = new double[DEFAULT_SETSIGNAL_SIZE];
dArray = Marshal.PtrToStructure<DoubleArray>(values);
return null;
Here is going the other way.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct StringArray
{
public IntPtr[] unmanagedArray;
}
static void Main(string[] args)
{
string[] strArray = { "Message 1", "Message 2", "Message 3"};
StringArray stringStruct = new StringArray();
stringStruct.unmanagedArray = new IntPtr[strArray.Length];
for (int i = 0; i < strArray.Length; i++)
{
stringStruct.unmanagedArray[i] = Marshal.StringToBSTR(strArray[i]);
}
IntPtr unmanagedArray = Marshal.AllocHGlobal(Marshal.SizeOf(stringStruct));
Marshal.StructureToPtr(stringStruct, unmanagedArray, false);
}
I'm pasting a small example code that calls Fortran from C# below. The larger problem on which this small reproducible example is based was previously published as 32-bit. More recently, I have need of getting the published code working in 64-bit, and that's when it was discovered that 64-bit is not working.
The code's development has been in Visual Studio, with Intel Visual Fortran installed. The .sln file has 2 projects - C# as the "startup project" and an Intel Visual Fortran project that is compiled as a DLL and called by the C# project. From the .sln file's properties page, setting the "Configuration" as 32-bit (x86) has worked well. However, if instead x64 is used for both the C# and Fortran, the following error message results:
System.DllNotFoundException: 'Unable to load DLL 'passStr_Fortran_C.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)'
My hope is that there is a flag or setting that simply needs to be toggled? If it is more complicated than that, my hope is that someone could lay out the modifications that need to take place (perhaps with the example problem provided below)? Here are the two codes that form the basis of the C# and Fortran projects that work with x86 (win32) but not x64.
C#:
using System;
using System.Runtime.InteropServices;
public static class Program
{
public static int Nsegshold;
public static double[] Diversions = new double[1];
public static int Process_mode;
//Fortran DLL interface
[DllImport("passStr_Fortran_C.dll", CallingConvention = CallingConvention.Cdecl)] // CharSet = CharSet.Ansi,
public static extern void TEST_Var(ref int Process_mode, ref int Nsegshold, [In, Out] double[] Diversions);
public static void Main(string[] args)
{
Process_mode = 1;
Nsegshold = 1;
TEST_Var(ref Process_mode, ref Nsegshold, Diversions);
// Redimension arrays to size determined in fortran
Diversions = (double[])ResizeArray(Diversions, new int[] { Nsegshold });
Process_mode = 2;
TEST_Var(ref Process_mode, ref Nsegshold, Diversions);
Console.WriteLine("Two calls to fortran complete. ");
}
private static Array ResizeArray(Array arr, int[] newSizes)
{
if (newSizes.Length != arr.Rank)
throw new ArgumentException("arr must have the same number of dimensions " +
"as there are elements in newSizes", "newSizes");
var temp = Array.CreateInstance(arr.GetType().GetElementType(), newSizes);
int length = arr.Length <= temp.Length ? arr.Length : temp.Length;
Array.ConstrainedCopy(arr, 0, temp, 0, length);
return temp;
}
}
Fortran:
!
MODULE Fortran_C
!
CONTAINS
!
SUBROUTINE TEST_Var(Process_mode, Nsegshold, Diversions) BIND(C,NAME="TEST_Var")
!
!DEC$ ATTRIBUTES DLLEXPORT :: TEST_Var
!
INTEGER, INTENT(IN) :: Process_mode
INTEGER, INTENT(INOUT) :: Nsegshold
DOUBLE PRECISION, INTENT(INOUT) :: Diversions(Nsegshold)
!
INTEGER :: i
!
IF(Process_mode.eq.1) THEN
Nsegshold = 100
ELSE IF(Process_mode.eq.2) THEN
DO i = 1, Nsegshold
Diversions(i) = 3.14 * i
ENDDO
ENDIF
!
END SUBROUTINE TEST_Var
!
END MODULE Fortran_C
I'm currently working on an C# (.NET Framework 4.7.2) application using some business logic from an unmanaged C++ library. I try to pass data (interop) back and forth from C# to C++. I may not use C++/CLI, no common language runtime allowed in my project.
It works fine for int. Unfortunately as soon as I try to send another datatype I'm getting an conversion error e.g. float 4.2f becomes 1 and string "fourtytwo" turns into -1529101360.
My C# code looks like this:
// works fine, creates an instance of TestClass
var test = TestProxy.Wrapper_Create("test");
// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);
// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);
// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");
My C# Interop Proxy class to call the native (unmanaged) C++ code looks like this:
public static class TestProxy
{
private const string coreDLL = "test.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrapper_Create(string name);
[DllImport(coreDLL, EntryPoint = "?TryInt#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryInt(IntPtr instance, int n);
[DllImport(coreDLL, EntryPoint = "?TryFloat#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryFloat(IntPtr instance, float n);
[DllImport(coreDLL, EntryPoint = "?TryString#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryString(IntPtr instance, string n);
}
My native (unmanaged) C++ looks like that:
the header file:
#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif
#pragma once
extern "C"
{
class TESTCORE_API TestClass
{
private:
char* name;
public:
TestClass(char*);
int TryInt(int);
float TryFloat(float);
char* TryString(char*);
};
TESTCORE_API TestClass* Wrapper_Create(char* name);
}
the implementation file:
#include "stdafx.h"
#include "TESTCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
int TestClass::TryInt(int n)
{
return n; // works fine
}
float TestClass::TryFloat(float n)
{
return n; // something goes wrong here
}
char* TestClass::TryString(char* n)
{
return n; // something goes wrong here
}
extern "C"
{
TESTCORE_API TestClass * Wrapper_Create(char* name)
{
return new TestClass(name);
}
TESTCORE_API int TryInt(TestClass * instance, int n)
{
if (instance != NULL)
{
return instance->TryInt(n);
}
}
TESTCORE_API float TryFloat(TestClass * instance, float n)
{
if (instance != NULL)
{
return instance->TryFloat(n);
}
}
TESTCORE_API char* TryString(TestClass * instance, char* n)
{
if (instance != NULL)
{
return instance->TryString(n);
}
}
}
Do you know how to correctly marshal float, string from C# to C++ and back?
Thank you!
C++ doesn't have standard ABI. It's rarely a good idea to use C++ classes across DLLs, even when you have same language on both sides.
There're better ways.
Replace your __thiscall class methods with global functions, cdecl or stdcall whichever you like (but note C# and C++ have different defaults, if you'll do nothing C++ will use cdecl, C# will import as stdcall). You can pass "this" pointer of the class in the first argument, IntPtr in C#, just like you're doing now. Also if you'll write extern "C" or use a module definition file, they will have human-readable names.
If you want objects, use COM. Declare an interface that inherits from IUnknown, implement it in C++ (I usually use ATL), and export a global function to create an instance of that object (2 lines in ATL, CComObject<T>::CreateInstance followed by AddRef). No need to register, type libraries, you just need to implement IUnknown (but see this if you want to use them from multiple threads)
Update: strings are indeed harder. Apply [MarshalAs(UnmanagedType.LPTStr)] to the argument. Apply [return: MarshalAs(UnmanagedType.LPTStr)] to the function. Specify PreserveSig=true in your DllImport. Finally, modify the C++ code to return a copy of the string, i.e. call strlen then CoTaskMemAlloc (don't forget about the '\0') then strcpy.
Easier way to deal with strings is like this:
HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )
At least there're CComBSTR and _bstr_t built-in classes to deal with memory management.
I'm translating a program from VB to C# which communicates with an external program MMTTY.EXE. This program has ActiveX controls for VB6.0 and I added to my C# project in Visual Studio 2013.
The code in VB is this:
Dim m_nmmr(63) As Long
Private Sub XMMR_OnNotifyNMMR(pNMMR As Long)
Call CopyMemory(m_nmmr(0), pNMMR, 64 * 4) 'Windows API CopyMemory()*
This event is produced when MMTTY.EXE have data and the data pointed by pNMMR is copied in m_nmmr(63) buffer.
The program in C# that I made is this:
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
static extern void CopyMemory(Int32[] Destination, IntPtr Source, uint Length);
private void XMMR_OnNotifyNMMR(object sender, AxXMMTLib._DXMMREvents_OnNotifyNMMREvent e)
{
IntPtr ptr = (IntPtr)e.pNMMR;
Int32[] m_nmmr = new Int32[63];
Marshal.Copy(ptr, m_nmmr, 0, 63);
}*
But when I execute it I get an AccessViolationException. It tells me that there is an attempt of writing or reading in a protected memory.
How can I solve this problem? any idea?
This is the help of the original method for VB6.0:
void OnNotifyNMMR(long* pNMMR)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This event is generated at the timing of TXM_LEVLEL message arrival. NMMR struct pointed by pNMMR is defined by
#pragma pack(push, 1)
typedef struct {
DWORD m_markfreq;
DWORD m_spacefreq;
DWORD m_siglevel;
DWORD m_sqlevel;
DWORD m_codeswitch;
DWORD m_codeview;
DWORD m_notch1;
DWORD m_notch2;
DWORD m_baud;
DWORD m_fig;
DWORD m_radiofreq;
DWORD m_Reserved[53];
}NMMR;
#pragma pack(pop)
the application does not have to respond all the XMMR events if it uses this struct. Because NMMR struct simply consists of LONG variables, it can be copied to VB Long array. The index of the array has several names, such as xr_markfreq, in XMMT.ocx. For details, refer to Predefined constants of XMMR.
[Example]
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Dim m_nmmr(63) As Long
Private Sub XMMR_OnNotifyNMMR(pNMMR As Long)
Call CopyMemory(m_nmmr(0), pNMMR, 64 * 4) 'Windows API CopyMemory()
|
MarkFreq = m_nmmr(xr_markfreq)
SpaceFreq = m_nmmr(xr_spacefreq)
|
'Pass pNMMR to the control for the supplemental control
Call XMMSpec.UpdateByNMMR(pNMMR) 'Update the frequency property of XMMSpec control
Call XMMLvl.DrawByNMMR(pNMMR) 'Draw the level indicator
End Sub
The 63 is the Problem:
In VB6, 63 is to top index (0..63).
in C#, 63 is the COUNT of elements (0..62).
So you miss 4 Bytes. You can simple check this with a small console program, breakpoint at first assignment:
static void Main(string[] args)
{
int[] test = new int[1];
test[0] = 1;
test[1] = 2; // <- this will crash
}
I just spent the last week or so figuring out how to execute C++ code from C# as part of my day job. It took us forever to figure it out, but the final solution is fairly simple.
Now I'm curious... How hard would it be to call Haskell from C#? (Note carefully: That's call Haskell from C#, not the other way around. So the main executable is C#.)
If it's really hard, I won't bother. But if it's reasonably easy, I might have to have a play with it...
Basically, we wrote some C++ code. On Windows it gets compiled into a DLL, on Linux it gets compiled into a shared object (*.so). Then on the C# side you do a DllImport and write some manual memory management code if you're trying to pass anything nontrivial across. (E.g., arrays, strings, etc.)
I know GHC is supposed to support building shared libraries on both platforms, but I'm not sure of the technical details. What's the syntax for exporting stuff, and does the caller have to do anything special to initialise the DLL first?
To be concrete: Suppose there exists a function foobar :: FilePath -> IO Int32. Can somebody throw together a small sketch showing:
What Haskell declarations I need to write to expose this to the outside world.
How do I tell GHC to build a single self-contained DLL / SO file.
Anything special the caller needs to do, beyond the usual process of binding foobar itself.
I'm not too worried about the actual syntax for the C# side; I think I've more or less puzzled that out.
P.S. I did briefly look at hs-dotnet, but this appears to be Windows-specific. (I.e., won't work with Mono, so won't work on Linux.)
As far as both languages are concerned, you can basically pretend you're trying to interface with C code.
This is a complex topic, so rather than try to explain all of it, I will focus on making a simple example that you can build on using the resources linked below.
First, you need to write wrappers for your Haskell functions that use types from the Foreign.C.* modules instead of the usual haskell types. CInt instead of Int, CString instead of String, etc. This is the most complicated step, especially when you have to deal with user-defined types.
You also have to write foreign export declarations for those functions using the ForeignFunctionInterface extension.
{-# LANGUAGE ForeignFunctionInterface #-}
module Foo where
import Foreign.C.String
import Foreign.C.Types
foreign export ccall
foo :: CString -> IO CInt
foo :: CString -> IO CInt
foo c_str = do
str <- peekCString c_str
result <- hs_foo str
return $ fromIntegral result
hs_foo :: String -> IO Int
hs_foo str = do
putStrLn $ "Hello, " ++ str
return (length str + 42)
Then, when compiling, you tell GHC to make a shared library:
$ ghc -O2 --make -no-hs-main -optl '-shared' -o Foo.so Foo.hs
From the C# side, in addition to importing the function you want to call, you also have to import hs_init() and call it to initialize the runtime system before you can call any Haskell functions. You should also call hs_exit() when you're done.
using System;
using System.Runtime.InteropServices;
namespace Foo {
class MainClass {
[DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)]
private static extern void hs_init(IntPtr argc, IntPtr argv);
[DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)]
private static extern void hs_exit();
[DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)]
private static extern int foo(string str);
public static void Main(string[] args) {
Console.WriteLine("Initializing runtime...");
hs_init(IntPtr.Zero, IntPtr.Zero);
try {
Console.WriteLine("Calling to Haskell...");
int result = foo("C#");
Console.WriteLine("Got result: {0}", result);
} finally {
Console.WriteLine("Exiting runtime...");
hs_exit();
}
}
}
}
Now we compile and run:
$ mcs -unsafe Foo.cs
$ LD_LIBRARY_PATH=. mono Foo.exe
Initializing runtime...
Calling to Haskell...
Hello, C#
Got result: 44
Exiting runtime...
It works!
Resources:
GHC User's Guide
HaskellWiki
For reference, I was able to get the following procedure to work under Windows...
{-# LANGUAGE ForeignFunctionInterface #-}
module Fibonacci () where
import Data.Word
import Foreign.C.Types
fibs :: [Word32]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
fibonacci :: Word8 -> Word32
fibonacci n =
if n > 47
then 0
else fibs !! (fromIntegral n)
c_fibonacci :: CUChar -> CUInt
c_fibonacci (CUChar n) = CUInt (fibonacci n)
foreign export ccall c_fibonacci :: CUChar -> CUInt
Compile this with
ghc --make -shared Fibonacci.hs
This produces half a dozen files, one of which is HSdll.dll. I then copied that into a Visual Studio C# project, and did the following:
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
public sealed class Fibonacci : IDisposable
{
#region DLL imports
[DllImport("HSdll.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern unsafe void hs_init(IntPtr argc, IntPtr argv);
[DllImport("HSdll.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern unsafe void hs_exit();
[DllImport("HSdll.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern UInt32 c_fibonacci(byte i);
#endregion
#region Public interface
public Fibonacci()
{
Console.WriteLine("Initialising DLL...");
unsafe { hs_init(IntPtr.Zero, IntPtr.Zero); }
}
public void Dispose()
{
Console.WriteLine("Shutting down DLL...");
unsafe { hs_exit(); }
}
public UInt32 fibonacci(byte i)
{
Console.WriteLine(string.Format("Calling c_fibonacci({0})...", i));
var result = c_fibonacci(i);
Console.WriteLine(string.Format("Result = {0}", result));
return result;
}
#endregion
}
}
The Console.WriteLine() calls are obviously optional.
I haven't tried running this under Mono / Linux yet, but it's presumably similar.
In summary, it's approximately the same difficulty as getting a C++ DLL to work. (I.e., getting the type signatures to match up and making marshaling work correctly is the hard bit.)
I also had to edit the project settings and select "allow unsafe code".