对 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的文件夹

同样,根本没有创建任何文件

对 C DLL 的 C# 调用仅部分起作用

这看起来像是调用代码中的简单拼写错误。而不是:

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);

对我来说,它正在工作

杰士