C# 对 C DLL 的调用只有部分功能
C# call to a C DLL is only partly functional
我正在通过用 C# 重写示例 C++/CLR 项目,从我的 C++/CLR 背景中学习 C#。
该项目是一个简单的 GUI(使用 Visual Studio/Windows 形式),它执行对用 C 编写的 DLL 的调用(实际上,在 NI LabWindows/CVI 中,但这只是带有自定义库的 ANSI C)。 DLL 不是我写的,我无法对其进行任何更改,因为它也在其他地方使用。
DLL 包含使 RFID 设备执行特定功能的函数(如 reading/writing RFID 标签等)。在这些函数中的每一个中,总是调用另一个执行日志文件写入的函数。如果日志文件不存在,则使用特定 header 创建它,然后附加数据。
问题是: C++/CLR 项目工作正常。
但是,在 C# 中,功能有效(RFID 标签是正确的 written/read 等)但是没有 activity 关于日志文件!
DLL 导出的声明如下所示(仅举一例,当然还有更多):
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
save_Logdatei
函数在 Magnetfeld_einschalten
执行期间被调用,如下所示:
save_Logdatei(path_Logfile_RFID, "Magnetfeld_einschalten", "OK");
在C++/CLR项目中,我这样声明函数:
#ifdef __cplusplus
extern "C" {
#endif
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
#ifdef __cplusplus
}
#endif
那么对函数的简单调用就可以了。
在C#项目中,声明如下:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
并且,正如我所说,尽管主要功能正在运行(在本例中,打开 RFID 设备的磁场),但日志记录从未完成(因此,内部 DLL 调用 save_Logdatei
没有正确执行)。
Form构造函数中的相关代码如下:
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);
我错过了什么?我已经尝试使用 unsafe
作为 DLL 方法声明 - 因为 save_Logdatei
中有一个 File
指针 - 但它没有任何区别。
===================================编辑========= =========================
根据 David Heffernan 的建议,我尝试以一种易于测试的方式重现该问题。为此,我创建了一个非常简单的 DLL ("test.dll") 并且我已经将它从自定义 CVI 库中完全删除,因此即使没有 CVI 它也应该可以重现。我已经上传了here。不管怎样,DLL的代码是:
#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;
}
C# 代码也被精简了,我唯一添加到标准 Forms 项目的是 Button 1(以及生成的按钮点击,如图所示)。代码是这样的:
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);
}
}
}
可以看出,我避免对日志文件使用自动命名方案(通常我使用日期),并且在 dll 和 C# 代码中,日志文件都是“20160212_RFID_Logdatei.dat” .我也避免使用应用程序路径作为放置日志文件的目录,而是选择了我在 ProgramData
中创建的名为 test 的文件夹
同样,根本没有创建任何文件
字符串为Unicode格式,转换为byte[]
Encoding ec = Encoding.GetEncoding(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);
byte[] bpathlog = ec.GetBytes(pathlog);
并将参数类型更改为 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);
对我来说很有效
JSh
Platform Invoke Tutorial - MSDN Library.
中给出了 C# 中 P/Invoke 的广泛概述
问题在于您需要传递一个固定的 char
数组而不是标准的 char*
。 Default Marshalling for Strings.
中对此进行了介绍
要点是,您需要从 C# 字符串构造一个 char[300]
并传递它而不是字符串。
对于这种情况,指定了两种方式:
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);
在这种情况下,缓冲区是可修改的。
define a struct
with the required format 并手动将您的字符串转换为它:
[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});
根据 UnmanagedType
docs,UnmanagedType.ByValTStr
仅在结构中有效,因此似乎不可能两全其美。
这看起来像是您的调用代码中的一个简单拼写错误。而不是:
ret = Magnetfeld_einschalten(pathlog);
你的意思是写:
ret = Magnetfeld_einschalten(pathapp);
在C#代码中,这两个字符串的值如下:
pathapp == "C:\ProgramData\test"
pathlog == "C:\ProgramData\test\20160212_RFID_Logdatei.dat"
当您将 pathlog
传递给非托管代码时,它会执行以下操作:
sprintf(path,"%s\20160212_RFID_Logdatei.dat",path_Logdatei);
将path
设置为
path == "C:\ProgramData\test\20160212_RFID_Logdatei.dat\20160212_RFID_Logdatei.dat"
换句话说,您将文件名附加到路径中两次,而不是一次。
我正在通过用 C# 重写示例 C++/CLR 项目,从我的 C++/CLR 背景中学习 C#。
该项目是一个简单的 GUI(使用 Visual Studio/Windows 形式),它执行对用 C 编写的 DLL 的调用(实际上,在 NI LabWindows/CVI 中,但这只是带有自定义库的 ANSI C)。 DLL 不是我写的,我无法对其进行任何更改,因为它也在其他地方使用。
DLL 包含使 RFID 设备执行特定功能的函数(如 reading/writing RFID 标签等)。在这些函数中的每一个中,总是调用另一个执行日志文件写入的函数。如果日志文件不存在,则使用特定 header 创建它,然后附加数据。
问题是: C++/CLR 项目工作正常。 但是,在 C# 中,功能有效(RFID 标签是正确的 written/read 等)但是没有 activity 关于日志文件!
DLL 导出的声明如下所示(仅举一例,当然还有更多):
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
save_Logdatei
函数在 Magnetfeld_einschalten
执行期间被调用,如下所示:
save_Logdatei(path_Logfile_RFID, "Magnetfeld_einschalten", "OK");
在C++/CLR项目中,我这样声明函数:
#ifdef __cplusplus
extern "C" {
#endif
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
#ifdef __cplusplus
}
#endif
那么对函数的简单调用就可以了。
在C#项目中,声明如下:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
并且,正如我所说,尽管主要功能正在运行(在本例中,打开 RFID 设备的磁场),但日志记录从未完成(因此,内部 DLL 调用 save_Logdatei
没有正确执行)。
Form构造函数中的相关代码如下:
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);
我错过了什么?我已经尝试使用 unsafe
作为 DLL 方法声明 - 因为 save_Logdatei
中有一个 File
指针 - 但它没有任何区别。
===================================编辑========= =========================
根据 David Heffernan 的建议,我尝试以一种易于测试的方式重现该问题。为此,我创建了一个非常简单的 DLL ("test.dll") 并且我已经将它从自定义 CVI 库中完全删除,因此即使没有 CVI 它也应该可以重现。我已经上传了here。不管怎样,DLL的代码是:
#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;
}
C# 代码也被精简了,我唯一添加到标准 Forms 项目的是 Button 1(以及生成的按钮点击,如图所示)。代码是这样的:
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);
}
}
}
可以看出,我避免对日志文件使用自动命名方案(通常我使用日期),并且在 dll 和 C# 代码中,日志文件都是“20160212_RFID_Logdatei.dat” .我也避免使用应用程序路径作为放置日志文件的目录,而是选择了我在 ProgramData
中创建的名为 test 的文件夹同样,根本没有创建任何文件
字符串为Unicode格式,转换为byte[]
Encoding ec = Encoding.GetEncoding(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);
byte[] bpathlog = ec.GetBytes(pathlog);
并将参数类型更改为 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);
对我来说很有效
JSh
Platform Invoke Tutorial - MSDN Library.
中给出了 C# 中 P/Invoke 的广泛概述问题在于您需要传递一个固定的 char
数组而不是标准的 char*
。 Default Marshalling for Strings.
要点是,您需要从 C# 字符串构造一个 char[300]
并传递它而不是字符串。
对于这种情况,指定了两种方式:
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);
在这种情况下,缓冲区是可修改的。
define a
struct
with the required format 并手动将您的字符串转换为它:[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});
根据 UnmanagedType
docs,UnmanagedType.ByValTStr
仅在结构中有效,因此似乎不可能两全其美。
这看起来像是您的调用代码中的一个简单拼写错误。而不是:
ret = Magnetfeld_einschalten(pathlog);
你的意思是写:
ret = Magnetfeld_einschalten(pathapp);
在C#代码中,这两个字符串的值如下:
pathapp == "C:\ProgramData\test"
pathlog == "C:\ProgramData\test\20160212_RFID_Logdatei.dat"
当您将 pathlog
传递给非托管代码时,它会执行以下操作:
sprintf(path,"%s\20160212_RFID_Logdatei.dat",path_Logdatei);
将path
设置为
path == "C:\ProgramData\test\20160212_RFID_Logdatei.dat\20160212_RFID_Logdatei.dat"
换句话说,您将文件名附加到路径中两次,而不是一次。