【问题标题】:C# calling C DLL, pass char * as parameter not correctC# 调用 C DLL,将 char * 作为参数传递不正确
【发布时间】:2013-06-07 23:04:36
【问题描述】:

我在 VS2012 中以 C# 语言调用 DLL(C 编写)。 mylib.dll 是我要在 C# 中调用的本机 dll,而 mylib.dll 也将调用另一个 mylib_another.dll。

C 函数声明为:

extern DECLSPEC_DLL BOOLEAN_TYPE SetConnection(char *dev, char *addr); 

在我的 C#​ 文件中,我将其声明为:

[DllImport("C:\\mylib.dll", EntryPoint = "SetConnection", CharSet = CharSet.Auto)]
public static unsafe extern int SetConnection(StringBuilder  dev,  StringBuilder addr);

当我在代码中调用它时,我发现字符串只传递了一个字符,当我将dev传递为“USB”时,本机DLL实际上只得到一个“U”。

如果我将声明更改为:

[DllImport("C:\\mylib.dll", EntryPoint = "SetConnection", CharSet = CharSet.Ansi)]
public static unsafe extern int SetConnection(StringBuilder  dev,  StringBuilder addr);

然后它会引发 System.AccessViolationException 异常:

System.Reflection.TargetInvocationException was unhandled
HResult=-2146232828
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
   at QC.QTMDotNetKernel.DotNetKernel.RunDotNetTest(Object stateObject)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
InnerException: System.AccessViolationException
   HResult=-2147467261
   Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

对此有什么想法吗?

已解决如下(第一个问题的答案,第二个问题的答案,那是因为mylib.dll调用了另一个本机dll,系统没有找到它)。

类似问题here:

【问题讨论】:

  • SetConnection() 的这两个声明是相同的。它们应该不同吗?
  • CharSet.Auto 错误,改成 Ansi。对于以“Set”开头的函数,StringBuilder很可能出错,将其设为字符串。
  • 第二个是:CharSet = CharSet.Ansi 更改为 this 时,会引发 System.AccessViolationException。

标签: c# dll pinvoke native


【解决方案1】:

当您只收到一个这样的字符时,通常意味着您将 Unicode (UTF16) 字符串传递给需要 8 位格式(如 ASCII、UTF8 或 ANSI)的 C/C++ 函数。

对于 ASCII/ANSI 范围内的字符,16 位 Unicode 格式最终将其两个字节中的一个字节设置为零,并且因为 C/C++ 程序将零字节视为字符串结尾字符,它会截断字符串。

很容易尝试使用 ANSI 看看是否有帮助;改变

CharSet = CharSet.Auto

CharSet = CharSet.Ansi

【讨论】:

  • 第二个是:CharSet = CharSet.Ansi 更改为 this 时,会引发 System.AccessViolationException。
  • 是的,这个是正确的,关于System.AccessViolationException的第二个问题是因为在原生dll中,
  • 是的,这个是正确的,关于System.AccessViolationException的第二个问题是因为在本机dll中,我调用了另一个名为mylib_another.dll的本机dll,这是因为我专门从一个完整路径,主程序无法自动加载该路径中的mylib_another.dll。所以所有这些dll(除了你代码中加载的那个)都应该放在你的主程序的主目录中,以确保它可以自动加载。
【解决方案2】:

您的 p/invoke 存在一些问题:

  1. 调用约定看起来是cdecl。它没有在本机代码中指定,最有可能的默认值是cdecl
  2. 您正在传递用于out 参数的StringBuilder。请注意,我假设这两个参数都是输入参数。
  3. 字符集错误。本机代码接收 8 位编码文本。
  4. 您不需要使用unsafe

更正的声明是:

[DllImport(@"C:\mylib.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SetConnection(string dev, string addr);

您可以省略字符集的指定,因为 ANSI 是默认设置。当然,如果您更喜欢明确,指定CharSet=CharSet.Ansi 也没有什么坏处。如果EntryPoint与您声明的函数同名,则无需指定。

【讨论】:

  • 我确实需要这行:CharSet=CharSet.Ansi 来解决第一个问题。
  • 不,你没有。它是默认值,可以省略。显然,您不能像在问题中那样将其设置为 CharSet.Auto,但我的答案中的代码是准确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-19
  • 1970-01-01
相关资源
最近更新 更多