我的同事有一个用C制作的图书馆,我想用C#来称呼它。我认为这几乎是正确的但是当我调用第二个方法/函数时它会得到一个AccessViolationException。我尝试过几件不同的事情:
下面的代码用于C#端和我正在使用的C头。
C:
#pragma once
#define MAX_FEATURE_LENGTH 30
#define HELPERDLL_API __declspec(dllimport)
struct HelperAttributes {
void(*SaveCallBack) ();
void(*ExitCallBack) ();
char* feature_name;
int attempt_limit;
int check_interval;
};
extern "C"
{
void HELPERDLL_API DoStartUp();
void HELPERDLL_API ReSpecify();
void HELPERDLL_API Initialise(HelperAttributes* attributes);
}
C#:
namespace test
{
public partial class Form1 : Form
{
public delegate void SaveCallBack();
public delegate void ExitCallBack();
public Form1()
{
InitializeComponent();
}
[StructLayout(LayoutKind.Sequential)]
public struct HelperAttributes
{
public SaveCallBack saveCallBack;
public ExitCallBack exitCallBack;
[MarshalAs(UnmanagedType.LPStr)]
public string feature_name;
public int attempt_limit;
public int check_interval;
};
[DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DoStartUp();
[DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ReSpecify();
[DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Initialise(
[In, MarshalAs(UnmanagedType.LPStruct)]
HelperAttributes attributes
);
private void button1_Click(object sender, EventArgs e)
{
HelperAttributes attributes = new HelperAttributes();
attributes.saveCallBack = saveCallBackDelegate;
attributes.exitCallBack = exitCallBackDelegate;
attributes.feature_name = "XXX";
attributes.attempt_limit = 10;
attributes.check_interval = 30;
Initialise(attributes);
DoStartUp();
}
public void saveCallBackDelegate()
{
this.textBox1.Text = "save callback made";
}
public void exitCallBackDelegate()
{
this.textBox1.Text = "exit callback made";
}
}
}
答案 0 :(得分:3)
HelperAttributes attributes = new HelperAttributes();
这非常非常麻烦。您在此代码中有明确的内存管理问题。您在此处分配的结构具有非常有限的生命周期。它仅对Click事件处理程序方法的持续时间有效,最多为纳秒。这会以不止一种方式爆发:
C代码不能存储传递的指针,它必须复制struct。它可能不会这样做。你现在有一个“悬垂的指针”,一个非常臭名昭着的C bug。稍后取消引用指针会产生任意垃圾。
您的代码创建并分配给struct的saveCallback和exitCallback成员的委托对象无法生存。垃圾收集器无法发现C代码要求它们保持活动状态。因此,当C代码进行回调时,下一个垃圾收集会破坏对象Big Kaboom。另请注意,必须使用[UnmanagedFunctionPointer]声明它们,如果它们实际接受参数,则使它们成为Cdecl。
解决这些问题的方法不止一种。到目前为止,最简单的方法是将变量移到方法之外并将其声明为 static 。这使它在程序的生命周期内有效。垃圾收集器始终可以看到对委托对象的引用。然而,您无法绕过修复C代码并让它复制的需要,此结构不是blittable,C代码获取指向pinvoke marshaller创建的临时副本的指针。一旦Initialise()返回它就会变成垃圾。
答案 1 :(得分:2)
这些函数在非托管端都是void
,在托管端以int
返回类型声明。必须纠正这种不匹配。
您的回调函数指针使用cdecl
调用约定。您从托管代码传递的代理使用stdcall
调用约定。
通过标记代表来解决这个问题:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SaveCallBack();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ExitCallBack();
Initialise
可以更简单地声明为:
public static extern void Initialise(ref HelperAttributes attributes);
除此之外,我们必须猜测我们看不到的代码。例如,传递Initialise
复制结构,还是记住结构的地址。
它是后者,那是一个致命的问题。解决方案是修复非托管代码,使其不记住地址,而是复制内容。即使它复制结构,它是做深度复制,还是将指针复制到字符数组?要了解这一点,需要查看问题中没有的代码。
即使函数确实正确地复制了struct,你也需要让代表保持活着,正如Hans所解释的那样。
答案 2 :(得分:0)
在您的C dll中,您需要使用dllexport
代替dllimport
#define HELPERDLL_API __declspec(dllexport)
在C#代码导入函数中
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]