I am trying to use a Golang library that is compiled as a shared C library in my .NET application. It's compiled with the following command: go build --buildmode=c-shared -o main.dll
My Golang code looks like this:
func DoRequest(request *C.char) *C.char {
// ...
// b = []byte
return (*C.char)(unsafe.Pointer(&b[0]))
}
How can I get this string back into a usable form in the .NET world? I've tried this but I get a panic from Go:
[DllImport("C:\\Users\\GolandProjects\\awesomeProject\\main.dll", EntryPoint = "DoRequest", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
extern static string DoRequest(byte[] requestJsonString);
static void Main(string[] args)
{
var result = DoRequest(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new Request
{
// ...
})));
Console.ReadLine();
}
Please start with thoroughly reading the doc on cgo.
Strings in Go are not NUL-terminated, so there's thre ways to gateway a Go string across a C-compatible shared library barrier:
Copy it to a C-style NUL-terminated string—yielding a pointer to the first character of a copy, and passing it.
Pass the pointer to the 1st character of a Go string, and then also explicitly pass the string length along with it.
Teach "the other side" about the native Go's string representation and use it. A string in Go is a (packed) struct containing a pointer and a pointer-sized integer.
Related
I am trying to build a WPF GUI to control IXIA interface which is written in TCL (IXIA is application which control hardware), TCL version is 86.
For this purpose, I have created the following:
C# project which consists of
TCL_API.cs class - contains the tcl DLLs.
IXIA.cs class - Contains all methods to access IXIA additianlly the evalScript method that invoke the TCL_API.cs class which is handle with the TCL commands.
WPF in visual Studio 2013. With calling methods to IXIA.cs.
The problem is that one of TCL commands get an error "can not find channel named "stdout".
As I found out, its probably happens because this TCL command contains puts method inside.
I think puts override can solve this issue but don"t know how to do is as I don"t have entire TCL code but only the DLLs.
See the code:
TclAPI.cs
class TclAPI
{
[DllImport("tcl86.DLL", CallingConvention = CallingConvention.Cdecl)]
public static extern int Tcl_Init(IntPtr interp);
[DllImport("tcl86.DLL", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Tcl_CreateInterp();
[DllImport("tcl86.Dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Tcl_Eval(IntPtr interp, string skript);
[DllImport("tcl86.Dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Tcl_GetObjResult(IntPtr interp);
[DllImport("tcl86.Dll", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern char* Tcl_GetStringFromObj(IntPtr tclObj, IntPtr length);
}
IXIA.cs constructor:
public IXIA(string ip, string username="admin", string password = "admin")
{
interp = TclAPI.Tcl_CreateInterp();
TclAPI.Tcl_Init(interp);
if (interp == IntPtr.Zero)
{
throw new SystemException("can not ini tialize Tcl interpreter");
}
_ip = ip;
_userName = username;
_password = password;
}
evalScript method in IXIA.cs class:
public int evalScript(string script)
{
int Evalres = TclAPI.Tcl_Eval(interp, script);
return Evalres;
}
Since you're interfacing to a real Tcl interpreter, the problem is that the standard channels (of which stdout is a member) are not being registered. I'm not exactly sure what is wrong, but the problem is either that Tcl's incompletely initialised, or that you've built your overall application in a mode that doesn't support standard channels.
If it is the former, adding a registration for Tcl_FindExecutable (takes a single char*, which can be NULL, and returns void) and calling it once, before any other Tcl API functions will fix it. (That function is a bit mis-named; it really should be called something like Tcl_InitLibrary or something like that.)
[DllImport("tcl86.DLL", CallingConvention = CallingConvention.Cdecl)]
public static extern void Tcl_FindExecutable(string argv0);
TclAPI.Tcl_FindExecutable(null);
(See How can I run a static initializer method in C# before the Main() method? for a good way to make that call happen exactly once; this is exactly the sort of code that belongs in a static constructor.)
If the latter, you need to either build your application in a mode that ensures that there's a console available, or use Tcl_SetStdChannel to install a replacement. That's a public API, but not one designed to be easy to call from any languages other than C (or C++ in a pinch) since it involves creating a general Tcl channel instance first via Tcl_CreateChannel and that's a non-trivial API; I think you probably ought to regard it as off-limits as it requires quite a lot of knowledge of Tcl's channel model to use well.
Because of the API complexity involved, I'll not guide you through doing a new channel.
You could try replacing the puts command prior to running the problem script… but as that is used for all file and socket writing, not just writing to the console, you need to be careful. Try evaluating the script below before running the problem script:
rename puts puts_original
proc puts {args} {
if {[llength $args] == 1} {
set args [linsert $args 0 stdout]
} elseif {[llength $args] == 2 && [lindex $args 0] eq "-nonewline"} {
set args [linsert $args 1 stdout]
}
if {[lindex $args end-1] eq "stdout"} {
# Doing nothing here, but might want to log somehow?
} else {
puts_original {*}$args
}
}
This isn't code that I'd recommend normally — it's much better to actually fix the standard channels so that they work, and this is all rather flaky — but sometimes it is the best that can be done. Unless the code also creates sub-interpreters or threads, in which case you're really stuck.
I am learning C# from my C++/CLR background by rewriting a sample C++/CLR project in C#.
The project is a simple GUI (using Visual Studio/ Windows Forms) that performs calls to a DLL written in C (in fact, in NI LabWindows/CVI but this is just ANSI C with custom libraries). The DLL is not written by me and I cannot perform any changes to it because it is also used elsewhere.
The DLL contains functions to make an RFID device perform certain functions (like reading/writing RFID tag etc). In each of these functions, there is always a call to another function that performs writing to a log file. If the log file is not present, it is created with a certain header and then data is appended.
The problem is: the C++/CLR project works fine.
But, in the C# one, the functions work (the RFID tag is correctly written/read etc.) but there is no activity regarding the log file!
The declarations for DLL exports look like this (just one example, there are more of them, of course):
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
The save_Logdatei function is called during execution of Magnetfeld_einschalten like this:
save_Logdatei(path_Logfile_RFID, "Magnetfeld_einschalten", "OK");
In the C++/CLR project, I declared the function like this:
#ifdef __cplusplus
extern "C" {
#endif
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
#ifdef __cplusplus
}
#endif
then a simple call to the function is working.
In the C# project, the declaration goes like:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
and, as I said, although the primary function is working (in this case, turning on the magnetic field of the RFID device), the logging is never done (so, the internal DLL call to save_Logdatei is not executing correctly).
The relevant code in the Form constructor is the following:
pathapp = Application.StartupPath;
pathlog = string.Format("{0}\\{1:yyyyMMdd}_RFID_Logdatei.dat", pathapp, DateTime.Now);
//The naming scheme for the log file.
//Normally, it's autogenerated when a `save_Logdatei' call is made.
Magnetfeld_einschalten(pathlog);
What am I missing? I have already tried using unsafe for the DLL method declaration - since there is a File pointer in save_Logdatei - but it didn't make any difference.
===================================EDIT==================================
Per David Heffernan's suggestion, i have tried to recreate the problem in an easy to test way. For this, i have created a very simple DLL ("test.dll") and I have stripped it completely from the custom CVI libaries, so it should be reproducible even without CVI. I have uploaded it here. In any case, the code of the DLL is:
#include <stdio.h>
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300])
{
save_Logdatei(path_Logfile_RFID, "Opening Magnet Field", "Success");
return 0;
}
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[])
{
FILE *fp; /* File-Pointer */
char line[700]; /* Zeilenbuffer */
char path[700];
sprintf(path,"%s\\20160212_RFID_Logdatei.dat",path_Logdatei);
fp = fopen (path, "a");
sprintf(line, "Just testing");
sprintf(line,"%s %s",line, Funktion);
sprintf(line,"%s %s",line, Mitteilung);
fprintf(fp,"%s\n",line);
fclose(fp);
return 0;
}
The C# code is also stripped down and the only thing i have added to the standard Forms project, is Button 1 (and the generated button click as can be seen). The code is this:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TestDLLCallCSharp
{
public partial class Form1 : Form
{
public int ret;
public string pathapp;
public string pathlog;
[DllImport("test", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
public Form1()
{
pathapp = #"C:\ProgramData\test";
pathlog = string.Format("{0}\\20160212_RFID_Logdatei.dat", pathapp);
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
ret = Magnetfeld_einschalten(pathlog);
}
}
}
As can be seen, I have avoided using an automatic naming scheme for the log file (normally i use the date) and in both the dll and the C# code, the log file is "20160212_RFID_Logdatei.dat". I have also avoided using the app path as the directory where to put the log file and instead I have opted for a folder named test i created in ProgramData
Again, no file is created at all
This looks like a simple typo in your calling code. Instead of:
ret = Magnetfeld_einschalten(pathlog);
you mean to write:
ret = Magnetfeld_einschalten(pathapp);
In the C# code, these two strings have the following values:
pathapp == "C:\ProgramData\\test"
pathlog == "C:\ProgramData\\test\\20160212_RFID_Logdatei.dat"
When you pass pathlog to the unmanaged code it then does the following:
sprintf(path,"%s\\20160212_RFID_Logdatei.dat",path_Logdatei);
which sets path to be
path == "C:\\ProgramData\\test\\20160212_RFID_Logdatei.dat\\20160212_RFID_Logdatei.dat"
In other words you are appending the file name to the path twice instead of once.
An extensive overview for P/Invoke in C# is given in Platform Invoke Tutorial - MSDN Library.
The problematic bit is you need to pass a fixed char array rather than the standard char*. This is covered in Default Marshalling for Strings.
The gist is, you need to construct a char[300] from your C# string and pass that rather than the string.
For this case, two ways are specified:
pass a StringBuilder instead of a string initialized to the specified length and with your data (I omitted non-essential parameters):
[DllImport("MyDLL.dll", ExactSpelling = true)]
private static extern int Magnetfeld_einschalten(
[MarshalAs(UnmanagedType.LPStr)] StringBuilder path_Logfile_RFID);
<...>
StringBuilder sb = new StringBuilder(pathlog,300);
int result = Magnetfeld_einschalten(sb);
In this case, the buffer is modifiable.
define a struct with the required format and manually convert your string to it:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct Char300 {
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=300)]String s;
}
[DllImport("MyDLL.dll")]
private static extern int Magnetfeld_einschalten(Char300 path_Logfile_RFID);
<...>
int result = Magnetfeld_einschalten(new Char300{s=pathlog});
You can define an explicit or implicit cast routine to make this more straightforward.
According to UnmanagedType docs, UnmanagedType.ByValTStr is only valid in structures so it appears to be impossible to get the best of both worlds.
The String is in Unicode format, convert it to byte[]
Encoding ec = Encoding.GetEncoding(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);
byte[] bpathlog = ec.GetBytes(pathlog);
and change parameter type to byte[]
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(byte[] path_Logfile_RFID);
For me it is working
JSh
Consider the following C function:
void get_lib_version(const char **ver_string);
How do I marshall this correctly with PInvoke? The documentation says it returns a pointer to a static string. I thought this would do it:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_lib_version(StringBuilder version);
but all I get is gibberish.
The function returns a brand new C-string. The pinvoke marshaller always makes sure that the memory required to store a string that's returned by native code is released again. This will not come to a good end, surely the caller of this function is not supposed to release it. The const keyword is a strong hint that the native code will return a pointer to a string literal that's not allocated on the heap. Trying to release such a pointer will crash your program on later Windows versions, the kind that have a strict heap implementation (after XP).
You have to help to stop the marshaller from doing this. This requires you to declare the argument as a raw pointer, not a string:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_lib_version(out IntPtr version);
And you have to make the extra step to convert the pointer to a string:
public string GetLibraryVersion() {
IntPtr strptr;
get_lib_version(out strptr);
return Marshal.PtrToStringAnsi(strptr);
}
Write a little test program to verify this assumption. Call GetLibraryVersion() a billion times. If the memory usage doesn't explode then you're good.
According to this answer, when you marshal something as string, PInvoke makes all sorts of assumptions about how it's supposed to get freed. Notice that this is a const char *; it's a constant string somewhere. It never needs to be deallocated!
Apparently the way to deal with this is
Marshall as IntPtr.
Use Marshall.PtrToStringAnsi() to copy the result into a C# string.
I managed to get this to work correctly:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int get_lib_version(ref IntPtr version);
public static string GetLibVersion()
{
var ptrVersion = IntPtr.Zero;
get_lib_version(ref ptrVersion);
var version = Marshal.PtrToStringAnsi(ptrVersion);
return version;
}
-In my c code I have a struct which contains many unknown sized arrays in an unmanaged dll (c code)
-I need the data of one instance of this struct marshaled over to c#, which I will later on send back to the unmanaged c code
-I do not need to manipulate this data once it gets to csharp, only hold onto it/store it for a while (so it can remain in a byte array).
-I do not want to use the keyword 'unsafe' as it is a big project and this is just one small piece and I don't want to be compiling like that.
I tried marshaling it as a lpArray and everything looks fine but when i look at the contents after coming back to the csharp, it is always empty. This type of marshaling style worked for me for dynamic arrays of various types but not the struct.
Searching the web is drawing blanks and much more complicated scenarios than my own, but if anybody has seen such a link please post it here I would be very greatful!
Thanks.
--update here is more or less the structure of my code:
c#:
[DllImport("mydll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern int W_Thread_Connect_NET(
[MarshalAs(UnmanagedType.LPStr, SizeConst = 100)] string IPAddress,
int DevicePort,
[MarshalAs(UnmanagedType.LPArray)] byte[] connectionHandle
);
//and call it like this, with an empty struc to be populated by c (can this be done? it is comming back without the data):
byte[] myStrucParam= new byte[100];
int result = W_Thread_Connect_NET(myStrucParam, myParam1, myParam2, ...);
c:
typedef struct myStructDef{
char* myArray1,
char* myArray2,
int myInt1,
...
} mystrucObj, *pMystrucObj;
//method that i am wanting to marshal the struct as a paramter here..
MYDLL_DLLIMPORT int APIENTRY W_Thread_Connect_NET(pMystrucObj strucReturn_handle, char * IPAddress, int DevicePort, ...)
{
//(omitted)
}
You say that the C# code does not need to manipulate the struct. That makes it a pretty simple problem to solve. You can treat the struct pointer as an opaque pointer, that is an IntPtr.
First of all you add a new function to your native code:
pMystrucObj CreateStruct(void)
{
pMystrucObj res = malloc(sizeof(*res));
return res;
}
Then in your C# code you call it like this:
[DllImport("mydll.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern IntPtr CreateStruct();
Now declare W_Thread_Connect_NET like this:
[DllImport("mydll.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int W_Thread_Connect_NET(
IntPtr theStructPtr,
string IPAddress,
int DevicePort,
....
);
And call it all like this:
IntPtr theStructPtr = CreateStruct();
int res = W_Thread_Connect_NET(theStructPtr, IPAddress, DevicePort, ...);
And of course you'll want to add another function named DestroyStruct to deallocate the struct's memory once you are done with it.
I wave a method in C++ that receives a parameter of the LPCOLESTR type. I'm accessing this method through C#, but I can't make the correct conversion between String and this type.
Let's say the method signinature in C++ is:
void Something(LPCOLESTR str)
In C#, I'm trying to call it (all reference issues to access the method through a DLL have been solved already):
String test = "Hello world";
Something(test);
But with no luck, of course. If anyone can help me, I'd be very glad. Thank you!
Code snippet:
As an example, here's my C++ portion of code, defined in the file MixedCodeCpp.h (CLR Class Library)
#include "windows.h"
#pragma once
using namespace System;
namespace MixedCodeCpp
{
public ref class MyClass
{
public:
HRESULT Something(LPCOLESTR str)
{
return S_OK;
}
};
}
And here's my code in C# (I've added a reference to the C++ project in the C# project, through Visual Studio):
StringBuilder sb = new StringBuilder();
sb.Append("Hello world");
MixedCodeCpp.MyClass cls = new MixedCodeCpp.MyClass();
cls.Something(sb);
The argument will appear as Char* on the C# side. That requires unsafe code, like this:
unsafe static void CallSomething(MyClass obj, string arg) {
IntPtr mem = Marshal.StringToCoTaskMemUni(arg);
try {
obj.Something((Char*)mem);
}
finally {
Marshal.FreeCoTaskMem(mem);
}
}
It makes very little sense to expose the LPCOLESTR to other managed code. This method really should accept a String^ and convert to wchar_t* internally.
Try StringBuilder instead of String thusly:
System.Text.StringBuilder test = new System.Text.StringBuilder ();
test.Append("Hello world");
Something(test);
I've used it that way in pinvoke to Win32 functions that required various string pointers as parameters. Not sure it will work with your API but it's worth a shot. Here's some MSDN info about the process. And here is another.
Here's an arbitrary sample of what your import statement and declaration ought to look like. (To be taken with a grain of salt.)
[DllImport(SomeLib.SomeName, CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Something(StringBuilder pMyString);
StringBuilder str = new StringBuilder(MAX_PATH);
DWORD uSize;
bool b = Something(str);