首發:
長沙快付
版權所有,未經許可嚴禁轉載
一、寫一個最簡單的C函數并編譯成動態庫
嗯,直入正題,我們先用GVIM寫一個很簡單的C函數:
然后用GCC對它進行編譯(注意:這里是使用Windows中的GCC,采用MinGW安裝,如有不懂如何使用的讀者,可以移步到《如何安裝使用MinGW》進行學習)。
然后就出現了一個編譯好的庫,我們C這邊的工作就基本完成了。
二、如何通過Dllimport引入C寫的庫
如何引入一個非C#的庫,通常的方法就是采用DllImport,通過P/Invoke機制進行引入。當然在Mono上除了使用Dllimport外,還有另外的方式引入,這里我們不作任何探討。
我們新建一個控制臺程序,然后寫上我們的程序:
1 namespace demo2
2 {
3 internal class Program
4 {
5 private static void Main(string[] args)
6 {
7 var cLib = new CLib();
8 var p = CLib.SayHello();
9 var str = Marshal.PtrToStringAuto(p);
10 Console.WriteLine(str);
11 Console.ReadKey();
12 }
13 }
14
15 internal class CLib
16 {
17 [DllImport("你的so文件路徑/demo1.so")]
18 public extern static IntPtr SayHello();
19 }
20 }
按下F6編譯之后,我們在運行編譯好的exe文件:
瞧,成功調用了C的函數了,SO Easy,這里要注意一點的,也是很多童鞋經常會犯的錯誤,那就是我們需要找到控制臺bin里的exe,雙擊運行,不能直接在VS中按F5調試運行,否則是無法看到C語言輸出的東西的。
三、關于參數傳遞
關于C/C#之間的參數傳遞,這里水比較深,拋開數組、結構體等復雜類型不說,就簡單類型(int、char等)而言我私以為可以分為兩個部分,其一就是C#向C語言的參數傳遞,另外就是C語言向C#的return。
我們先對C#->C的傳遞方式進行講解,同樣的我們也繼續上demo。
我們定義了一個計算加法并輸出的函數,然后修改我們C#的源代碼為:
using System;
using System.Runtime.InteropServices;
namespace demo2
{
internal class Program
{
private static void Main(string[] args)
{
var a = 10;
var b = 20;
var cLib = new CLib();
unsafe
{
CLib.Add(&a, &b);
}
Console.ReadKey();
}
}
internal class CLib
{
[DllImport("你的路徑/c/demo1.so")]
public unsafe extern static IntPtr Add(int* a,int* b);
}
}
按下F6之后運行exe文件:
Oh~Year。同樣沒有問題。
我們在此基礎上,試試字符串,同樣的,我們在C語言這里添加一個新函數:
同樣的C#這里也進行改造:
using System;
using System.Runtime.InteropServices;
namespace demo2
{
internal class Program
{
private static void Main(string[] args)
{
var str = "你好,我是小蝶驚鴻";
unsafe
{
fixed (char* p = str)
{
CLib.Say(p);
}
}
Console.ReadKey();
}
}
internal class CLib
{
[DllImport("你的地址/demo1.so")]
public unsafe extern static IntPtr Say(char* input);
}
然后再重新生成so文件,重新編譯C#,并點擊exe運行:
可以看出,程序立馬報了個錯誤,具體原因就不跟各位讀者探討了,大概就是字符串沒有結束符,造成printf讀取完字符串本身之后還繼續的讀其他內存,造成了越界。我們把代碼小改一下,由外部傳入一個字符串的長度。這里還需要注意一點,那就是在C語言中,char只占一個字節而C#中的char則是兩個字節,由C#傳入的字符串還需要轉換一下。修改后的代碼如下圖所示:
同樣的C#代碼也跟著修改:
using System;
using System.Runtime.InteropServices;
namespace demo2
{
internal class Program
{
private static void Main(string[] args)
{
var str = "hi,i am xiaodiejinghong";
unsafe
{
fixed (char* p = str)
{
int length = str.Length;
CLib.Say(p, &length);
}
}
Console.ReadKey();
}
}
internal class CLib
{
[DllImport("E:/ASP/Mono/project/嵌入技術/c/demo1.so", CallingConvention = CallingConvention.StdCall)]
public unsafe extern static IntPtr Say(char* input, int* length);
}
}
編譯后再次運行:
程序正常無誤。至此,C#向C傳遞參數部分暫且講解完畢,下面我們再講解C#如何接收從C函數返回的信息。
可能有讀者認為直接使用Return即可,在某種程度上,確實是可以使用Return,譬如返回一個在C中寫死了的字符串(那是因為編譯器已經把這段固定字串當成常量存儲起來),但如若需要返回一串動態的字串,這種直接Return的方式就行不通了(字符串離開函數之后被回收,不信可以試試)。我們需要使用一些其他方法來接收從C返回的資源,在這里,我們要給各位讀者介紹的方式是通過在C#中給出一個容器,并把它的指針傳入C中,C需要返回的東西都存放到改容器中,這樣想返回的東西就不會被C回收掉了。
我們的C示例代碼如下:
C#的代碼如下:
using System;
using System.Runtime.InteropServices;
namespace demo2
{
internal class Program
{
private static void Main(string[] args)
{
var str1 = "hi,";
var str2 = "i am xiaodiejinghong ";
var l1 = str1.Length;
var l2 = str2.Length;
var output = new char[l1 + l2];
unsafe
{
fixed (char* p1 = str1) fixed (char* p2 = str2) fixed (char* op = output)
{
CLib.Merge(p1, &l1, p2, &l2, op);
Console.WriteLine(Marshal.PtrToStringAnsi((IntPtr)op));
}
}
Console.ReadKey();
}
}
internal class CLib
{
[DllImport("你的路徑/demo1.so", CallingConvention = CallingConvention.StdCall)]
public unsafe extern static IntPtr Merge(char* input1, int* length1, char* input2, int* length2, char* output);
}
}
重新編譯后運行:
成功合并了。
至此,本篇內容就到此結束,關于C/C#互調混合編程的水很深,對各方面的要求都很高,可以說,能夠玩轉的人必定是雙料達人。