Reputation: 2030
I am currently learning C++ (for eventually creating DLL's for Hooks). I have strengths in C# and would like to access the DLL's via pInvoke/DllImports.
I am using VS 2019 (and .NET Framework 4.7.2). As a sample for learning purposes, I have replicated the DLL as well as the C++ (console app) as described here: https://learn.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp?view=vs-2019. This is working perfectly fine (as expected).
Now when I start trying to import the DLL into a C# Project I am running into problems (which I believe are DllImport related)
In the sample (c++) console app (which is working):
int main()
{
// Initialize a Fibonacci relation sequence.
fibonacci_init(1, 1);
// Write out the sequence values until overflow.
do {
std::cout << fibonacci_index() << ": " << fibonacci_current() << std::endl;
} while (fibonacci_next());
// Report count of values written before overflow.
std::cout << fibonacci_index() + 1 << " Fibonacci sequence values fit in an " << "unsigned 64-bit integer." << std::endl;
}
In the C# test console app (not working properly):
class Program
{
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void fibonacci_init(ulong a, ulong b);
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool fibonacci_next();
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern ulong fibonacci_current();
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern uint fibonacci_index();
static void Main(string[] args)
{
// Initialize a Fibonacci relation sequence.
fibonacci_init(1, 1);
// Write out the sequence values until overflow.
do {
Console.WriteLine($"{fibonacci_index()}: {fibonacci_current()}");
} while (fibonacci_next());
// Report count of values written before overflow.
Console.WriteLine($"{fibonacci_index() + 1} Fibonacci sequence values fit in an unsigned 64-bit integer.");
}
}
In the working (C++) app, the loop reaches 92 and prints
92: 12200160415121876738
and then exits the loop and prints 93 Fibonacci sequence values fit in an unsigned 64-bit integer.
However, in the failing (C#) app, it works up until the 92: 12200160415121876738
, but rather than exiting the loop, fabonacci_next()
continues to return true
and it keeps looping the line 92: 12200160415121876738
(no more increments, and never exits).
For the DllImport in C# I have tried both:
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool fibonacci_next();
and:
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
public static extern bool fibonacci_next();
Same results either way. If someone could explain to me what I am doing wrong with the DllImport (causing it not to ever return true, when the same DLL works properly when linked to a C++ console app).
--- UPDATE ---
So the solution to the example above changing [return: MarshalAs(UnmanagedType.Bool)]
to [return: MarshalAs(UnmanagedType.I1)]
does work (with further important related information provided in the links given by GSerg in the comments below).
--- UPDATE 2 ---
(@David) I did intentionally (and incorrectly) leave out the exports as they were in the link I provided to the original DLL 'walkthrough' (my apologies). For completeness of the question the exports are:
#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif
extern "C" MATHLIBRARY_API void fibonacci_init(unsigned long long a, unsigned long long b);
extern "C" MATHLIBRARY_API bool fibonacci_next();
extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();
extern "C" MATHLIBRARY_API unsigned fibonacci_index();
Upvotes: 1
Views: 1345
Reputation: 2030
tldr;
The solution was to simply change the DllImport for the fibonacci_next() import to:
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool fibonacci_next();
A little extra:
Technically that was all that was necessary to answer this question. For those who found this question under similar terms, I am adding a bit more info below, as the real point of the question was more about understanding and learning, as opposed to copying and pasting other peoples code and trying to get it to work.
Starting with the comment by GSerg:
C++'s bool is not C#'s bool. UnmanagedType.Bool is C++'s BOOL, not bool.
I was using this list when converting data types:
Table below taken from Converting C++ Data Types to C# (I am sure there are more official conversion charts around)
C++ Type C# Type Size -------- ------- ---- BOOL bool 1 byte BYTE byte 1 byte CHAR byte 1 byte DECIMAL Decimal 16 bytes DOUBLE double 8 bytes DWORD uint, UInt32 4 bytes FLOAT float, single 4 bytes INT, signed int int, Int32 4 bytes INT16, signed short int short, Int16 2 bytes INT32, signed int int, Int32 4 bytes INT64 long, Int64 8 bytes LONG int, Int32 4 bytes LONG32, signed int int, Int32 4 bytes LONG64 long, Int64 8 bytes LONGLONG long, Int64 8 bytes SHORT, signed short int short, Int16 2 bytes UCHAR, unsigned char byte 1 byte UINT, unsigned int uint, UInt32 4 bytes UINT16, WORD ushort, UInt16 2 bytes UINT32, unsigned int uint, UInt32 4 bytes UINT64 ulong, UInt64 8 bytes ULONG, unsigned long uint, UInt32 4 bytes ULONG32 uint, UInt32 4 bytes ULONG64 ulong, UInt64 8 bytes ULONGLONG ulong, UInt64 8 bytes WORD ushort 2 bytes void*, pointers IntPtr x86=4 bytes, x64=8 bytes
As I am just starting to learn a little bit of C++, I made the assumption that (c++) BOOL in the list above was equivalent to (c++) bool, and when not having [return: MarshalAs(UnmanagedType.Bool)]
was failing I tried adding that, which was also failing.
Quick note for more information on UnmanagedTypes see Microsoft: UnmanagedType Enum :
UnmanagedType.I1 : A 1-byte signed integer. You can use this member to transform a Boolean value into a 1-byte, C-style bool (true = 1, false = 0).
One further bit I liked was in the accepted solution of https://stackoverflow.com/a/32115697/716296 which is not exactly relevant to this question, as I was not really trying to change the c++ side of things, rather using it as a basic snippet of code to learn how things fit together cross language.
It's also possible to use a ref argument instead of a return value. This is in fact a common pattern in unmanaged interop:
I have seen this "common pattern" used quite often working with 3rd party tools that wrap unmanaged libraries. Of course, if I am writing my own unmanaged dll's / libraries, I am going to write them to best fit their purpose and usage, unlike the original question which was just a tool to start learning how.
I hope this answer helps a few people (I am terrible at terminology) but the question was pretty basic, and I not only got the answer but found some good references for expanding now as well.
Upvotes: 1