对 C DLL 的 C# 调用仅部分起作用
本文关键字:仅部 起作用 调用 DLL | 更新日期: 2023-09-27 18:34:16
我正在通过用C#重写一个示例C++/CLR项目,从我的C++/CLR背景中学习C#。
该项目是一个简单的GUI(使用Visual Studio/Windows Forms(,它执行对用C编写的DLL的调用(实际上,在NI LabWindows/CVI中,但这只是带有自定义库的ANSI C(。DLL 不是我写的,我无法对它执行任何更改,因为它也在其他地方使用。
DLL包含使RFID设备执行某些功能(如读取/写入RFID标签等(的功能。在每个函数中,始终会调用另一个执行写入日志文件的函数。如果日志文件不存在,则使用某个标头创建该文件,然后附加数据。
问题是:C++/CLR 项目工作正常。但是,在 C# 中,函数工作(RFID 标记正确写入/读取等(,但没有关于日志文件的活动!
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 设备的磁场(,但日志记录从未完成(因此,对save_Logdatei
的内部 DLL 调用未正确执行(。
窗体构造函数中的相关代码如下所示:
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,它也应该是可重现的。我已经上传到这里。在任何情况下,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 项目的唯一内容是按钮 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的文件夹
同样,根本没有创建任何文件
这看起来像是调用代码中的简单拼写错误。而不是:
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# 中 P/Invoke 的广泛概述,请参阅平台调用教程 - MSDN 库。
有问题的一点是您需要传递固定的char
数组而不是标准char*
。这在字符串的默认封送中有所介绍。
要点是,您需要从 C# 字符串构造一个char[300]
并传递该字符串而不是字符串。
对于这种情况,指定了两种方法:
-
传递一个
StringBuilder
,而不是初始化为指定长度的字符串,并使用您的数据(我省略了非必要的参数(:[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);
在这种情况下,缓冲区是可修改的。
-
定义具有所需格式的
struct
,并手动将字符串转换为该:[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
文档,UnmanagedType.ByValTStr
仅在结构中有效,因此似乎不可能两全其美。
字符串采用 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);
对我来说,它正在工作
杰士