===================================================================web
本文闡述如何用C#建立COM組件,並能用VB6.0等調用。附有完整測試經過的代碼。該功能整體看來很簡單,實際值得注意的地方仍是挺多。由於不多有人寫這類文章,有些代碼也是轉來轉去的不全,有些甚至讓人誤入歧途。後來在C# help上面看一個老外寫的文章 (上面有一個完整的SQL SERVER實例),才完成該功能。拿來分享。編程
開發工具:VS2008api
附:本文適用任何VS系列工具。服務器
在用C#建立COM組件時,必定要記住如下幾點:函數
1。所要導出的類必須爲公有;工具
2。全部屬性、方法也必須爲公有;學習
3。要導出的屬性、方法必須用接口方式;若是沒有在接口中聲明,即便該方法(屬性)爲公有,也不能正常導出到COM。但他們能夠被別的.NET程序所使用;開發工具
4。全部的事件也必須用接口方式;測試
如今開始正題:ui
1、新建一個Visual C#工程(習慣這種叫法了,如今應該叫解決方案),選擇類型「類庫」;就叫MyCom吧。
2、編寫導出接口。爲了你們理解方便,僅以一加法操做舉例。以下:
[Guid("154BD6A6-5AB8-4d7d-A343-0A68AB79470B")]
public
Guid爲全局惟一標識,能夠用VS2008的命令提示符中輸入:用guidgen工具(系統自帶)能夠產生guid,在幾個複選框選擇最後一個Registry Format,點擊New Guid,而後COPY就好了(如下若有guid的字符串,可用一樣操做產生)
[DispId(1)]爲函數的標識。若是有多個函數可相應的在函數前面加[DispId(2)], [DispId(3)]…
3、建立事件接口。
[Guid("D11FEA37-AC57-4d39-9522-E49C4F9826BB"),
public
}
Guid同二,很少說
InterfaceType表示向COM公開的方式,這裏選擇爲以調度的方式向COM公開。
4、 建立具體類:
[Guid("2E3C7BAD-1051-4622-9C4C-215182C6BF58"),
public
}
至此,代碼完畢。很簡單吧。別急,後面來有經常被忽略的步驟,先把總體代碼放在下面(注意引用InteropServices):using
5、你們都知道COM是須要註冊的。註冊時要加密鑰文件.SNK。這一部就是生成SNK文件。進入VS2008命令提示符。用命令:sn –k MyCom.snk回車。個人在E:\vs2008\vc下面就生成了一個(MyCom.snk)的文件。(大家能夠根據本身的命令提示符所在的文件目錄下去找)。而後把它COPY到你的工程根目錄下。
6、打開AssemblyInfo.cs。在裏面加入[assembly:AssemblyKeyFile("MyCom.snk")]
7、(1)項目屬性->應用程序->程序集信息->選中「使程序集COM可見」(英文版 - Make assembly Com-Visible
8、生成。若是在Debug下有一個MyCom.tlb,那你就成功了(確定還有MyCom.dll)要此tlb文件是爲了在VB6.0裏面測試。
9、在VB裏面建一個Stardard EXE工程(這個最方便)。建好後,到主菜單的項目->引用裏選中MyCom,點ok。
10、在Form_Load事件里加入以下代碼:
Dim o, a, b, s
Set o = CreateObject("MyCom.Class1")
a = 3
b = 6
s = o.Add(a, b)
MsgBox s
運行代碼,當彈出一個9,就說明你已經成功地在vb6.0下調用C#建立COM組件了。
========================
你們在實際工做學習C#的時候,可能會問:爲何咱們要爲一些已經存在的功能(好比 Windows中的一些功能,C++中已經編寫好的一些方法)要從新編寫代碼,C#有沒有方法能夠直接都用這些本來已經存在的功能呢?答案是確定的,你們 能夠經過C#中的DllImport直接調用這些功能。
DllImport所在的名字空間 using System.Runtime.InteropServices;
MSDN中對DllImportAttribute的解釋是這樣的:可將該屬性應用於方法。DllImportAttribute 屬性提供對從非託管 DLL 導出的函數進行調用所必需的信息。做爲最低要求,必須提供包含入口點的 DLL 的名稱。
DllImport 屬性定義以下:
namespace System.Runtime.InteropServices
{
[AttributeUsage(AttributeTargets.Method)]
public class DllImportAttribute: System.Attribute
{
public DllImportAttribute(string dllName) {...}
public CallingConvention CallingConvention;
public CharSet CharSet;
public string EntryPoint;
public bool ExactSpelling;
public bool PreserveSig;
public bool SetLastError;
public string Value { get {...} }
}
}
說明:
一、DllImport只能放置在方法聲明上。
二、DllImport具備單個定位參數:指定包含被導入方法的 dll 名稱的 dllName 參數。
三、DllImport具備五個命名參數:
a、CallingConvention 參數指示入口點的調用約定。若是未指定 CallingConvention,則使用默認值 CallingConvention.Winapi。
b、CharSet 參數指示用在入口點中的字符集。若是未指定 CharSet,則使用默認值 CharSet.Auto。
c、EntryPoint 參數給出 dll 中入口點的名稱。若是未指定 EntryPoint,則使用方法自己的名稱。
d、ExactSpelling 參數指示 EntryPoint 是否必須與指示的入口點的拼寫徹底匹配。若是未指定 ExactSpelling,則使用默認值 false。
e、PreserveSig 參數指示方法的簽名應當被保留仍是被轉換。當簽名被轉換時,它被轉換爲一個具備 HRESULT 返回值和該返回值的一個名爲 retval 的附加輸出參數的簽名。若是未指定 PreserveSig,則使用默認值 true。
f、SetLastError 參數指示方法是否保留 Win32"上一錯誤"。若是未指定 SetLastError,則使用默認值 false。
四、它是一次性屬性類。
五、此外,用 DllImport 屬性修飾的方法必須具備 extern 修飾符。
========================================================
[AttributeUsage(AttributeTargets.Method)]用法示例:
public class DllImportAttribute: System.Attribute
{
public DllImportAttribute(string dllName) {…} //定位參數爲dllName
public CallingConvention CallingConvention; //入口點調用約定
public CharSet CharSet; //入口點採用的字符接
public string EntryPoint; //入口點名稱
public bool ExactSpelling; //是否必須與指示的入口點拼寫徹底一致,默認false
public bool PreserveSig; //方法的簽名是被保留仍是被轉換
public bool SetLastError; //FindLastError方法的返回值保存在這裏
public string Value { get {…} }
}
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section,string key,string val,string filePath);
以上是用來寫入ini文件的一個win32api。
用此方式調用Win32API的數據類型對應:DWORD=int或uint,BOOL=bool,預約義常量=enum,結構=struct。
DllImport會按照順序自動去尋找的地方: 一、exe所在目錄 二、System32目錄 三、環境變量目錄因此只須要你把引用的DLL 拷貝到這三個目錄下 就能夠不用寫路徑了 或者能夠這樣server.MapPath(.\bin\*.dll)web中的,同時也是應用程序中的 後來發現用[DllImport(@"C:\OJ\Bin\Judge.dll")]這樣指定DLL的絕對路徑就能夠正常裝載。 這個問題最常出如今使用 第三方非託管DLL組件的時候,個人也一樣是這時出的問題,Asp.Net Team的官方解決方案以下: 首先須要確認你引用了哪些組件,那些是託管的,哪些是非託管的.託管的很好辦,直接被使用的須要引用,間接使用的須要拷貝 到bin目錄下.非託管的處理會比較麻煩.實際上,你拷貝到bin沒有任何幫助,由於CLR會把文件拷貝到一個臨時目錄下,而後在那運行web,而CLR 只會拷貝託管文件,這就是爲何咱們明明把非託管的dll放在了bin下卻依然提示不能加載模塊了. 具體作法以下: 首先咱們在服務器上隨便找個地 方新建一個目錄,假如爲C:\DLL 而後,在環境變量中,給Path變量添加這個目錄 最後,把全部的非託管文件都拷貝到C:\DLL中. 或者 更乾脆的把DLL放到system32目錄 對於能夠本身部署的應用程序,這樣未償不是一個解決辦法,然而,若是咱們用的是虛擬空間,咱們是沒辦法把注 冊PATH變量或者把咱們本身的DLL拷到system32目錄的。同時咱們也不必定知道咱們的Dll的物理路徑。 DllImport裏面只能用字符 串常量,而不可以用Server.MapPath(@"~/Bin/Judge.dll")來肯定物理路徑。ASP.NET中要使用DllImport 的,必須在先「using System.Runtime.InteropServices;」不過,我發現,調用這種"非託管Dll」至關的慢,多是由於個人方法須要遠程驗證 吧,可是實在是太慢了。通過一翻研究,終於想到了一個完美的解決辦法首先咱們用
[DllImport("kernel32.dll")]
private extern static IntPtr LoadLibrary(String path);
[DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr lib, String funcName);
[DllImport("kernel32.dll")]
private extern static bool FreeLibrary(IntPtr lib);
分別取得了LoadLibrary和GetProcAddress函數的地址,再經過這兩個函數來取得咱們的DLL裏面的函數。
咱們能夠先用Server.MapPath(@"~/Bin/Judge.dll")來取得咱們的DLL的物理路徑,而後再用LoadLibrary進行載入,最後用GetProcAddress取得要用的函數地址
如下自定義類的代碼完成LoadLibrary的裝載和函數調用:
public class DllInvoke
{
[DllImport("kernel32.dll")]
private extern static IntPtr LoadLibrary(String path);
[DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr lib, String funcName);
[DllImport("kernel32.dll")]
private extern static bool FreeLibrary(IntPtr lib);
private IntPtr hLib;
public DllInvoke(String DLLPath)
{
hLib = LoadLibrary(DLLPath);
}
~DllInvoke()
{
FreeLibrary(hLib);
}
//將要執行的函數轉換爲委託
public Delegate Invoke(String APIName,Type t)
{
IntPtr api = GetProcAddress(hLib, APIName);
return (Delegate)Marshal.GetDelegateForFunctionPointer(api,t);
}
}
下面代碼進行調用
public delegate int Compile(String command, StringBuilder inf);
//編譯
DllInvoke dll = new DllInvoke(Server.MapPath(@"~/Bin/Judge.dll"));
Compile compile = (Compile)dll.Invoke("Compile", typeof(Compile));
StringBuilder inf;
compile(@「gcc a.c -o a.exe「,inf);//這裏就是調用個人DLL裏定義的Compile函數
========================================================
DllImport的用法:
DllImport("MyDllImport.dll")]
private static extern int mySum(int a,int b);
一 在C#程序設計中使用Win32類庫
經常使用對應類型:
一、DWORD 是 4 字節的整數,所以咱們可使用 int 或 uint 做爲 C# 對應類型。
二、bool 類型與 BOOL 對應。
示例一:調用 Beep() API 來發出聲音
Beep() 是在 kernel32.lib 中定義的,在MSDN 中的定義,Beep具備如下原型:
BOOL Beep(DWORD dwFreq, // 聲音頻率
DWORD dwDuration // 聲音持續時間);
用 C# 編寫如下原型:
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
示例二:枚舉類型和常量
MessageBeep() 是在 user32.lib 中定義的,在MSDN 中的定義,MessageBeep具備如下原型:
BOOL MessageBeep(UINT uType // 聲音類型
);
用C#編寫一下原型:
public enum BeepType
{
SimpleBeep = -1,
IconAsterisk = 0x00000040,
IconExclamation = 0x00000030,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
Ok = 0x00000000,
}
uType 參數實際上接受一組預先定義的常量,對於 uType 參數,使用 enum 類型是合乎情理的。
[DllImport("user32.dll")]
public static extern bool MessageBeep(BeepType beepType);
示例三:處理結構
有時我須要肯定我筆記本的電池情況。Win32 爲此提供了電源管理函數,搜索 MSDN 能夠找到GetSystemPowerStatus() 函數。
BOOL GetSystemPowerStatus(
LPSYSTEM_POWER_STATUS lpSystemPowerStatus
);
此函數包含指向某個結構的指針,咱們還沒有對此進行過處理。要處理結構,咱們須要用 C# 定義結構。咱們從非託管的定義開始:
typedef struct _SYSTEM_POWER_STATUS {
BYTE ACLineStatus;
BYTE BatteryFlag;
BYTE BatteryLifePercent;
BYTE Reserved1;
DWORD BatteryLifeTime;
DWORD BatteryFullLifeTime;
} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS;
而後,經過用 C# 類型代替 C 類型來獲得 C# 版本。
struct SystemPowerStatus
{
byte ACLineStatus;
byte batteryFlag;
byte batteryLifePercent;
byte reserved1;
int batteryLifeTime;
int batteryFullLifeTime;
}
這樣,就能夠方便地編寫出 C# 原型:
[DllImport("kernel32.dll")]
public static extern bool GetSystemPowerStatus(
ref SystemPowerStatus systemPowerStatus);
在此原型中,咱們用「ref」指明將傳遞結構指針而不是結構值。這是處理經過指針傳遞的結構的通常方法。
此函數運行良好,可是最好將 ACLineStatus 和 batteryFlag 字段定義爲 enum:
enum ACLineStatus: byte
{
Offline = 0,
Online = 1,
Unknown = 255,
}
enum BatteryFlag: byte
{
High = 1,
Low = 2,
Critical = 4,
Charging = 8,
NoSystemBattery = 128,
Unknown = 255,
}
請注意,因爲結構的字段是一些字節,所以咱們使用 byte 做爲該 enum 的基本類型
示例四:處理字符串
二 C# 中調用C++代碼
int 類型
[DllImport(「MyDLL.dll")]
//返回個int 類型
public static extern int mySum (int a1,int b1);
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(int a2,int b2)
{
//a2 b2不能改變a1 b1
//a2=..
//b2=...
return a+b;
}
//參數傳遞int 類型
public static extern int mySum (ref int a1,ref int b1);
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(int *a2,int *b2)
{
//能夠改變 a1, b1
*a2=...
*b2=...
return a+b;
}
DLL 需傳入char *類型
[DllImport(「MyDLL.dll")]
//傳入值
public static extern int mySum (string astr1,string bstr1);
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(char * astr2,char * bstr2)
{
//改變astr2 bstr 2 ,astr1 bstr1不會被改變
return a+b;
}
DLL 需傳出char *類型
[DllImport(「MyDLL.dll")]
// 傳出值
public static extern int mySum (StringBuilder abuf, StringBuilder bbuf );
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(char * astr,char * bstr)
{
//傳出char * 改變astr bstr -->abuf, bbuf能夠被改變
return a+b;
}
DLL 回調函數
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
using System;
using System.Runtime.InteropServices;
public delegate bool CallBack(int hwnd, int lParam); //定義委託函數類型
public class EnumReportApp
{
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
public static void Main() {
CallBack myCallBack = new CallBack(EnumReportApp.Report); EnumWindows(myCallBack, 0);
}
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd); return true;
}
}
DLL 傳遞結構
BOOL PtInRect(const RECT *lprc, POINT pt);
using System.Runtime.InteropServices;[StructLayout(LayoutKind.Sequential)]public struct Point {public int x;public int y;}[StructLayout(LayoutKind.Explicit)]public struct Rect{[FieldOffset(0)] public int left;[FieldOffset(4)] public int top;[FieldOffset(8)] public int right;[FieldOffset(12)] public int bottom;}Class XXXX {[DllImport("User32.dll")]public static extern bool PtInRect(ref Rect r, Point p);}