extern "C" __declspec(dllexport) int __stdcall testfunc(char* astr,int* a);
extern ”C”
通常來說,C++編譯器可能會改變函數和變數的名字,從而導致嚴重的連結程式問題。例如,假設使用C++編寫一個DLL,當建立DLL時,Microsoft的編譯器就會改變函數的名字。函數名將被設定一個前置底線,再加上一個@符號的首碼,後隨一個數字,表示作為參數傳遞給函數的位元組數。例如,下面的函數是作為DLL的輸出節中的_MyFunc@8輸出的:
__declspec(dllexport) LONG __stdcall MyFunc(int a, int b);
如果用另一個供應商的工具建立了一個可執行模組,它將設法連結到一個名叫MyFunc的函數,該函數在Microsoft編譯器已有的DLL中並不存在,因此連結將失敗。
使用extern “C”關鍵字可以使編譯器按照C語言的方式編譯DLL檔案,即編譯時間不改變函數名。
__declspec(dllexport)
在 32 位編譯器版本中,可以使用__declspec(dllexport) 關鍵字從DLL匯出資料、函數、類或類成員函數。__declspec(dllexport) 會將匯出指令添加到對象檔案中,因此不需要使用.def檔案。
若要匯出函數,__declspec(dllexport) 關鍵字必須出現在呼叫慣例關鍵字的左邊(如果指定了關鍵字)。例如:
__declspec(dllexport) void __cdecl Function1(void);
__stdcall
表明被呼叫者清理堆棧。
C#中的函式宣告
using System.Runtime.InteropServices;
…
public class Program
{
[DllImport(@"E:\Projects\testdll\debug\testdll.dll")]
public static extern int testfunc(StringBuilder abuf,ref int a);
}
using System.Runtime.InteropServices;
System.Runtime.InteropServices 命名空間提供各種各樣支援 COM interop 及平台叫用服務的成員,使程式可以與Unmanaged 程式碼進行互動操作。
[DllImport(“dllfile path”)]
代碼中DllImport關鍵字作用是告訴編譯器進入點在哪裡,並將打包函數捆綁在這個類中。在聲明的時候還可以添加幾個屬性:
[DllImport("MyDLL.dll",
EntryPoint="mySum",
CharSet=CharSet.Auto,
CallingConvention=CallingConvention.StdCall)]
EntryPoint: 指定要調用的 DLL 進入點。預設進入點名稱是託管方法的名稱 。
CharSet: 控制名稱重整和封送 String 參數的方式 (預設是UNICODE)
CallingConvention指示進入點的函數呼叫慣例(預設WINAPI)
注意:必須在標記為”static”和”extern”的方法上指定”DllImport”屬性。
資料傳遞方法
1.基礎資料型別 (Elementary Data Type)的傳遞
函數參數和傳回值可以是C#和C++的各種基礎資料型別 (Elementary Data Type),如int, float, double, char(注意不是char*)等。
樣本:
C#代碼:
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport(@"E:\Projects\testdll\debug\testdll.dll")]
public static extern int testfunc(int a,float b,double c,char d);
static void Main(string[] args)
{
int a = 1;
float b = 12;
double c = 12.34;
char d = ‘A‘;
testfunc(a,b,c,d);
Console.ReadKey();
}
}
C++代碼:
#include <iostream>
using namespace std;
extern "C"
{
_declspec(dllexport) int __stdcall testfunc(int a,float b,double c,char d)
{
cout<<a<<", "<<b<<", "<<c<<", "<<d<<endl;
return 0;
}
}
2.向DLL傳入字串
C#中使用string定義字串,將字串對象名傳給DLL。
注意:在DLL中更改字串的值,C#中的值也會改變。
缺點:無法改變字串的長度,建議使用第3種方法。
C#代碼:
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport(@"E:\Projects\testdll\debug\testdll.dll")]
public static extern int testfunc(string a);
static void Main(string[] args)
{
string a="Hello World!";
testfunc(a);
Console.ReadKey();
}
} C++代碼:
#include <iostream>
using namespace std;
extern "C"
{
_declspec(dllexport) int __stdcall testfunc(char* astr)
{
cout<<astr<<endl;
*astr=‘A‘;//更改字串的資料
cout<<astr<<endl;
return 0;
}
}
3.DLL傳出字串
C#中使用StringBuilder對象建立變長數組,並設定StringBuilder的Capacity為數組最大長度。將此對象名傳遞給DLL,使用char*接收。
C#代碼:
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport(@"E:\Projects\testdll\debug\testdll.dll")]
public static extern int testfunc(StringBuilder abuf);
static void Main(string[] args)
{
StringBuilder abuf=new StringBuilder();
abuf.Capacity = 100;//設定字串最大長度
testfunc(abuf);
Console.ReadKey();
}
} C++代碼:
#include <iostream>
using namespace std;
extern "C"
{
_declspec(dllexport) int __stdcall testfunc(char* astr)
{
*astr++=‘a‘;
*astr++=‘b‘;//C#中abuf隨astr改變
*astr=‘\0‘;
return 0;
}
}
4.DLL傳遞結構體(需要在C#中重新定義,不推薦使用)
C#中使用StructLayout重新定義需要使用的結構體。
注意:在DLL改變結構體成員的值,C#中隨之改變。
C#代碼:
using System;
using System.Text;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public double x;
public double y;
}
class Program
{
[DllImport(@"E:\Projects\testdll\debug\testdll.dll")]
public static extern int testfunc(Point p);
static void Main(string[] args)
{
Point p;
p.x = 12.34;
p.y = 43.21;
testfunc(p);
Console.ReadKey();
}
}
C++代碼:
#include <iostream>
using namespace std;
struct Point
{
double x;
double y;
};
extern "C"
{
_declspec(dllexport) int __stdcall testfunc(Point p)
{
cout<<p.x<<", "<<p.y<<endl;
return 0;
}
}