This may be a red herring, but my non-array version looks like this:
C#
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
namespace Blah
{
public static class Program
{
[DllExport("printstring", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.AnsiBStr)]
public static string PrintString()
{
return "Hello world";
}
}
}
Python
import ctypes
dll = ctypes.cdll.LoadLibrary(“test.dll")
dll.printstring.restype = ctypes.c_char_p
dll.printstring()
I am looking for a printstrings, which would fetch a List<string> of variable size. If that's not possible, I will settle for a fixed-length string[].
.NET is capable of transforming the object type into COM Automation's VARIANT and the reverse, when going through the p/invoke layer.
VARIANT is declared in python's automation.py that comes with comtypes.
What's cool with the VARIANT is it's a wrapper that can hold many things, including arrays of many things.
With that in mind, you can declare you .NET C# code like this:
[DllExport("printstrings", CallingConvention = CallingConvention.Cdecl)]
public static void PrintStrings(ref object obj)
{
obj = new string[] { "hello", "world" };
}
And use it like this in python:
import ctypes
from ctypes import *
from comtypes.automation import VARIANT
dll = ctypes.cdll.LoadLibrary("test")
dll.printstrings.argtypes = [POINTER(VARIANT)]
v = VARIANT()
dll.printstrings(v)
for x in v.value:
print(x)
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
I am trying to use a C++ dll to edit my StringBuilder object in C#. My C++ code looks like this:
extern "C" __declspec(dllexport) void __stdcall PrintHead(char* myString)
{
myString = "testIsOkey";
}
and my C# code is:
[DllImport("StringEdit.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = false)]
public static extern void PrintHead([MarshalAs(UnmanagedType.LPStr)] StringBuilder stringBuilder);
private void button1_Click(object sender, EventArgs e)
{
StringBuilder stringBuilder = new StringBuilder("123456");
PrintHead(stringBuilder);
}
After PrintHead is called, i am expecting to see that the stringBuilder object's value is changed from "123456" to "testIsOkey" , but it does not change. I can't figure out where do i make a mistake.
Thanks for your help.
void __stdcall PrintHead(char* myString) {
myString = "testIsOkey";
}
That's not correct C++ code. It merely changes the pointer that was passed to the function. This has no side effects whatsoever. Fix:
void __stdcall PrintHead(char* myString) {
strcpy(myString, "testIsOkey");
}
But never write interop code like this, the C++ function can easily destroy the garbage collected heap this way. Which is exactly what happens in your code, the StringBuilder's Capacity isn't enough. You should add an extra argument that provides the size of the passed buffer. Fix:
void __stdcall PrintHead(char* myString, size_t bufferSize) {
strcpy_s(myString, bufferSize, "testIsOkey");
}
Pass the string builder's Capacity for that extra argument, like this:
var buffer = new StringBuilder(666);
PrintHead(buffer, buffer.Capacity);
var result = buffer.ToString();
Doesn't it have to be marked ref or with some other attribute, so that .NET knows that the marshalling should occur both ways?
I am using the PInvoke, reverse PInvoke scheme as described by Thottam R. Sriram http://blogs.msdn.com/b/thottams/archive/2007/06/02/pinvoke-reverse-pinvoke-and-stdcall-cdecl.aspx
Everything seems to work well, except for passing a string from C++ to C.
( In Sriram the string is constructed in c# and passed untouched through c++, so the issue is avoided. )
The c# code looks like this
class Program
{
public delegate void callback(string str);
public static void callee(string str)
{
System.Console.WriteLine("Managed: " + str);
}
static void Main(string[] args)
{
gpscom_start(new callback(Program.callee));
Console.WriteLine("NMEA COM Monitor is running");
System.Threading.Thread.Sleep(50000);
}
[DllImport("gpscomdll.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void gpscom_start(callback call);
}
The C++ code looks like this
extern "C" dllapi void __stdcall gpscom_start(void (__stdcall *callback) (BSTR str))
{
BSTR bstr = SysAllocString(L"starting monitor");
(*callback)(bstr);
SysFreeString(bstr);
When run, everything looks good except the callback string
Managed: m
It looks like a UNICODE string being printed out by an ANSI routine, but surely c# strings are unicode?
TIA
When marshalling strings across a P/Invoke boundary, it is always good practice to use a MarshalAs attribute with the appropriate string type. I think putting a [MarshalAs(UnmanagedType.BStr)] on the parameter should take care of the problem.
public delegate void callback([MarshalAs(UnmanagedType.BStr)]string str);
This article has an similar example where someone was passing BSTRs between managed and unmanaged code, but he used IntPtr and some methods on the Marshal class. Marshal.PtrToStringBSTR seems most useful here.
C# strings are UTF-16. I would suggest that C# could be expecting an LPWSTR or something similar rather than a BSTR. If you look at the first example posted, he puts the call type as a wchar_t*, not a BSTR.
I have a c# dll and a c++ dll . I need to pass a string variable as reference from c# to c++ . My c++ dll will fill the variable with data and I will be using it in C# how can I do this. I tried using ref. But my c# dll throwed exception . "Attempted to read or write protected memory. ... This is often an indication that other memory is corrupt". Any idea on how this can be done
As a general rule you use StringBuilder for reference or return values and string for strings you don't want/need to change in the DLL.
StringBuilder corresponds to LPTSTR and string corresponds to LPCTSTR
C# function import:
[DllImport("MyDll.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static void GetMyInfo(StringBuilder myName, out int myAge);
C++ code:
__declspec(dllexport) void GetMyInfo(LPTSTR myName, int *age)
{
*age = 29;
_tcscpy(name, _T("Brian"));
}
C# code to call the function:
StringBuilder name = new StringBuilder(512);
int age;
GetMyInfo(name, out age);
Pass a fixed size StringBuilder from C# to C++.