C#/.Net/Mono bindings
We are interested in doing other language bindings for SoapySDR so that anyone can get involved in SDR using their favourite language, tools, and libraries. We may already have a volunteer to tackle C# bindings for SoapySDR, but first we need to know if there is any interest. Please let us know in the comments. Thanks!
:+1: Would also be interested in a UWP Component for Windows 10.
Would be interested in using C# mono bindings in Xamarin.
Yes I'm interested in C# interface to SoapySDR (and in turn to SDRPlay RSP2). I'd be happy to help but I've never done anything like this before. If someone would help me get started. I was able to build SoapySDR dlls using Windows 10 VS2015.
@guruofquality , do you know if a c# has been created for soapy? Or a java one (which should be close)? I've searched but found nothing for soapy itself.
I saw there was a semi-old project of java binding for Pothos also, but I'd be more interested in the soapy layer.
Not that I know of. Given that SoapySDR bindings for python are basically swig based, I think we could get away with another swig .i file and some build glue for C# or java (which I do think are similar). It would be a lot simpler than the way Pothos handles bindings (because he calls into the language from c++ runtime). Any volunteers? :-)
@guruofquality , I've tried to create something, but I'm afraid that's very very alpha, as I'm 100% new to swig. Would you mind helping me on that?
Here is my SoapySDR.i, copied and adapted from the python one: `// Copyright (c) 2014-2017 Josh Blum // Copyright (c) 2016-2016 Bastille Networks // SPDX-License-Identifier: BSL-1.0
%module SoapySDR
//////////////////////////////////////////////////////////////////////// // Include all major headers to compile against //////////////////////////////////////////////////////////////////////// %{ #include <SoapySDR/Version.hpp> #include <SoapySDR/Modules.hpp> #include <SoapySDR/Device.hpp> #include <SoapySDR/Errors.hpp> #include <SoapySDR/Formats.hpp> #include <SoapySDR/Time.hpp> #include <SoapySDR/Logger.hpp> %}
//////////////////////////////////////////////////////////////////////// // http://www.swig.org/Doc2.0/Library.html#Library_stl_exceptions //////////////////////////////////////////////////////////////////////// %include <exception.i>
%exception { try{$action} catch (const std::exception &ex) {SWIG_exception(SWIG_RuntimeError, ex.what());} catch (...) {SWIG_exception(SWIG_RuntimeError, "unknown");} }
//////////////////////////////////////////////////////////////////////// // Config header defines API export //////////////////////////////////////////////////////////////////////// %include <SoapySDR/Config.h>
//////////////////////////////////////////////////////////////////////// // Commonly used data types //////////////////////////////////////////////////////////////////////// // %include <std_complex.i> %include <std_string.i> %include <std_vector.i> %include <std_map.i> %include <SoapySDR/Types.hpp>
//handle arm 32-bit case where size_t and unsigned are the same
#ifdef SIZE_T_IS_UNSIGNED_INT
%typedef unsigned int size_t;
#else
%template(SoapySDRUnsignedList) std::vector
%template(SoapySDRKwargs) std::map<std::string, std::string>;
%template(SoapySDRKwargsList) std::vectorSoapySDR::Kwargs;
%template(SoapySDRArgInfoList) std::vectorSoapySDR::ArgInfo;
%template(SoapySDRStringList) std::vectorstd::string;
%template(SoapySDRRangeList) std::vectorSoapySDR::Range;
%template(SoapySDRSizeList) std::vector<size_t>;
%template(SoapySDRDoubleList) std::vector
// %extend std::map<std::string, std::string> // { // %insert("proxycode") // %{ // def str(self): // Change into ToString() // out = list() // for k, v in self.iteritems(): // out.append("%s=%s"%(k, v)) // return '{'+(', '.join(out))+'}' // %} // };
// %extend SoapySDR::Range // { // %insert("proxycode") // %{ // def str(self): // Change into ToString() // fields = [self.minimum(), self.maximum()] // if self.step() != 0.0: fields.append(self.step()) // return ', '.join(['%g'%f for f in fields]) // %} // };
//////////////////////////////////////////////////////////////////////// // Stream result class // Helps us deal with stream calls that return by reference //////////////////////////////////////////////////////////////////////// %inline %{ struct StreamResult { StreamResult(void): ret(0), flags(0), timeNs(0), chanMask(0){} int ret; int flags; long long timeNs; size_t chanMask; }; %}
// %extend StreamResult // { // %insert("proxycode") // %{ // def str(self) // Change into ToString() // return "ret=%s, flags=%s, timeNs=%s"%(self.ret, self.flags, self.timeNs) // %} // };
//////////////////////////////////////////////////////////////////////// // Constants SOAPY_SDR_* //////////////////////////////////////////////////////////////////////// %include <SoapySDR/Constants.h> %include <SoapySDR/Errors.h> %include <SoapySDR/Version.h> %include <SoapySDR/Formats.h>
%ignore SoapySDR_logf; %ignore SoapySDR_vlogf; %ignore SoapySDR_registerLogHandler; %include <SoapySDR/Logger.h>
//////////////////////////////////////////////////////////////////////// // Utility functions //////////////////////////////////////////////////////////////////////// %include <SoapySDR/Errors.hpp> %include <SoapySDR/Version.hpp> %include <SoapySDR/Modules.hpp> %include <SoapySDR/Formats.hpp> %include <SoapySDR/Time.hpp>
%ignore SoapySDR::logf; %ignore SoapySDR::vlogf; %ignore SoapySDR::registerLogHandler; %include <SoapySDR/Logger.hpp>
//////////////////////////////////////////////////////////////////////// // Device object //////////////////////////////////////////////////////////////////////// // %nodefaultctor SoapySDR::Device; %include <SoapySDR/Device.hpp>
// //global factory lock support // %proxycode %{
// all = list() // for key in sorted(globals().keys()): // if key.startswith('SOAPY_SDR_'): // all.append(key) // %}
// //make device a constructable class // %insert("proxycode") // %{ // _Device = Device // class Device(Device): // Device Create(*args, **kwargs) // { // return Device.make(*args, **kwargs) // }
// def extractBuffPointer(buff): // if hasattr(buff, 'array_interface'): return buff.array_interface['data'][0] // if hasattr(buff, 'buffer_info'): return buff.buffer_info()[0] // if hasattr(buff, 'long'): return long(buff) // if hasattr(buff, 'int'): return int(buff) // raise Exception("Unrecognized data format: " + str(type(buff))) // %}
%extend SoapySDR::Device { StreamResult readStream__(SoapySDR::Stream *stream, const std::vector<size_t> &buffs, const size_t numElems, const int flags, const long timeoutUs) { StreamResult sr; sr.flags = flags; std::vector<void *> ptrs(buffs.size()); for (size_t i = 0; i < buffs.size(); i++) ptrs[i] = (void *)buffs[i]; sr.ret = self->readStream(stream, (&ptrs[0]), numElems, sr.flags, sr.timeNs, timeoutUs); return sr; }
StreamResult writeStream__(SoapySDR::Stream *stream, const std::vector<size_t> &buffs, const size_t numElems, const int flags, const long long timeNs, const long timeoutUs)
{
StreamResult sr;
sr.flags = flags;
std::vector<const void *> ptrs(buffs.size());
for (size_t i = 0; i < buffs.size(); i++) ptrs[i] = (const void *)buffs[i];
sr.ret = self->writeStream(stream, (&ptrs[0]), numElems, sr.flags, timeNs, timeoutUs);
return sr;
}
StreamResult readStreamStatus__(SoapySDR::Stream *stream, const long timeoutUs)
{
StreamResult sr;
sr.ret = self->readStreamStatus(stream, sr.chanMask, sr.flags, sr.timeNs, timeoutUs);
return sr;
}
// %insert("proxycode")
// %{
// #call unmake from custom deleter
// def __del__(self):
// Device.unmake(self)
// def __str__(self):
// return "%s:%s"%(self.getDriverKey(), self.getHardwareKey())
// def readStream(self, stream, buffs, numElems, flags = 0, timeoutUs = 100000):
// ptrs = [extractBuffPointer(b) for b in buffs]
// return self.readStream__(stream, ptrs, numElems, flags, timeoutUs)
// def writeStream(self, stream, buffs, numElems, flags = 0, timeNs = 0, timeoutUs = 100000):
// ptrs = [extractBuffPointer(b) for b in buffs]
// return self.writeStream__(stream, ptrs, numElems, flags, timeNs, timeoutUs)
// def readStreamStatus(self, stream, timeoutUs = 100000):
// return self.readStreamStatus__(stream, timeoutUs)
// %}
}; `
Yes, I’m interested in calling the LimeSDR Mini from C#. Target platform will be .NET 6 on Raspberry Pi 4. Did the SWIG file above ever get tested? Did any bindings ever get generated? Did the API change much since the file above was written?
Hi @M0LTE , our needs changed in the meantime and we went coding directly in C, so I didn't go further on the C# binding. What I had done looked promising, but I never tested it. As the API seldom changes (almost never I'd say), I encourage you to build up on it and to share with others.
https://github.com/pothosware/SoapySDR/tree/wip/csharp
I have .NET bindings in the works, but due to CMake (and sanity) limitations, it's only buildable with MSVC.
It's mostly done, and I don't anticipate too much C#-specific API churn, but no guarantees on that.
ncorgan - Thanks for the timely update - I am working on a few projects now with Pluto and SDRPlay devices and it would be good for me to have the same 'hook' to them via Soapy. Right now I am using their respective DLL's. I (we) appreciate your work on this. I use C# because its: Fast, easy, powerful, and reliable. Plus it's supported and cost effective.
Closing, support was recently merged in master.