如何调用由 C# Kotlin/Native 生成的本机 C 库中的函数?
How can I call functions in a native C library generated by Kotlin/Native from C#?
给定以下由 Kotlin/Native 生成的 C API:
#ifndef KONAN_bar_H
#define KONAN_bar_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
/* Service functions. */
void (*DisposeStablePointer)(bar_KNativePtr ptr);
void (*DisposeString)(const char* string);
bar_KBoolean (*IsInstance)(bar_KNativePtr ref, const bar_KType* type);
/* User functions. */
struct {
struct {
void (*foo)(const char* string);
} root;
} kotlin;
} bar_ExportedSymbols;
extern bar_ExportedSymbols* bar_symbols(void);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* KONAN_bar_H */
如何使用 P/Invoke 从 C# 访问本机函数 foo
?
我一直在经历 Marshal API,并尝试了几种不同的方法来编组本机对象(例如 Marshal.PtrToStructure
在从 extern
调用返回 IntPtr
之后),但我知道我对如何编组本机对象有一个根本性的误解,而且当给定一个像上面这样的嵌套结构时,事情会更加复杂。
我一直在 this guide 尝试学习如何编组复杂对象,但似乎没有涵盖这个特定的用例。
经过几个小时尝试压缩我们的任何东西后,这是我的应用程序的当前状态:
public class TestExtern
{
[UnmanagedFunctionPointer( CallingConvention.StdCall )]
public delegate void foo( string @string );
[DllImport( "bar" )]
private static extern BarSymbols bar_symbols();
private void Start()
{
var barSymbols = bar_symbols();
var kotlin = barSymbols.kotlin;
var root = kotlin.root;
var fooDelegate = Marshal.GetDelegateForFunctionPointer<foo>( root.instance );
fooDelegate( "Testing" ); // Access Violation
}
[StructLayout( LayoutKind.Sequential )]
public struct BarSymbols
{
public Kotlin kotlin;
}
[StructLayout( LayoutKind.Sequential )]
public struct Kotlin
{
public Root root;
}
[StructLayout( LayoutKind.Sequential )]
public struct Root
{
public IntPtr instance;
}
}
提前致谢。
首先,我不熟悉 Kotlin/Native,但是如果有任何方法可以指定它应该为 C 代码生成一个平面 API,那么您绝对应该使用它。就目前而言,生成的 API 太复杂了; bar_ExportedSymbols
结构只不过是一堆函数——根本不需要将其定义为结构。
但是,继续使用我们现有的代码,您需要定义与本机函数指针相对应的委托。
public class NativeMethods
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisposeStablePointer(IntPtr ptr);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisposeString([MarshalAs(UnmanagedType.LPStr)] string str);
// this assumes that bar_KBoolean is defined as an int
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public delegate bool IsInstance(IntPtr pRef, IntPtr type);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void Foo([MarshalAs(UnmanagedType.LPStr)] string str);
[DllImport("KONAN_bar.dll", EntryPoint = "bar_symbols")]
public static extern IntPtr BarSymbols();
}
接下来要做的是定义一个托管结构来保存函数委托。本机结构不必要地包含嵌套的 root
和 kotlin
结构,它们不会做任何有用的事情。此定义应该有效,除非存在特定于您的 compiler/platform 的结构填充问题,您必须自己解决这些问题。
[StructLayout(LayoutKind.Sequential)]
public struct ExportedSymbols
{
public NativeMethods.DisposeStablePointer FuncPointerDispose;
public NativeMethods.DisposeString FuncStringDispose;
public NativeMethods.IsInstance FuncIsInstance;
public NativeMethods.Foo FuncFoo;
}
一个测试访问foo
函数的简单程序:
class Program
{
static void Main(string[] args)
{
IntPtr p = NativeMethods.BarSymbols();
ExportedSymbols es = (ExportedSymbols)Marshal.PtrToStructure(p, typeof(ExportedSymbols));
es.FuncFoo("Testing");
}
}
由于这个结构只不过是一组函数指针,您可能会将其存储在某个地方的静态变量中,该变量将持续应用程序的生命周期。如果这涉及一个包含数据成员的结构,该结构已分配并需要在某个时候释放,您将存储指针 p
以便您可以将其传递给将释放分配内存的库函数。
我建议使用@konan.internal.CName("topLevelFunctionName") 注释将您的函数导出为顶级 C 函数,并像往常一样使用它,因此对于如下代码:
@konan.internal.CName("topLevelFunctionVoidFromC")
fun topLevelFunctionVoid(x1: Int): Int
将生成以下 C 代码:
int topLevelFunctionVoid(int x1);
参见 https://github.com/JetBrains/kotlin-native/blob/master/backend.native/tests/produce_dynamic/simple。
给定以下由 Kotlin/Native 生成的 C API:
#ifndef KONAN_bar_H
#define KONAN_bar_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
/* Service functions. */
void (*DisposeStablePointer)(bar_KNativePtr ptr);
void (*DisposeString)(const char* string);
bar_KBoolean (*IsInstance)(bar_KNativePtr ref, const bar_KType* type);
/* User functions. */
struct {
struct {
void (*foo)(const char* string);
} root;
} kotlin;
} bar_ExportedSymbols;
extern bar_ExportedSymbols* bar_symbols(void);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* KONAN_bar_H */
如何使用 P/Invoke 从 C# 访问本机函数 foo
?
我一直在经历 Marshal API,并尝试了几种不同的方法来编组本机对象(例如 Marshal.PtrToStructure
在从 extern
调用返回 IntPtr
之后),但我知道我对如何编组本机对象有一个根本性的误解,而且当给定一个像上面这样的嵌套结构时,事情会更加复杂。
我一直在 this guide 尝试学习如何编组复杂对象,但似乎没有涵盖这个特定的用例。
经过几个小时尝试压缩我们的任何东西后,这是我的应用程序的当前状态:
public class TestExtern
{
[UnmanagedFunctionPointer( CallingConvention.StdCall )]
public delegate void foo( string @string );
[DllImport( "bar" )]
private static extern BarSymbols bar_symbols();
private void Start()
{
var barSymbols = bar_symbols();
var kotlin = barSymbols.kotlin;
var root = kotlin.root;
var fooDelegate = Marshal.GetDelegateForFunctionPointer<foo>( root.instance );
fooDelegate( "Testing" ); // Access Violation
}
[StructLayout( LayoutKind.Sequential )]
public struct BarSymbols
{
public Kotlin kotlin;
}
[StructLayout( LayoutKind.Sequential )]
public struct Kotlin
{
public Root root;
}
[StructLayout( LayoutKind.Sequential )]
public struct Root
{
public IntPtr instance;
}
}
提前致谢。
首先,我不熟悉 Kotlin/Native,但是如果有任何方法可以指定它应该为 C 代码生成一个平面 API,那么您绝对应该使用它。就目前而言,生成的 API 太复杂了; bar_ExportedSymbols
结构只不过是一堆函数——根本不需要将其定义为结构。
但是,继续使用我们现有的代码,您需要定义与本机函数指针相对应的委托。
public class NativeMethods
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisposeStablePointer(IntPtr ptr);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisposeString([MarshalAs(UnmanagedType.LPStr)] string str);
// this assumes that bar_KBoolean is defined as an int
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public delegate bool IsInstance(IntPtr pRef, IntPtr type);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void Foo([MarshalAs(UnmanagedType.LPStr)] string str);
[DllImport("KONAN_bar.dll", EntryPoint = "bar_symbols")]
public static extern IntPtr BarSymbols();
}
接下来要做的是定义一个托管结构来保存函数委托。本机结构不必要地包含嵌套的 root
和 kotlin
结构,它们不会做任何有用的事情。此定义应该有效,除非存在特定于您的 compiler/platform 的结构填充问题,您必须自己解决这些问题。
[StructLayout(LayoutKind.Sequential)]
public struct ExportedSymbols
{
public NativeMethods.DisposeStablePointer FuncPointerDispose;
public NativeMethods.DisposeString FuncStringDispose;
public NativeMethods.IsInstance FuncIsInstance;
public NativeMethods.Foo FuncFoo;
}
一个测试访问foo
函数的简单程序:
class Program
{
static void Main(string[] args)
{
IntPtr p = NativeMethods.BarSymbols();
ExportedSymbols es = (ExportedSymbols)Marshal.PtrToStructure(p, typeof(ExportedSymbols));
es.FuncFoo("Testing");
}
}
由于这个结构只不过是一组函数指针,您可能会将其存储在某个地方的静态变量中,该变量将持续应用程序的生命周期。如果这涉及一个包含数据成员的结构,该结构已分配并需要在某个时候释放,您将存储指针 p
以便您可以将其传递给将释放分配内存的库函数。
我建议使用@konan.internal.CName("topLevelFunctionName") 注释将您的函数导出为顶级 C 函数,并像往常一样使用它,因此对于如下代码:
@konan.internal.CName("topLevelFunctionVoidFromC")
fun topLevelFunctionVoid(x1: Int): Int
将生成以下 C 代码:
int topLevelFunctionVoid(int x1);
参见 https://github.com/JetBrains/kotlin-native/blob/master/backend.native/tests/produce_dynamic/simple。