我正在尝试调用一个在方言之间转换sql查询的函数。我正在使用开源项目的DLL在C#大学项目中使用用于转换的函数。我面临的问题是我正在获取访问冲突读取位置。
我在这里阅读了一些有关堆栈溢出的文章,这表明在某处可能有一个错误的指针,但我找不到位置。我的指针没有损坏
转换功能是这样的:
int ConvertSql(void *parser, const char *input, int64_t size, const char
**output, int64_t *out_size, int64_t *lines)
{
if(parser == NULL)
return -1;
SqlParser *sql_parser = (SqlParser*)parser;
// Run conversion
sql_parser->Convert(input, size, output, out_size, lines);
return 0;
}
我正在用C#调用该函数
char *parentInput;
fixed(char *input = &inputStr.ToCharArray()[0])
{
parentInput = input;
}
char** output = null;
Int64 out_size = 0;
Int64 lines = 0;
Int64 size = inputStr.Length;
Console.WriteLine(new IntPtr(&out_size)+" "+ new IntPtr(&lines)+" "+new IntPtr(&parserObj)+" "+new IntPtr(output));
int result = ConvertSql(&parserObj, intputStr, size, output, &out_size, &lines);
我从这段代码中获取了我的解析器对象,该对象没有错误:
IntPtr parserObj = CreateParserObject();
该功能的dllimport使用以下代码:
[DllImport(dllName: "PATHTODLLFOLDER\\sqlparser.dll", EntryPoint = "CreateParserObject", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateParserObject();
[DllImport(dllName: "PATHTODLLFOLDER\\sqlparser.dll", EntryPoint = "ConvertSql", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern int ConvertSql(void *parser, String input, Int64 size, char **output, Int64 *out_size, Int64 *lines);
答案 0 :(得分:2)
在.NET中,通过P / invoke调用非托管方法(这是在调用extern
方法时发生的情况)涉及对参数的各种类型转换,这称为“编组”,并自动完成在运行时的一部分称为“编组器”。
总的来说,编组指针是一个糟糕的主意。而是使用CLR编组器的功能,通过更改P /调用方法的签名为您将某些类型转换为指针:
// split on multiple lines for readability
[DllImport("PATHTODLLFOLDER\\sqlparser.dll", EntryPoint = "ConvertSql", CallingConvention = CallingConvention.Cdecl)]
public static extern int ConvertSql(
IntPtr parser,
[MarshalAs(UnmanagedType.LPStr)] string input,
long size,
out IntPtr output, // see explanation below
out long out_size,
out long lines);
关于以上几点。首先,我自由使用了C#类型别名(字符串,长整数),因为它是C#的惯用语,但不会改变行为。另外,由于不再有指针,因此不需要unsafe
。
首先,我将parser
声明为IntPtr
,因为如果需要,它们会自动转换为void*
,而CreateParserObject()
早已返回了该值。
第二个out参数可以转换为指向未初始化对象的指针,因此通过将out_size
和lines
都标记为out
,可以解决另一个问题。
input
是一个字符串,在.NET中具有特定格式。由于您的函数采用const char*
,因此您需要告诉编组器如何转换字符。这就是MarshalAs
属性出现的地方:默认转换对您不起作用。在这种情况下,UnmanagedType.LPStr
的意思是char*
,因此字符串将被转换。运行时将为您管理内存。
但是在这里,我们遇到了一个大障碍:输出。在整理事物时,总是存在关于生命周期的问题,或者特别是:谁释放了记忆? output
是char**
的事实意味着解析器分配一个内存块,然后通过该内存块将其返回,这意味着调用者必须释放它。从一开始,C ++设计就很糟糕,因为调用者不知道如何分配内存。是malloc
吗? new[]
?特定于平台的API,例如LocalAlloc
?它是指向静态内存块的指针吗?通常,这些API附带了文档,这些文档准确地告诉了您完成使用指针后的处理方式。好的C ++ API返回智能指针,或要求调用者传递先前分配的内存块,然后供他们使用。
但是,这就是您在玩的东西,因此,这是使其工作的方法。首先,您认为可以将output
声明为[MarshalAs(UnmanagedType.LPStr)] out string
:编组将把字符复制到托管字符串中并返回它……但是然后是本机字符串(在C ++端)的内存将泄漏,因为运行时不知道如何分配字符串,因此它不愿对此做任何事情。此外,这还假定该字符串以null结尾,但这并非总是如此。
因此,另一种选择是将output
声明为out IntPtr
。然后,您可以使用Marshal.PtrToStringAnsi
将指针转换为字符串,然后释放它...但是您需要首先知道如何分配指针。
将它们放在一起:
var parserObj = CreateParserObject();
var output = IntPtr.Zero;
try
{
long lines;
long out_size;
int result = ConvertSql(parserObj, inputStr, inputStr.Length, out output, out out_size, out lines);
var outputStr = Marshal.PtrToStringAnsi(output, (int)out_size);
// do what you want with outputStr here
}
finally
{
if (output != IntPtr.Zero)
{
// release output here
}
}
哦,还有一个最后的想法:CreateParserObject()
返回的任何内容都可能必须在某一时刻被释放。您可能需要另一个功能,例如:
[DllImport(/* ... */)]
public static extern void DestroyParserObject(IntPtr parserObject);
它甚至可能已经存在于您的DLL中。
祝你好运!