C
C#•9mo ago
nope

Using a c++ dll in c#

How can I go about trying to send image via c++ to display on c# I'm trying to do so asynchronously, every time a new image is acquired by the c++ dll I'll send it to the winform to display. If more code is needed to explain this more than willing to share 🙂 just know I haven't wrote anything in c# yet just the dll
56 Replies
reflectronic
reflectronic•9mo ago
Platform Invoke (P/Invoke) - .NET
Learn how to call native functions via P/Invoke in .NET.
reflectronic
reflectronic•9mo ago
you would need to expose a C API from your C++ library then, you can use P/Invoke to call those C API functions from C# you cannot call C++ from C# directly, you need the C API wrappers that only use things like structs and pointers and so on
Angius
Angius•9mo ago
There's a useful source generator, too, that will generate the bunk of the binding code for you https://github.com/microsoft/CsWin32 found it, I think this is the one
reflectronic
reflectronic•9mo ago
that is only for Win32 APIs, it does not help for a custom DLL
Angius
Angius•9mo ago
Ah, whoops, wrong generator then. Lemme search some more Ah, it's built-in...? https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation
nope
nopeOP•9mo ago
okay so how would i set up c in a way that it can use it? do i put the image in a struct and create a c event? sorry if the question is dumb but i'm new to programming
Angius
Angius•9mo ago
If you're new to programming, and apparently, you're making both the C++ part and the C# part... ...why even compilcate things instead of doing everything in one language?
nope
nopeOP•9mo ago
to aquire the images i'm using an sdk, can i use the libraries in c#?
reflectronic
reflectronic•9mo ago
can you show what the functions in the SDK look like
Angius
Angius•9mo ago
Well, depends, what libraries?
nope
nopeOP•9mo ago
what do you want to know about the library? not sure what you mean dlls, libs and header files
Angius
Angius•9mo ago
What does it do, why do you need it
nope
nopeOP•9mo ago
it acquires the image
Angius
Angius•9mo ago
Wgat image, where from?
nope
nopeOP•9mo ago
virtual void image_acquired( uint16_t bank, // Internal image ID, device DDR bank number. uint16_t frame, // Internal image ID, device DDR frame number. int width, // Image width, pixels. int height, // Image height, pixels. const uint16_t* image, // Image, points to widthheight array of uint16_t elements. bool more_available) override // true - another image is available. { UNREFERENCED_PARAMETER(bank); UNREFERENCED_PARAMETER(frame); sync_print("image acquired: ", image_index, " ", width, "", height); ++image_index; if (more_available) { sync_print("more_available = true, it seems that our program is too slow"); return; } // Save one image to current directory - just as example. // Note: in real program is is better to make this asynchronously, by copying an image // to your own storage. Save operation should be executed asynchronously in another thread. // Correct image_acquired implementation should contain only one copy memory call. // // In this example, for symplicity, we save one image synchronously. if (image_index == 16) // just any image index { const char* file_name = "stn_image.raw"; std::string save_result = utilities::save_file(file_name, reinterpret_cast<const uint8_t>(image), widthheightsizeof(uint16_t)); if (save_result.empty()) { sync_print(FUNCTION, " Image is saved: ", file_name); } else { sync_print(FUNCTION, " Error saving image to file ", file_name, ". ", save_result); } } every time an image is acquired it calls image_acquired i used to just save the image like this std::string utilities::save_file( const char file_name, // [in] file name const uint8_t* data, // [in] data to save int size) // [in] data size (bytes) { FILE* f = std::fopen(file_name, "wb"); if (f == nullptr) { std::ostringstream s; s << "Cannot open file " << file_name << ". " << strerror(errno); return s.str(); } std::string result; size_t write_result = std::fwrite(data, sizeof(uint8_t),size, f); if (write_result < static_cast<size_t>(size)) { std::ostringstream s; s << "fwrite failed. write_result " << write_result << " expected " << size << " file name " << file_name; result = s.str(); } fclose(f); return result; } now i'm trying to send it to the c# winform to display instead of saving is this enough info?
reflectronic
reflectronic•9mo ago
if you are new to programming this is going to be quite challenging
nope
nopeOP•9mo ago
i know but i really gotta learn to do it i mean i learned c before c++ but i haven't made a program this complex with it before if anyone even knows the things i need to learn to do it it would be a great help
Angius
Angius•9mo ago
I'd just do the UI in C++ tbh
nope
nopeOP•9mo ago
ohh it's easier that way? then sure
Angius
Angius•9mo ago
Besides that, I guess you could use callbacks
nope
nopeOP•9mo ago
so there is a way to send this image to a "display", how would that work?
Angius
Angius•9mo ago
Maybe. Not sure if a pointer to a function in C++ DLL would translate into a C# Func<>
nope
nopeOP•9mo ago
oh i meant in cpp
Angius
Angius•9mo ago
Well, you would start by making a C++ UI, with something like... dunno what's in use nowadays, ImGUI? QT?
nope
nopeOP•9mo ago
ok, i'm trying to learn qt and it's beating my ass but yeah i'll use that then so going with QT or imgui is smarter Thanks to everyone here. I wanted to do it on c# because of not so good advice I got irl
reflectronic
reflectronic•9mo ago
i don’t think there is anything wrong with the idea, it is just more difficult than using that SDK from C++, and there needs to be a reason to take on that difficulty if you are writing a large application where Windows Forms will be more productive than C++, it is worth spending a little bit of time figuring out the interop so you can use C# instead of C++ if you are just going to display the picture in a window, that is simple enough using a library like Qt that it’s easier to just do it all from C++, it will be a couple hundred lines of code at most
nope
nopeOP•9mo ago
so in a bigger application it's better to use winform? and what i would need to learn is interop? the program was simple, save an image and close but i'm going to build it quite a bit. then it would be better to learn what you callo interop? what i'll do is display basically a video, every time a new image is acquired i'll show to screen, there will be some filters that can be applied on that image and some math stuff done to it so it looks good but i guess that doesn't have much to do with winform, right? std::string save_result = utilities::save_file(file_name, reinterpret_cast<const uint8_t>(image), widthheight*sizeof(uint16_t)); basically i want to do this but inside the c# program, i guess it would be called display file and would put the data in a c# object that would be displayed i already checked dllimport and stuff only thing is idk how i go about a virtual void acuired image sending info to the c# display
reflectronic
reflectronic•9mo ago
most people like using C# over C++. so, if the “application” is going to be a lot of code, it’s probably worth doing the setup so that you can write the application in C# it is probably not that complicated yes, you have to be a little bit clever i cannot really help without knowing what the API looks like, though i’m assuming it’s some kind of abstract class, and you (the person using the SDK) need to override image_acquired, right? and is that all you need? or are there more functions you will need
nope
nopeOP•9mo ago
For now yes, a friend will try to create a couple filters for the images but I don't think that is a problem for c# right? He would change the image pixel by pixel and then send I'd send it in the same way to the winform Yeah, you can see the code above
reflectronic
reflectronic•9mo ago
yeah ok. when i get home i can explain what the interop will look like you will have to write a little bit of your own C++ as the glue code that the C# will have access to but the logic you should be able to do in C# fully
nope
nopeOP•9mo ago
Oh perfect thanks Where did you learn this? Also want to learn so i don't find myself stumped in a similar but different situation
reflectronic
reflectronic•9mo ago
can you explain what you do with the object? like, ok, you have the class which implements image_acquired, but what do you do with it? how do you make it do something
nope
nopeOP•9mo ago
I just saved the image in a raw file for now You can see the code above of how I saved the image I reveibed
reflectronic
reflectronic•9mo ago
yes, what i mean is, how do you hook up the class you made to the library like. you have some kind of class MyThing : public TheThing { virtual void image_acquired(...) { ... } } what do you do with MyThing
nope
nopeOP•9mo ago
Oh it's a class that's present in the library already
reflectronic
reflectronic•9mo ago
that method is your code, though, right? like, you wrote that, that's not the library utilities::save_file etc.
nope
nopeOP•9mo ago
that's already how the image in sent by through the sdk device dev; connect(dev); // Connect bool connect(device& dev) { std::string connect_error; bool connect_result = dev.connect(); // SIXTeen SDK opens log file on any connect call. // We want to print here log file name, both if connect succeeded or failed. // First we need to call stn_device::get_last_error and remember it, since last error information // is overridden on every device call. if (!connect_result) { connect_error = utilities::stn_error_to_string("connect"); } // Print log file name const char* log_file_name{}; if (dev.get_log_file_name(log_file_name)) { sync_print(FUNCTION, " Log file ", log_file_name); } if (!connect_result) { // Print dev.connect error sync_print(connect_error); return false; } // OK sync_print(FUNCTION, " Device is connected"); return true; } bool acquire_images(device& dev) { if (!dev.start_acquisition()) { sync_print(utilities::stn_error_to_string("start_acquisition")); return false; }
// Specifically, device::image_acquired is called as many times as it was set to per second #ifdef _WIN32 sync_print(FUNCTION, " Acquisition started. Press any key to stop"); _getch(); #else sync_print(FUNCTION, " Acquisition started. Enter any string to stop"); std::string s; std::cin >> s; #endif if (!dev.stop_acquisition()) { sync_print(utilities::stn_error_to_string("stop_acquisition")); return false; } sync_print(FUNCTION, " Acquisition stopped"); return true; } start_acquisition is an sdk function it calls device::image acquired @reflectronic makes sense? class device : public stn::stn_device { public: device(){}; virtual ~device(){} private: int image_index{}; protected: virtual void image_acquired(.......
reflectronic
reflectronic•9mo ago
so you just
device dev;
connect(dev);
acquire_images(dev);
device dev;
connect(dev);
acquire_images(dev);
and that's all you need to do?
nope
nopeOP•9mo ago
yeah for now yess maybe it's a stupid question but i'm new to object programming
reflectronic
reflectronic•9mo ago
ok. so, the first thing you would need is a class that allows C# to provide an implementation of image_acquired it is not possible for C# to inherit a C++ class directly, so you need to provide some kind of wrapper in C++ it would look something like this, i think:
class dotnet_device : stn::stn_device
{
public:
using image_acquired_callback = void(uint16_t, uint16_t, int, int, const uint16_t*, bool);

dotnet_device(image_acquired_callback* dotnet_callback)
: dotnet_callback(dotnet_callback)
{
}

private:
image_acquired_callback* dotnet_callback;

protected:
virtual void image_acquired(...) override
{
dotnet_callback(bank, frame, width, height, image, more_available);
}
};
class dotnet_device : stn::stn_device
{
public:
using image_acquired_callback = void(uint16_t, uint16_t, int, int, const uint16_t*, bool);

dotnet_device(image_acquired_callback* dotnet_callback)
: dotnet_callback(dotnet_callback)
{
}

private:
image_acquired_callback* dotnet_callback;

protected:
virtual void image_acquired(...) override
{
dotnet_callback(bank, frame, width, height, image, more_available);
}
};
the image_acquired_callback* is a function pointer--this is what's going to let C# provide the function you will write a function in C# and pass it to this code. then, you can see image_acquired invoke that function pointer. that will allow it to call back into the C# the next thing you will need is a way to create this object. you cannot create or delete a C++ class directly from C#, so you will need some helper functions that are callable from C#
extern "C"
{
__declspec(dllexport) dotnet_device* create_dotnet_device(dotnet_device::image_acquired_callback* callback)
{
try
{
return new dotnet_device(callback);
}
catch (...)
{
return nullptr;
}
}

__declspec(dllexport) void delete_dotnet_device(dotnet_device* device)
{
try
{
delete device;
}
catch (...)
{
}
}
}
extern "C"
{
__declspec(dllexport) dotnet_device* create_dotnet_device(dotnet_device::image_acquired_callback* callback)
{
try
{
return new dotnet_device(callback);
}
catch (...)
{
return nullptr;
}
}

__declspec(dllexport) void delete_dotnet_device(dotnet_device* device)
{
try
{
delete device;
}
catch (...)
{
}
}
}
you can see that the create_dotnet_device takes in the callback (which C# will provide), and then it'll return back a pointer the dotnet_device object. C# won't be able to use this pointer directly, but it'll be able to pass it to other functions which you define. (like, for example, delete_dotnet_device, which will delete the object from memory once you are done with it.) this creates the objects, but you need to actually call the functions start the acquisition. actually, you can probably do this inside of create_dotnet_device... but of course you could split it into a different function. so, that would look like: hm, actually, it's probably better to do it in different functions... so:
extern "C"
{
__declspec(dllexport) bool connect_device(dotnet_device* device)
{
try
{
return device->connect();
}
catch (...)
{
return false;
}
}

__declspec(dllexport) bool start_acquisition(dotnet_device* device)
{
try
{
return device->start_acquisition();
}
catch (...)
{
return false;
}
}

__declspec(dllexport) bool stop_acquisition(dotnet_device* device)
{
try
{
return device->stop_acquisition();
}
catch (...)
{
return false;
}
}
}
extern "C"
{
__declspec(dllexport) bool connect_device(dotnet_device* device)
{
try
{
return device->connect();
}
catch (...)
{
return false;
}
}

__declspec(dllexport) bool start_acquisition(dotnet_device* device)
{
try
{
return device->start_acquisition();
}
catch (...)
{
return false;
}
}

__declspec(dllexport) bool stop_acquisition(dotnet_device* device)
{
try
{
return device->stop_acquisition();
}
catch (...)
{
return false;
}
}
}
(i'm putting these try/catches because i don't know if this library uses exceptions) anyway, this is all you need on the C++ side for the basic interop... you would put all of that code in a C++ project, compile it into a dll file. the extern "C" and __declspec(dllexport) things are the magic sauce that will make these functions available to C# then, on the C# side, you can use P/Invoke to access these dllexported functions
nope
nopeOP•9mo ago
ook so now i can access image_acuired from c#
reflectronic
reflectronic•9mo ago
internal static class NativeMethods
{
[DllImport("StnHelper.dll", EntryPoint = "create_dotnet_device")]
internal static extern StnDeviceHandle CreateDotnetDevice(ImageAcquiredCallback callback);

[DllImport("StnHelper.dll", EntryPoint = "delete_dotnet_device")]
internal static extern void DeleteDotnetDevice(IntPtr device);

[DllImport("StnHelper.dll", EntryPoint = "connect_device")]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool ConnectDevice(StnDeviceHandle device);

[DllImport("StnHelper.dll", EntryPoint = "start_acquisition")]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool StartAcquisition(StnDeviceHandle device);

[DllImport("StnHelper.dll", EntryPoint = "stop_acquisition")]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool StopAcquisition(StnDeviceHandle device);
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal unsafe delegate void ImageAcquiredCallback(ushort bank, ushort frame, int width, int height, ushort* image, [MarshalAs(UnmanagedType.I1)] bool moreAvailable);

internal sealed class StnDeviceHandle : SafeHandle
{
public StnDeviceHandle(IntPtr handle) : base(handle, true)
{
}

public override bool IsInvalid => handle == 0;

protected override bool ReleaseHandle()
{
NativeMethods.DeleteDotnetDevice(handle);
return true;
}
}
internal static class NativeMethods
{
[DllImport("StnHelper.dll", EntryPoint = "create_dotnet_device")]
internal static extern StnDeviceHandle CreateDotnetDevice(ImageAcquiredCallback callback);

[DllImport("StnHelper.dll", EntryPoint = "delete_dotnet_device")]
internal static extern void DeleteDotnetDevice(IntPtr device);

[DllImport("StnHelper.dll", EntryPoint = "connect_device")]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool ConnectDevice(StnDeviceHandle device);

[DllImport("StnHelper.dll", EntryPoint = "start_acquisition")]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool StartAcquisition(StnDeviceHandle device);

[DllImport("StnHelper.dll", EntryPoint = "stop_acquisition")]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool StopAcquisition(StnDeviceHandle device);
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal unsafe delegate void ImageAcquiredCallback(ushort bank, ushort frame, int width, int height, ushort* image, [MarshalAs(UnmanagedType.I1)] bool moreAvailable);

internal sealed class StnDeviceHandle : SafeHandle
{
public StnDeviceHandle(IntPtr handle) : base(handle, true)
{
}

public override bool IsInvalid => handle == 0;

protected override bool ReleaseHandle()
{
NativeMethods.DeleteDotnetDevice(handle);
return true;
}
}
nope
nopeOP•9mo ago
after using P/invoke on the c# side i have to create a function so that when the method acquired_image is called the winform displays the image right?
reflectronic
reflectronic•9mo ago
then, you could write a wrapper class over this:
public sealed class StnDevice : IDisposable
{
private StnDeviceHandle handle;
private ImageAcquiredCallback callback;

public StnDevice()
{
callback = OnImageAcquired;
handle = NativeMethods.CreateDotnetDevice(callback);
if (handle.IsInvalid)
{
throw new Exception("Couldn't create device");
}
}

public void Connect()
{
if (!NativeMethods.ConnectDevice(handle))
{
throw new Exception("Couldn't connect device");
}
}

public void StartAcquisition()
{
if (!NativeMethods.StartAcquisition(handle))
{
throw new Exception("Couldn't start acquisition");
}
}

public void StopAcquisition()
{
if (!NativeMethods.StopAcquisition(handle))
{
throw new Exception("Couldn't stop acquisition");
}
}

private unsafe void OnImageAcquired(ushort bank, ushort frame, int width, int height, ushort* image, bool moreAvailable)
{
Task.Run(() => ImageAcquired?.Invoke(bank, frame, width, height, image, moreAvailable));
}

public event ImageAcquiredCallback ImageAcquired;

public void Dispose()
{
handle.Dispose();
}
}
public sealed class StnDevice : IDisposable
{
private StnDeviceHandle handle;
private ImageAcquiredCallback callback;

public StnDevice()
{
callback = OnImageAcquired;
handle = NativeMethods.CreateDotnetDevice(callback);
if (handle.IsInvalid)
{
throw new Exception("Couldn't create device");
}
}

public void Connect()
{
if (!NativeMethods.ConnectDevice(handle))
{
throw new Exception("Couldn't connect device");
}
}

public void StartAcquisition()
{
if (!NativeMethods.StartAcquisition(handle))
{
throw new Exception("Couldn't start acquisition");
}
}

public void StopAcquisition()
{
if (!NativeMethods.StopAcquisition(handle))
{
throw new Exception("Couldn't stop acquisition");
}
}

private unsafe void OnImageAcquired(ushort bank, ushort frame, int width, int height, ushort* image, bool moreAvailable)
{
Task.Run(() => ImageAcquired?.Invoke(bank, frame, width, height, image, moreAvailable));
}

public event ImageAcquiredCallback ImageAcquired;

public void Dispose()
{
handle.Dispose();
}
}
and, so, finally, the way you would use this class is like this:
var device = new StnDevice();
device.ImageAcquired += Device_ImageAcquired;
device.Connect();
device.StartAcquisition();
// ...

unsafe void Device_ImageAcquired(ushort bank, ushort frame, int width, int height, ushort* image, bool moreAvailable)
{
// do the image processing
}
var device = new StnDevice();
device.ImageAcquired += Device_ImageAcquired;
device.Connect();
device.StartAcquisition();
// ...

unsafe void Device_ImageAcquired(ushort bank, ushort frame, int width, int height, ushort* image, bool moreAvailable)
{
// do the image processing
}
to put it all together you would copy the C++ DLL (which i assumed is named StnHelper.dll, but you can name it whatever you want and change the DllImports) next to the build output of your windows forms and then it would do the thing if there is something you did not quite understand i can explain in more detail
nope
nopeOP•9mo ago
ok, took a moment to process what exactly the code is doing wnh is the class internal? would it not work otherwise?
reflectronic
reflectronic•9mo ago
no, there is no particular reason the wrapper class should mean that you don’t need to use the native functions directly, though
nope
nopeOP•9mo ago
I get a missing method exception
reflectronic
reflectronic•9mo ago
from where
nope
nopeOP•9mo ago
The first one, handle= CreateDotNetDevice(callback)
reflectronic
reflectronic•9mo ago
what does the exception say
nope
nopeOP•9mo ago
It's not giving much info tbh Maybe the issue is in the connection between the cpp and c# code itself because it gives the same error without the cpp code
reflectronic
reflectronic•9mo ago
it definitely does not just say “MissingMethodException” it gives more information
nope
nopeOP•9mo ago
No description
reflectronic
reflectronic•9mo ago
hm. can you replace
public StnDeviceHandle(IntPtr handle) : base(handle, true)
{
}
public StnDeviceHandle(IntPtr handle) : base(handle, true)
{
}
with
public StnDeviceHandle() : base(true)
{
}
public StnDeviceHandle() : base(true)
{
}
. sorry, i meant
public StnDeviceHandle() : base(IntPtr.Zero, true)
{
}
public StnDeviceHandle() : base(IntPtr.Zero, true)
{
}
nope
nopeOP•9mo ago
Now it's dllnotfound exception
reflectronic
reflectronic•9mo ago
yes, now you need to copy the C++ DLL to the C# build output folder
Want results from more Discord servers?
Add your server