章介紹如何與本機(jī)(非托管)動態(tài)鏈接庫 (DLL) 和組件對象模型 (COM) 組件集成。除非另有說明,否則本章中提到的類型存在于 System 或 System.Runtime.InteropServices 命名空間中。
是縮寫,允許您訪問非托管DLL(Unix上的)中的函數(shù),結(jié)構(gòu)和回調(diào)。
例如,考慮在 Windows DLL 中定義的 MessageBox 函數(shù),如下所示:
int MessageBox (HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
可以通過聲明同名的靜態(tài)方法、應(yīng)用 extern 關(guān)鍵字并添加 DllImport 屬性來直接調(diào)用此函數(shù):
using System;
using System.Runtime.InteropServices;
MessageBox (IntPtr.Zero,
"Please do not press this again.", "Attention", 0);
[DllImport("user32.dll")]
static extern int MessageBox (IntPtr hWnd, string text, string caption,
int type);
System.Windows 和 System.Windows.Forms 命名空間中的 MessageBox 類本身調(diào)用類似的非托管方法。
下面是一個 Ubuntu Linux 的 DllImport 示例:
Console.WriteLine ($"User ID: {getuid()}");
[DllImport("libc")]
static extern uint getuid();
CLR 包括一個封送拆收器,該封送拆收器知道如何在 .NET 類型和非托管類型之間轉(zhuǎn)換參數(shù)和返回值。在 Windows 示例中,int 參數(shù)直接轉(zhuǎn)換為函數(shù)所需的四字節(jié)整數(shù),字符串參數(shù)轉(zhuǎn)換為以 null 結(jié)尾的 Unicode 字符數(shù)組(以 UTF-16 編碼)。IntPtr 是一種結(jié)構(gòu),旨在封裝非托管句柄;它在 32 位平臺上為 32 位寬,在 64 位平臺上為 64 位寬。類似的翻譯發(fā)生在Unix上。(從 C# 9 開始,您還可以使用 nint 類型,該類型映射到 IntPtr 。
在非托管端,可以有多種方法來表示給定的數(shù)據(jù)類型。例如,字符串可以包含單字節(jié) ANSI 字符或 UTF-16 Unicode 字符,并且可以是長度前綴、以 null 結(jié)尾或固定長度。使用 MarshalAs 屬性,可以向 CLR 封送拆收器指定使用的變體,以便它可以提供正確的轉(zhuǎn)換。下面是一個示例:
[DllImport("...")]
static extern int Foo ( [MarshalAs (UnmanagedType.LPStr)] string s );
非托管類型枚舉包括封送拆收器理解的所有 Win32 和 COM 類型。在這種情況下,封送拆收器被告知轉(zhuǎn)換為LPStr,這是一個以空結(jié)尾的單字節(jié)ANSI字符串。
在 .NET 端,您還可以選擇要使用的數(shù)據(jù)類型。例如,非托管句柄可以映射到 IntPtr 、int、uint、long 或 ulong。
大多數(shù)非托管句柄封裝地址或指針,因此必須映射到 IntPtr 才能與 32 位和 64 位操作系統(tǒng)兼容。一個典型的例子是HWND。
通常,使用 Win32 和 POSIX 函數(shù)時,您會遇到一個整數(shù)參數(shù),該參數(shù)接受一組常量,這些常量在C++頭文件(如 )中定義。無需將這些常量定義為簡單的 C# 常量,而是可以在枚舉中定義它們。使用枚舉可以使代碼更整潔,并提高靜態(tài)類型的安全性。我們在中提供了一個示例。
安裝 Visual Studio Microsoft時,請確保安裝C++頭文件,即使您在“C++”類別中未選擇任何其他文件也是如此。這是定義所有本機(jī) Win32 常量的位置。然后,您可以通過在 Visual Studio 程序目錄中搜索 來找到所有頭文件。
在Unix上,POSIX標(biāo)準(zhǔn)定義了常量的名稱,但是符合POSIX的Unix系統(tǒng)的單個實(shí)現(xiàn)可能會為這些常量分配不同的數(shù)值。您必須為所選操作系統(tǒng)使用正確的數(shù)值。同樣,POSIX 為互操作調(diào)用中使用的結(jié)構(gòu)定義了標(biāo)準(zhǔn)。結(jié)構(gòu)中字段的順序不是標(biāo)準(zhǔn)的固定的,Unix 實(shí)現(xiàn)可能會添加其他字段。定義函數(shù)和類型的C++頭文件通常安裝在 include 或 中。
將字符串從非托管代碼接收回 .NET 需要進(jìn)行一些內(nèi)存管理。如果使用 StringBuilder 而不是字符串聲明外部方法,則封送拆收器會自動執(zhí)行此工作,如下所示:
StringBuilder s=new StringBuilder (256);
GetWindowsDirectory (s, 256);
Console.WriteLine (s);
[DllImport("kernel32.dll")]
static extern int GetWindowsDirectory (StringBuilder sb, int maxChars);
在Unix上,它的工作方式類似。以下調(diào)用 getcwd 以返回當(dāng)前:
var sb=new StringBuilder (256);
Console.WriteLine (getcwd (sb, sb.Capacity));
[DllImport("libc")]
static extern string getcwd (StringBuilder buf, int size);
盡管 StringBuilder 使用起來很方便,但它的效率有些低下,因?yàn)?CLR 必須執(zhí)行額外的內(nèi)存分配和復(fù)制。在性能熱點(diǎn)中,可以通過改用 char[] 來避免此開銷:
[DllImport ("kernel32.dll", CharSet=CharSet.Unicode)]
static extern int GetWindowsDirectory (char[] buffer, int maxChars);
請注意,必須在 DllImport 屬性中指定字符集。您還必須在調(diào)用函數(shù)后將輸出字符串修剪為長度。您可以實(shí)現(xiàn)此目的,同時使用陣列池最小化內(nèi)存分配(參見中的),如下所示:
string GetWindowsDirectory()
{
var array=ArrayPool<char>.Shared.Rent (256);
try
{
int length=GetWindowsDirectory (array, 256);
return new string (array, 0, length).ToString();
}
finally { ArrayPool<char>.Shared.Return (array); }
}
(當(dāng)然,這個例子是人為的,因?yàn)槟梢酝ㄟ^內(nèi)置的 Environment.GetFolderPath 方法獲取 Windows 目錄。
如果您不確定如何調(diào)用特定的 Win32 或 Unix 方法,如果您搜索方法名稱和 ,您通常會在互聯(lián)網(wǎng)上找到一個示例。對于Windows,站點(diǎn) 是一個旨在記錄所有Win32簽名的wiki。
有時,您需要將結(jié)構(gòu)傳遞給非托管方法。例如,Win32 API 中的 GetSystemTime 定義如下:
void GetSystemTime (LPSYSTEMTIME lpSystemTime);
LPSYSTEMTIME符合以下C結(jié)構(gòu):
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
要調(diào)用 GetSystemTime ,我們必須定義一個與 C 結(jié)構(gòu)匹配的 .NET 類或結(jié)構(gòu):
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
class SystemTime
{
public ushort Year;
public ushort Month;
public ushort DayOfWeek;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public ushort Milliseconds;
}
屬性指示封送拆收器如何將每個字段映射到其非托管對應(yīng)項(xiàng)。LayoutKind.Sequential 意味著我們希望字段在邊界上按順序?qū)R(您很快就會看到這意味著什么),就像它們在 C 結(jié)構(gòu)中一樣。此處的字段名稱無關(guān)緊要;字段的順序很重要。
現(xiàn)在我們可以調(diào)用 GetSystemTime:
SystemTime t=new SystemTime();
GetSystemTime (t);
Console.WriteLine (t.Year);
[DllImport("kernel32.dll")]
static extern void GetSystemTime (SystemTime t);
同樣,在Unix上:
Console.WriteLine (GetSystemTime());
static DateTime GetSystemTime()
{
DateTime startOfUnixTime=new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
Timespec tp=new Timespec();
int success=clock_gettime (0, ref tp);
if (success !=0) throw new Exception ("Error checking the time.");
return startOfUnixTime.AddSeconds (tp.tv_sec).ToLocalTime();
}
[DllImport("libc")]
static extern int clock_gettime (int clk_id, ref Timespec tp);
[StructLayout(LayoutKind.Sequential)]
struct Timespec
{
public long tv_sec; /* seconds */
public long tv_nsec; /* nanoseconds */
}
在 C 和 C# 中,對象中的字段位于距離該對象地址 個字節(jié)的位置。不同之處在于,在 C# 程序中,CLR 通過使用字段標(biāo)記查找此偏移量來查找此偏移量;C 字段名稱直接編譯為偏移量。例如,在 C 中,wDay 只是一個標(biāo)記,用于表示 SystemTime 實(shí)例地址加上 24 個字節(jié)的任何內(nèi)容。
為了提高訪問速度,每個字段都放置在字段大小的倍數(shù)的偏移處。但是,該乘數(shù)限制為最大 x 字節(jié),其中 是。在當(dāng)前實(shí)現(xiàn)中,默認(rèn)包大小為 8 個字節(jié),因此由一個字節(jié)后跟一個(8 字節(jié))長的結(jié)構(gòu)占用 16 個字節(jié),并且該字節(jié)后面的 7 個字節(jié)被浪費(fèi)。您可以通過 StructLayout 屬性的 Pack 屬性指定包裝大小來減少或消除這種浪費(fèi):這將使字段與指定的倍數(shù)的偏移對齊。因此,當(dāng)包大小為 1 時,剛剛描述的結(jié)構(gòu)將僅占用 9 個字節(jié)。您可以指定 1、2、4、8 或 16 字節(jié)的包大小。
StructLayout 屬性還允許您指定顯式字段偏移量(請參閱)。
在前面的示例中,我們將 SystemTime 實(shí)現(xiàn)為一個類。我們可以選擇一個結(jié)構(gòu) — 前提是 GetSystemTime 是使用 ref 或 out 參數(shù)聲明的:
[DllImport("kernel32.dll")]
static extern void GetSystemTime (out SystemTime t);
在大多數(shù)情況下,C# 的方向參數(shù)語義與外部方法的工作方式相同。按值傳遞參數(shù)被復(fù)制到中,C# ref 參數(shù)被復(fù)制入/復(fù)制出,C# 輸出參數(shù)被復(fù)制出來。但是,具有特殊轉(zhuǎn)換的類型有一些例外。例如,數(shù)組類和 StringBuilder 類在從函數(shù)中出來時需要復(fù)制,因此它們是輸入/輸出。有時,使用 In 和 Out 屬性重寫此行為很有用。例如,如果一個數(shù)組應(yīng)該是只讀的,則 in 修飾符指示只復(fù)制進(jìn)入函數(shù)的數(shù)組,而不是從函數(shù)中出來的數(shù)組:
static extern void Foo ( [In] int[] array);
非托管方法通過堆棧和(可選)CPU 寄存器接收參數(shù)和返回值。因?yàn)橛胁恢挂环N方法可以實(shí)現(xiàn)這一點(diǎn),所以出現(xiàn)了許多不同的協(xié)議。這些協(xié)議稱為。
CLR 目前支持三種調(diào)用約定:StdCall、Cdeccl 和 ThisCall。
默認(rèn)情況下,CLR 使用調(diào)用約定(該平臺的標(biāo)準(zhǔn)約定)。在Windows上,它是StdCall,在Linux x86上,它是Cdecl。
如果非托管方法不遵循此默認(rèn)值,則可以顯式聲明其調(diào)用約定,如下所示:
[DllImport ("MyLib.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void SomeFunc (...)
有點(diǎn)誤導(dǎo)性的命名CallingConvention.WinApi指的是平臺默認(rèn)值。
C# 還允許外部函數(shù)通過回調(diào)調(diào)用 C# 代碼。有兩種方法可以完成回調(diào):
為了說明這一點(diǎn),我們將在 中調(diào)用以下 Windows 函數(shù),該函數(shù)枚舉所有頂級窗口句柄:
BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);
WNDENUMPROC 是一個回調(diào),它按順序使用每個窗口的句柄觸發(fā)(或直到回調(diào)返回 false)。以下是它的定義:
BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);
從 C# 9 開始,當(dāng)回調(diào)是靜態(tài)方法時,最簡單且性能最高的選項(xiàng)是使用。在 WNDENUMPROC 回調(diào)的情況下,我們可以使用以下函數(shù)指針:
delegate*<IntPtr, IntPtr, bool>
這表示一個接受兩個 IntPtr 參數(shù)并返回布爾值的函數(shù)。然后,您可以使用 & 運(yùn)算符為其提供靜態(tài)方法:
using System;
using System.Runtime.InteropServices;
unsafe
{
EnumWindows (&PrintWindow, IntPtr.Zero);
[DllImport ("user32.dll")]
static extern int EnumWindows (
delegate*<IntPtr, IntPtr, bool> hWnd, IntPtr lParam);
static bool PrintWindow (IntPtr hWnd, IntPtr lParam)
{
Console.WriteLine (hWnd.ToInt64());
return true;
}
}
對于函數(shù)指針,回調(diào)必須是靜態(tài)方法(或靜態(tài)本地函數(shù),如本例所示)。
通過將非托管關(guān)鍵字應(yīng)用于函數(shù)指針聲明,并將 [UnmanagedCallersOnly] 屬性應(yīng)用于回調(diào)方法,可以提高性能:
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
unsafe
{
EnumWindows (&PrintWindow, IntPtr.Zero);
[DllImport ("user32.dll")]
static extern int EnumWindows (
delegate* unmanaged <IntPtr, IntPtr, byte> hWnd, IntPtr lParam);
[UnmanagedCallersOnly]
static byte PrintWindow (IntPtr hWnd, IntPtr lParam)
{
Console.WriteLine (hWnd.ToInt64());
return 1;
}
}
此屬性標(biāo)記 PrintWindow 方法,以便從非托管代碼調(diào)用該方法,從而允許運(yùn)行時采用快捷方式。請注意,我們還將方法的返回類型從布爾值更改為字節(jié):這是因?yàn)閼?yīng)用 [UnmanagedCallersOnly] 的方法只能在簽名中使用 值類型。可直接封送類型是不需要任何特殊封送邏輯的類型,因?yàn)樗鼈冊谕泄芎头峭泄苁澜缰械谋硎痉绞较嗤F渲邪ɑ汀⒏↑c(diǎn)型、雙精度型和僅包含可拼接類型的結(jié)構(gòu)。char 類型也是可 blitable,如果結(jié)構(gòu)的一部分具有指定 CharSet.Unicode 的 StructLayout 屬性:
[StructLayout (LayoutKind.Sequential, CharSet=CharSet.Unicode)]
默認(rèn)情況下,編譯器假定非托管回調(diào)遵循平臺默認(rèn)調(diào)用約定。如果不是這樣,您可以通過 [UnmanagedCallersOnly] 屬性的 CallConvs 參數(shù)顯式聲明其調(diào)用約定:
[UnmanagedCallersOnly (CallConvs=new[] { typeof (CallConvStdcall) })]
static byte PrintWindow (IntPtr hWnd, IntPtr lParam) ...
還必須通過在非托管關(guān)鍵字后插入特殊修飾符來更新函數(shù)指針類型:
delegate* unmanaged[Stdcall] <IntPtr, IntPtr, byte> hWnd, IntPtr lParam);
編譯器允許您將任何標(biāo)識符(如 XYZ)放在方括號內(nèi),只要存在調(diào)用的 .NET 類型(運(yùn)行時可以理解該類型,并且與您在應(yīng)用 [UnmanagedCallersOnly] 屬性時指定的內(nèi)容匹配)。這使Microsoft將來更容易添加新的調(diào)用約定。CallConvXYZ
在本例中,我們指定了 StdCall,這是 Windows 的平臺默認(rèn)值(Cdecl 是 Linux x86 的默認(rèn)值)。以下是當(dāng)前支持的所有選項(xiàng):
名字 | 非托管修飾符 | 支撐類型 |
標(biāo)準(zhǔn)呼叫 | 非托管[標(biāo)準(zhǔn)呼叫] | CallConvStdcall |
中環(huán) | 非托管[Cdecl] | CallConvCdecl |
此調(diào)用 | 非托管[此調(diào)用] | CallConvThiscall |
非托管回調(diào)也可以通過委托完成。此方法適用于所有版本的 C#,并允許引用實(shí)例的回調(diào)。
若要繼續(xù),請首先聲明一個具有與回調(diào)匹配的簽名的委托類型。然后,可以將委托實(shí)例傳遞給外部方法:
class CallbackFun
{
delegate bool EnumWindowsCallback (IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
static extern int EnumWindows (EnumWindowsCallback hWnd, IntPtr lParam);
static bool PrintWindow (IntPtr hWnd, IntPtr lParam)
{
Console.WriteLine (hWnd.ToInt64());
return true;
}
static readonly EnumWindowsCallback printWindowFunc=PrintWindow;
static void Main()=> EnumWindows (printWindowFunc, IntPtr.Zero);
}
具有諷刺意味的是,將委托用于非托管回調(diào)是不安全的,因?yàn)楹苋菀紫萑朐试S在委托實(shí)例超出范圍后發(fā)生回調(diào)的陷阱(此時委托有資格進(jìn)行垃圾回收)。這可能會導(dǎo)致最糟糕的運(yùn)行時異常 - 沒有有用的堆棧跟蹤。對于靜態(tài)方法回調(diào),可以通過將委托實(shí)例分配給只讀靜態(tài)字段來避免這種情況(如本例所示)。對于實(shí)例方法回調(diào),此模式無濟(jì)于事,因此您必須仔細(xì)編碼,以確保在任何潛在回調(diào)期間至少維護(hù)一個對委托實(shí)例的引用。即便如此,如果非托管端存在錯誤(即在您告訴它不要調(diào)用回調(diào)后),您可能仍然需要處理無法追蹤的異常。解決方法是為每個非托管函數(shù)定義唯一的委托類型:這有助于診斷,因?yàn)槲蓄愋驮诋惓V袌蟾妗?/span>
您可以通過將 [UnmanagedFunctionPointer] 屬性應(yīng)用于委托,從平臺默認(rèn)值更改回調(diào)的調(diào)用約定:
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate void MyCallback (int foo, short bar);
結(jié)構(gòu)中的每個字段都有足夠的空間來存儲其數(shù)據(jù)。考慮一個包含一個 int 和一個字符的結(jié)構(gòu)。int 可能從偏移量 0 開始,并保證至少四個字節(jié)。因此,字符將從至少 4 的偏移量開始。如果由于某種原因,char 從偏移量 2 開始,如果您為 char 分配了一個值,您將更改 int 的值。聽起來像是混亂,不是嗎?奇怪的是,C 語言支持一種稱為的結(jié)構(gòu)的變體,它正是這樣做的。可以在 C# 中使用 LayoutKind.Explicit 和 FieldOffset 屬性來模擬這種情況。
想出一個有用的情況可能具有挑戰(zhàn)性。但是,假設(shè)您想在外部合成器上演奏音符。Windows Multimedia API 提供了一個通過 MIDI 協(xié)議執(zhí)行此操作的功能:
[DllImport ("winmm.dll")]
public static extern uint midiOutShortMsg (IntPtr handle, uint message);
第二個參數(shù) 消息 ,描述了要演奏的音符。問題在于構(gòu)造這個 32 位無符號整數(shù):它在內(nèi)部劃分為字節(jié),代表 MIDI 通道、音符和打擊速度。一種解決方案是通過按位<<、>> 和 |運(yùn)算符將這些字節(jié)與 32 位“打包”消息相互轉(zhuǎn)換。但是,更簡單的是定義具有顯式的結(jié)構(gòu):
[StructLayout (LayoutKind.Explicit)]
public struct NoteMessage
{
[FieldOffset(0)] public uint PackedMsg; // 4 bytes long
[FieldOffset(0)] public byte Channel; // FieldOffset also at 0
[FieldOffset(1)] public byte Note;
[FieldOffset(2)] public byte Velocity;
}
通道、注釋和速度字段故意與 32 位打包消息重疊。這允許您使用其中任何一個進(jìn)行讀取和寫入。無需計(jì)算即可使其他字段保持同步:
NoteMessage n=new NoteMessage();
Console.WriteLine (n.PackedMsg); // 0
n.Channel=10;
n.Note=100;
n.Velocity=50;
Console.WriteLine (n.PackedMsg); // 3302410
n.PackedMsg=3328010;
Console.WriteLine (n.Note); // 200
內(nèi)存映射文件或是 Windows 中的一項(xiàng)功能,它允許同一臺計(jì)算機(jī)上的多個進(jìn)程共享數(shù)據(jù)。共享內(nèi)存速度極快,與管道不同,它提供對共享數(shù)據(jù)的訪問。我們在中看到了如何使用 MemoryMappedFile 類來訪問內(nèi)存映射文件;繞過這一點(diǎn)并直接調(diào)用 Win32 方法是演示 P/Invoke 的好方法。
Win32 CreateFileMapping 函數(shù)分配共享內(nèi)存。您告訴它您需要多少字節(jié)以及用于標(biāo)識共享的名稱。然后,另一個應(yīng)用程序可以通過調(diào)用具有相同名稱的 OpenFileMapping 來訂閱此內(nèi)存。這兩種方法都返回一個,您可以通過調(diào)用 MapViewOfFile 將其轉(zhuǎn)換為指針。
下面是一個封裝對共享內(nèi)存的訪問的類:
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
public sealed class SharedMem : IDisposable
{
// Here we're using enums because they're safer than constants
enum FileProtection : uint // constants from winnt.h
{
ReadOnly=2,
ReadWrite=4
}
enum FileRights : uint // constants from WinBASE.h
{
Read=4,
Write=2,
ReadWrite=Read + Write
}
static readonly IntPtr NoFileHandle=new IntPtr (-1);
[DllImport ("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateFileMapping (IntPtr hFile,
int lpAttributes,
FileProtection flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);
[DllImport ("kernel32.dll", SetLastError=true)]
static extern IntPtr OpenFileMapping (FileRights dwDesiredAccess,
bool bInheritHandle,
string lpName);
[DllImport ("kernel32.dll", SetLastError=true)]
static extern IntPtr MapViewOfFile (IntPtr hFileMappingObject,
FileRights dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
[DllImport ("Kernel32.dll", SetLastError=true)]
static extern bool UnmapViewOfFile (IntPtr map);
[DllImport ("kernel32.dll", SetLastError=true)]
static extern int CloseHandle (IntPtr hObject);
IntPtr fileHandle, fileMap;
public IntPtr Root=> fileMap;
public SharedMem (string name, bool existing, uint sizeInBytes)
{
if (existing)
fileHandle=OpenFileMapping (FileRights.ReadWrite, false, name);
else
fileHandle=CreateFileMapping (NoFileHandle, 0,
FileProtection.ReadWrite,
0, sizeInBytes, name);
if (fileHandle==IntPtr.Zero)
throw new Win32Exception();
// Obtain a read/write map for the entire file
fileMap=MapViewOfFile (fileHandle, FileRights.ReadWrite, 0, 0, 0);
if (fileMap==IntPtr.Zero)
throw new Win32Exception();
}
public void Dispose()
{
if (fileMap !=IntPtr.Zero) UnmapViewOfFile (fileMap);
if (fileHandle !=IntPtr.Zero) CloseHandle (fileHandle);
fileMap=fileHandle=IntPtr.Zero;
}
}
在此示例中,我們在使用 SetLastError 協(xié)議發(fā)出錯誤代碼的 DllImport 方法上設(shè)置 SetLastError=true。這可確保在引發(fā)該異常時填充 Win32Exception 的錯誤詳細(xì)信息。(它還允許您通過調(diào)用 Marshal.GetLastWin32Error 來顯式查詢錯誤。
為了演示這個類,我們需要運(yùn)行兩個應(yīng)用程序。第一個創(chuàng)建共享內(nèi)存,如下所示:
using (SharedMem sm=new SharedMem ("MyShare", false, 1000))
{
IntPtr root=sm.Root;
// I have shared memory!
Console.ReadLine(); // Here's where we start a second app...
}
第二個應(yīng)用程序通過構(gòu)造同名的 SharedMem 對象來訂閱共享內(nèi)存,現(xiàn)有參數(shù)為 true:
using (SharedMem sm=new SharedMem ("MyShare", true, 1000))
{
IntPtr root=sm.Root;
// I have the same shared memory!
// ...
}
最終結(jié)果是每個程序都有一個 IntPtr,一個指向同一非托管內(nèi)存的指針。這兩個應(yīng)用程序現(xiàn)在需要以某種方式通過這個公共指針讀取和寫入內(nèi)存。一種方法是編寫一個封裝所有共享數(shù)據(jù)的類,然后使用 UnmanagedMemoryStream 將數(shù)據(jù)序列化(和反序列化)到非托管內(nèi)存。但是,如果有大量數(shù)據(jù),這是低效的。想象一下,如果共享內(nèi)存類有一兆字節(jié)的數(shù)據(jù),并且只需要更新一個整數(shù)。更好的方法是將共享數(shù)據(jù)構(gòu)造定義為結(jié)構(gòu),然后將其直接映射到共享內(nèi)存中。我們將在下一節(jié)中討論這個問題。
可以直接將具有順序或顯式結(jié)構(gòu)布局的結(jié)構(gòu)映射到非托管內(nèi)存。請考慮以下結(jié)構(gòu):
[StructLayout (LayoutKind.Sequential)]
unsafe struct MySharedData
{
public int Value;
public char Letter;
public fixed float Numbers [50];
}
固定指令允許我們內(nèi)聯(lián)定義固定長度的值類型數(shù)組,這就是將我們帶入不安全領(lǐng)域的原因。此結(jié)構(gòu)中的空間以內(nèi)聯(lián)方式分配給 50 個浮點(diǎn)數(shù)。與標(biāo)準(zhǔn) C# 數(shù)組不同,Numbers 不是對數(shù)組。如果我們運(yùn)行以下內(nèi)容
static unsafe void Main()=> Console.WriteLine (sizeof (MySharedData));
結(jié)果為 208:50 個四字節(jié)浮點(diǎn)數(shù),加上 Value 整數(shù)的四個字節(jié),加上字母字符的兩個字節(jié)。總數(shù) 206 四舍五入為 208,因?yàn)楦↑c(diǎn)數(shù)在四字節(jié)邊界上對齊(四個字節(jié)是浮點(diǎn)數(shù)的大小)。
我們可以在不安全的上下文中演示 MySharedData,最簡單的是使用堆棧分配的內(nèi)存:
MySharedData d;
MySharedData* data=&d; // Get the address of d
data->Value=123;
data->Letter='X';
data->Numbers[10]=1.45f;
or:
// Allocate the array on the stack:
MySharedData* data=stackalloc MySharedData[1];
data->Value=123;
data->Letter='X';
data->Numbers[10]=1.45f;
當(dāng)然,我們并沒有展示在托管環(huán)境中無法實(shí)現(xiàn)的任何內(nèi)容。但是,假設(shè)我們要將 MySharedData 的實(shí)例存儲在 CLR 垃圾回收器范圍之外的非上。這就是指針變得非常有用的地方:
MySharedData* data=(MySharedData*)
Marshal.AllocHGlobal (sizeof (MySharedData)).ToPointer();
data->Value=123;
data->Letter='X';
data->Numbers[10]=1.45f;
Marshal.AllocHGlobal 在非托管堆上分配內(nèi)存。以下是稍后釋放相同內(nèi)存的方法:
Marshal.FreeHGlobal (new IntPtr (data));
(忘記釋放內(nèi)存的結(jié)果是一個很好的老式內(nèi)存泄漏。
從 .NET 6 開始,可以改為使用新的 NativeMemory 類來分配和釋放非托管內(nèi)存。NativeMemory 使用比 AllocHGlobal 更新(更好)的底層 API,還包括執(zhí)行對齊分配的方法。
為了與它的名字保持一致,這里我們將MySharedData與我們在上一節(jié)中編寫的SharedMem類結(jié)合使用。以下程序分配共享內(nèi)存塊,然后將 MySharedData 結(jié)構(gòu)映射到該內(nèi)存中:
static unsafe void Main()
{
using (SharedMem sm=new SharedMem ("MyShare", false,
(uint) sizeof (MySharedData)))
{
void* root=sm.Root.ToPointer();
MySharedData* data=(MySharedData*) root;
data->Value=123;
data->Letter='X';
data->Numbers[10]=1.45f;
Console.WriteLine ("Written to shared memory");
Console.ReadLine();
Console.WriteLine ("Value is " + data->Value);
Console.WriteLine ("Letter is " + data->Letter);
Console.WriteLine ("11th Number is " + data->Numbers[10]);
Console.ReadLine();
}
}
您可以使用內(nèi)置的 MemoryMappedFile 類而不是 SharedMem ,如下所示:
using (MemoryMappedFile mmFile=MemoryMappedFile.CreateNew ("MyShare", 1000))
using (MemoryMappedViewAccessor accessor=mmFile.CreateViewAccessor())
{
byte* pointer=null;
accessor.SafeMemoryMappedViewHandle.AcquirePointer
(ref pointer);
void* root=pointer;
...
}
下面是附加到同一共享內(nèi)存的第二個程序,讀取第一個程序?qū)懭氲闹担ㄋ仨氃诘谝粋€程序等待 ReadLine 語句時運(yùn)行,因?yàn)楣蚕韮?nèi)存對象在離開其 using 語句時被釋放):
static unsafe void Main()
{
using (SharedMem sm=new SharedMem ("MyShare", true,
(uint) sizeof (MySharedData)))
{
void* root=sm.Root.ToPointer();
MySharedData* data=(MySharedData*) root;
Console.WriteLine ("Value is " + data->Value);
Console.WriteLine ("Letter is " + data->Letter);
Console.WriteLine ("11th Number is " + data->Numbers[10]);
// Our turn to update values in shared memory!
data->Value++;
data->Letter='!';
data->Numbers[10]=987.5f;
Console.WriteLine ("Updated shared memory");
Console.ReadLine();
}
}
每個程序的輸出如下所示:
// First program:
Written to shared memory
Value is 124
Letter is !
11th Number is 987.5
// Second program:
Value is 123
Letter is X
11th Number is 1.45
Updated shared memory
不要被指針嚇倒:C++程序員在整個應(yīng)用程序中使用它們,并且能夠讓一切正常工作。至少大多數(shù)時候是這樣!相比之下,這種用法相當(dāng)簡單。
碰巧的是,我們的例子不安全 - 確切地說 - 出于另一個原因。我們沒有考慮兩個程序同時訪問同一內(nèi)存時出現(xiàn)的線程安全(或者更準(zhǔn)確地說,進(jìn)程安全)問題。若要在生產(chǎn)應(yīng)用程序中使用它,我們需要將 volatile 關(guān)鍵字添加到 MySharedData 結(jié)構(gòu)中的“值”和“字母”字段中,以防止實(shí)時 (JIT) 編譯器(或 CPU 寄存器中的硬件)緩存字段。此外,隨著我們與字段的交互變得不平凡,我們很可能需要通過跨進(jìn)程互斥來保護(hù)它們的訪問,就像我們使用 lock 語句來保護(hù)對多線程程序中字段的訪問一樣。我們在中詳細(xì)討論了線程安全性。
將結(jié)構(gòu)直接映射到內(nèi)存的一個限制是結(jié)構(gòu)只能包含非托管類型。例如,如果需要共享字符串?dāng)?shù)據(jù),則必須改用固定字符數(shù)組。這意味著手動轉(zhuǎn)換到字符串類型或從字符串類型轉(zhuǎn)換。具體操作方法如下:
[StructLayout (LayoutKind.Sequential)]
unsafe struct MySharedData
{
...
// Allocate space for 200 chars (i.e., 400 bytes).
const int MessageSize=200;
fixed char message [MessageSize];
// One would most likely put this code into a helper class:
public string Message
{
get { fixed (char* cp=message) return new string (cp); }
set
{
fixed (char* cp=message)
{
int i=0;
for (; i < value.Length && i < MessageSize - 1; i++)
cp [i]=value [i];
// Add the null terminator
cp [i]='\0';
}
}
}
}
沒有對固定數(shù)組的引用;相反,你會得到一個指針。當(dāng)您索引到固定數(shù)組時,您實(shí)際上是在執(zhí)行指針?biāo)阈g(shù)!
第一次使用 fixed 關(guān)鍵字時,我們?yōu)榻Y(jié)構(gòu)中的 200 個字符分配內(nèi)聯(lián)空間。同一關(guān)鍵字(有些令人困惑)在以后在屬性定義中使用時具有不同的含義。它指示 CLR 固定對象,以便如果它決定在塊內(nèi)執(zhí)行垃圾回收,它不會在內(nèi)存堆上移動基礎(chǔ)結(jié)構(gòu)(因?yàn)樗膬?nèi)容是通過直接內(nèi)存指針迭代的)。看看我們的程序,你可能想知道MySharedData是如何在內(nèi)存中移動的,因?yàn)樗皇邱v留在堆上,而是駐留在非托管的世界中,垃圾收集器沒有管轄權(quán)。但是,編譯器不知道這一點(diǎn),并且托管上下文中使用MySharedData,因此它堅(jiān)持添加固定關(guān)鍵字以使不安全的代碼在托管上下文中安全。編譯器確實(shí)有一點(diǎn) - 以下是將MySharedData放在堆上所需的全部內(nèi)容:
object obj=new MySharedData();
這將生成一個帶盒的 MySharedData - 在堆上,并且有資格在垃圾回收期間進(jìn)行傳輸。
此示例說明如何在映射到非托管內(nèi)存的結(jié)構(gòu)中表示字符串。對于更復(fù)雜的類型,還可以選擇使用現(xiàn)有的序列化代碼。一個限制條件是序列化數(shù)據(jù)的長度不得超過其在結(jié)構(gòu)中的空間分配;否則,結(jié)果是與后續(xù)字段的意外聯(lián)合。
.NET 運(yùn)行時為 COM 提供特殊支持,使 COM 對象能夠從 .NET 使用,反之亦然。COM 僅在 Windows 上可用。
COM 是組件對象模型的首字母縮寫,組件對象模型是與庫接口的二進(jìn)制標(biāo)準(zhǔn),由 Microsoft 于 1993 年發(fā)布。發(fā)明 COM 的動機(jī)是使組件能夠以獨(dú)立于語言和版本容錯的方式相互通信。在 COM 之前,Windows 中的方法是發(fā)布使用 C 編程語言聲明結(jié)構(gòu)和函數(shù)的 DLL。這種方法不僅是特定于語言的,而且也很脆弱。這種庫中類型的規(guī)范與其實(shí)現(xiàn)密不可分:即使使用新字段更新結(jié)構(gòu)也意味著破壞其規(guī)范。
COM 的美妙之處在于通過稱為 的構(gòu)造將類型的規(guī)范與其基礎(chǔ)實(shí)現(xiàn)分開。COM 還允許在有狀態(tài)上調(diào)用方法,而不是局限于簡單的過程調(diào)用。
在某種程度上,.NET 編程模型是 COM 編程原則的演變:.NET 平臺還促進(jìn)了跨語言開發(fā),并允許二進(jìn)制組件在不破壞依賴于它們的應(yīng)用程序的情況下發(fā)展。
COM 類型系統(tǒng)圍繞接口旋轉(zhuǎn)。COM 接口很像 .NET 接口,但它更普遍,因?yàn)?COM 類型通過接口公開其功能。例如,在 .NET 世界中,我們可以簡單地聲明一個類型,如下所示:
public class Foo
{
public string Test()=> "Hello, world";
}
這種類型的消費(fèi)者可以直接使用 Foo。如果我們后來更改了 Test() 的,調(diào)用程序集將不需要重新編譯。在這方面,.NET 將接口與實(shí)現(xiàn)分開,不需要接口。我們甚至可以在不破壞調(diào)用者的情況下添加重載:
public string Test (string s)=> $"Hello, world {s}";
在COM世界中,F(xiàn)oo通過接口公開其功能以實(shí)現(xiàn)相同的解耦。因此,在Foo的類型庫中,將存在這樣的接口:
public interface IFoo { string Test(); }
(我們通過顯示 C# 接口(而不是 COM 接口)來說明這一點(diǎn)。然而,原理是相同的 - 盡管管道不同。
然后,呼叫者將與IFoo而不是Foo進(jìn)行交互。
在添加測試的重載版本時,COM的生活比.NET更復(fù)雜。首先,我們將避免修改 IFoo 接口,因?yàn)檫@會破壞與以前版本的二進(jìn)制兼容性(COM 的原則之一是接口一旦發(fā)布,就是的)。其次,COM 不允許方法重載。解決方案是讓Foo實(shí)現(xiàn):
public interface IFoo2 { string Test (string s); }
(同樣,為了熟悉起見,我們已將其音譯為 .NET 界面。
支持多個接口對于使 COM 庫至關(guān)重要。
所有 COM 接口都使用全局唯一標(biāo)識符 (GUID) 進(jìn)行標(biāo)識。
COM 中的根接口是 IUnknown — 所有 COM 對象都必須實(shí)現(xiàn)它。此接口有三種方法:
AddRef 和 Release 用于生存期管理,因?yàn)?COM 使用引用計(jì)數(shù)而不是自動垃圾回收(COM 旨在處理非托管代碼,其中自動垃圾回收不可行)。方法返回支持該接口的對象引用(如果可以)。
要啟用動態(tài)編程(例如,腳本和自動化),COM 對象還可以實(shí)現(xiàn) IDispatch 。這使得動態(tài)語言(如 VBScript)能夠以后期綁定的方式調(diào)用 COM 對象,就像 C# 中的動態(tài)語言(盡管僅用于簡單調(diào)用)。
CLR 對 COM 的內(nèi)置支持意味著您不直接使用 和 IDispatch。相反,您使用 CLR 對象,運(yùn)行時通過運(yùn)行時可調(diào)用包裝器 (RCW) 封送對 COM 世界的調(diào)用。運(yùn)行時還通過調(diào)用 AddRef 和 Release(當(dāng) .NET 對象完成時)來處理生存期管理,并處理兩個世界之間的基元類型轉(zhuǎn)換。例如,類型轉(zhuǎn)換可確保每一端都能看到熟悉形式的整數(shù)和字符串類型。
此外,還需要有某種方法以靜態(tài)類型方式訪問 RCW。這是 工作。COM 互操作類型是自動生成的代理類型,用于為每個 COM 成員公開一個 .NET 成員。類型庫導(dǎo)入程序工具 () 基于您選擇的 COM 庫從命令行生成 COM 互操作類型,并將其編譯為 。
如果 COM 組件實(shí)現(xiàn)多個接口,則 工具將生成一個類型,其中包含來自所有接口的成員的聯(lián)合。
可以在 Visual Studio 中創(chuàng)建 COM 互操作程序集,方法是轉(zhuǎn)到“添加引用”對話框,然后從“COM”選項(xiàng)卡中選擇一個庫。例如,如果您安裝了Microsoft Excel,則添加對Microsoft Excel 對象庫的引用允許您與 Excel 的 COM 類進(jìn)行互操作。下面是用于創(chuàng)建和顯示工作簿,然后在該工作簿中填充單元格的 C# 代碼:
using System;
using Excel=Microsoft.Office.Interop.Excel;
var excel=new Excel.Application();
excel.Visible=true;
Excel.Workbook workBook=excel.Workbooks.Add();
((Excel.Range)excel.Cells[1, 1]).Font.FontStyle="Bold";
((Excel.Range)excel.Cells[1, 1]).Value2="Hello World";
workBook.SaveAs (@"d:\temp.xlsx");
當(dāng)前需要在應(yīng)用程序中嵌入互操作類型(否則,運(yùn)行時不會在運(yùn)行時找到它們)。單擊 Visual Studio 的解決方案資源管理器中的 COM 引用,并在“屬性”窗口中將“嵌入互操作類型”屬性設(shè)置為 true,或者打開 文件并添加以下行(粗體):
<ItemGroup>
<COMReference Include="Microsoft.Office.Excel.dll">
...
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
Excel.Application 類是一種 COM 互操作類型,其運(yùn)行時類型為 RCW。當(dāng)我們訪問工作簿和單元格屬性時,我們會返回更多的互操作類型。
由于 COM API 不支持函數(shù)重載,因此具有大量參數(shù)的函數(shù)是很常見的,其中許多參數(shù)是可選的。例如,下面介紹了如何調(diào)用 Excel 工作簿的 Save 方法:
var missing=System.Reflection.Missing.Value;
workBook.SaveAs (@"d:\temp.xlsx", missing, missing, missing, missing,
missing, Excel.XlSaveAsAccessMode.xlNoChange, missing, missing,
missing, missing, missing);
好消息是 C# 對可選參數(shù)的支持是 COM 感知的,因此我們可以這樣做:
workBook.SaveAs (@"d:\temp.xlsx");
(正如我們在第中所述,可選參數(shù)由編譯器“擴(kuò)展”為完整的詳細(xì)形式。
命名參數(shù)允許您指定其他參數(shù),無論其位置如何:
workBook.SaveAs (@"c:\test.xlsx", Password:"foo");
某些 COM API(尤其是 Word Microsoft)公開將參數(shù)聲明為按引用傳遞的函數(shù),無論函數(shù)是否修改參數(shù)值。這是因?yàn)椴粡?fù)制參數(shù)值可以感知到性能增益(性能增益可以忽略不計(jì))。
從歷史上看,從 C# 調(diào)用此類方法很笨拙,因?yàn)楸仨殲槊總€參數(shù)指定 ref 關(guān)鍵字,這會阻止使用可選參數(shù)。例如,要打開Word文檔,我們過去必須這樣做:
object filename="foo.doc";
object notUsed1=Missing.Value;
object notUsed2=Missing.Value;
object notUsed3=Missing.Value;
...
Open (ref filename, ref notUsed1, ref notUsed2, ref notUsed3, ...);
借助隱式 ref 參數(shù),您可以在 COM 函數(shù)調(diào)用中省略 ref 修飾符,從而允許使用可選參數(shù):
word.Open ("foo.doc");
需要注意的是,如果您調(diào)用的 COM 方法確實(shí)改變了參數(shù)值,則不會收到編譯時或運(yùn)行時錯誤。
省略 ref 修飾符的功能還有另一個好處:它使具有 ref 參數(shù)的 COM 索引器可通過普通 C# 索引器語法訪問。否則將禁止這樣做,因?yàn)?C# 索引器不支持 ref / out 參數(shù)。
還可以調(diào)用接受參數(shù)的 COM 屬性。在下面的示例中,F(xiàn)oo 是一個接受整數(shù)參數(shù)的屬性:
myComObject.Foo [123]="Hello";
仍然禁止自己在 C# 中編寫此類屬性:類型只能在其自身(“默認(rèn)”索引器)上公開索引器。因此,如果要用 C# 編寫使上述語句合法的代碼,F(xiàn)oo 需要返回另一個公開(默認(rèn))索引器的類型。
動態(tài)綁定可以通過兩種方式在調(diào)用 COM 組件時提供幫助。
第一種方法是允許在沒有 COM 互操作類型的情況下訪問 COM 組件。為此,請使用 COM 組件名稱調(diào)用 Type.GetTypeFromProgID 以獲取 COM 實(shí)例,然后從此使用動態(tài)綁定調(diào)用成員。當(dāng)然,沒有智能感知,編譯時檢查是不可能的:
Type excelAppType=Type.GetTypeFromProgID ("Excel.Application", true);
dynamic excel=Activator.CreateInstance (excelAppType);
excel.Visible=true;
dynamic wb=excel.Workbooks.Add();
excel.Cells [1, 1].Value2="foo";
(同樣的事情也可以實(shí)現(xiàn),但更笨拙,用反射而不是動態(tài)綁定。
此主題的變體是調(diào)用支持 IDispatch 的 COM 組件。然而,這樣的組件非常罕見。
動態(tài)綁定在處理 COM 變體類型時也很有用(在較小程度上)。由于設(shè)計(jì)不佳而不是必要性的原因,COM API 函數(shù)通常充斥著這種類型,大致相當(dāng)于 .NET 中的對象。如果在項(xiàng)目中啟用“嵌入互操作類型”(稍后會詳細(xì)介紹),運(yùn)行時會將變量映射到動態(tài),而不是將變量映射到對象,從而避免了強(qiáng)制轉(zhuǎn)換的需要。例如,你可以合法地做
excel.Cells [1, 1].Font.FontStyle="Bold";
而不是:
var range=(Excel.Range) excel.Cells [1, 1];
range.Font.FontStyle="Bold";
以這種方式工作的缺點(diǎn)是會丟失自動完成功能,因此您必須知道名為 Font 的屬性恰好存在。因此,將結(jié)果分配給其已知的互操作類型通常更容易:
Excel.Range range=excel.Cells [1, 1];
range.Font.FontStyle="Bold";
如您所見,這比老式方法僅節(jié)省了五個字符!
變量到動態(tài)的映射是默認(rèn)設(shè)置,并且是在引用上啟用嵌入互操作類型的功能。
我們之前說過,C# 通常通過通過調(diào)用 工具(直接或通過 Visual Studio)生成的互操作類型來調(diào)用 COM 組件。
過去,唯一的選擇是引用互操作程序集,就像任何其他程序集一樣。這可能會很麻煩,因?yàn)槭褂脧?fù)雜的 COM 組件時,互操作程序集可能會變得非常大。例如,Microsoft Word 的小型加載項(xiàng)需要一個比自身大幾個數(shù)量級的互操作程序集。
您可以選擇嵌入所使用的部分,而不是引用互操作程序集。編譯器分析程序集以精確地計(jì)算出應(yīng)用程序所需的類型和成員,并直接在應(yīng)用程序中嵌入(僅)這些類型和成員的定義。這避免了膨脹以及需要發(fā)送其他文件。
若要啟用此功能,請?jiān)?Visual Studio 的解決方案資源管理器中選擇 COM 引用,然后在“屬性”窗口中將“嵌入互操作類型”設(shè)置為 true,或者按照前面所述編輯 文件(請參閱)。
CLR 支持鏈接互操作類型的。這意味著,如果兩個程序集分別鏈接到一個互操作類型,則如果這些類型包裝相同的 COM 類型,則它們將被視為等效。即使它們鏈接到的互操作程序集是獨(dú)立生成的,也是如此。
類型等效依賴于 System.Runtime.InteropServices 命名空間中的 TypeIdentifierAttribute 屬性。編譯器會在鏈接到互操作程序集時自動應(yīng)用此屬性。如果 COM 類型具有相同的 GUID,則認(rèn)為它們是等效的。
還可以使用 C# 編寫可在 COM 世界中使用的類。CLR 通過稱為 (CCW) 的代理使這成為可能。CCW 封送在兩個世界之間進(jìn)行類型(與 RCW 一樣),并根據(jù) COM 協(xié)議的要求實(shí)現(xiàn) IUnknown(以及可選的 IDispatch)。CCW 通過引用計(jì)數(shù)(而不是通過 CLR 的垃圾回收器)從 COM 端進(jìn)行生存期控制。
可以將任何公共類公開給 COM(作為“進(jìn)程內(nèi)”服務(wù)器)。為此,請首先創(chuàng)建一個接口,為其分配唯一的 GUID(在 Visual Studio 中,可以使用工具>),聲明它對 COM 可見,然后設(shè)置接口類型:
namespace MyCom
{
[ComVisible(true)]
[Guid ("226E5561-C68E-4B2B-BD28-25103ABCA3B1")] // Change this GUID
[InterfaceType (ComInterfaceType.InterfaceIsIUnknown)]
public interface IServer
{
int Fibonacci();
}
}
接下來,提供接口的實(shí)現(xiàn),為該實(shí)現(xiàn)分配唯一的 GUID:
namespace MyCom
{
[ComVisible(true)]
[Guid ("09E01FCD-9970-4DB3-B537-0EC555967DD9")] // Change this GUID
public class Server
{
public ulong Fibonacci (ulong whichTerm)
{
if (whichTerm < 1) throw new ArgumentException ("...");
ulong a=0;
ulong b=1;
for (ulong i=0; i < whichTerm; i++)
{
ulong tmp=a;
a=b;
b=tmp + b;
}
return a;
}
}
}
編輯您的 . 文件,添加以下行(粗體):
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<EnableComHosting>true</EnableComHosting>
</PropertyGroup>
現(xiàn)在,當(dāng)您生成項(xiàng)目時,會生成一個附加文件 ,該文件可以注冊為 COM 互操作。(請記住,根據(jù)您的項(xiàng)目配置,文件將始終為 32 位或 64 位:在這種情況下沒有“任何 CPU”這樣的東西。從的命令提示符下,切換到保存 DLL 的目錄并運(yùn)行 。
然后,可以從大多數(shù)支持 COM 的語言使用 COM 組件。例如,可以在文本編輯器中創(chuàng)建此 Visual Basic 腳本,并通過在 Windows 資源管理器中雙擊該文件來運(yùn)行它,或者像啟動程序一樣從命令提示符啟動它:
REM Save file as ComClient.vbs
Dim obj
Set obj=CreateObject("MyCom.Server")
result=obj.Fibonacci(12)
Wscript.Echo result
請注意,.NET Framework 不能加載到與 .NET 5+ 或 .NET Core 相同的進(jìn)程中。因此,.NET 5+ COM 服務(wù)器不能加載到 .NET Framework COM 客戶端進(jìn)程中,反之亦然。
傳統(tǒng)上,COM 將類型信息添加到注冊表。無注冊表 COM 使用清單文件而不是注冊表來控制對象激活。若要啟用此功能,請將以下行(粗體)添加到 文件:
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<EnableComHosting>true</EnableComHosting>
<EnableRegFreeCom>true</EnableRegFreeCom>
</PropertyGroup>
然后,您的構(gòu)建將生成。
.NET 5+ 不支持生成 COM 類型庫 (*.tlb)。您可以手動編寫 IDL(接口定義語言)文件或接口中本機(jī)聲明的C++標(biāo)頭。
023年中國計(jì)算機(jī)模塊(COM)市場規(guī)模達(dá) 億元(人民幣),全球計(jì)算機(jī)模塊(COM)市場規(guī)模達(dá) 億元。報告預(yù)測到2029年全球計(jì)算機(jī)模塊(COM)市場容量將達(dá) 億元。貝哲斯咨詢結(jié)合計(jì)算機(jī)模塊(COM)市場過去五年的增長態(tài)勢,給出了直觀的計(jì)算機(jī)模塊(COM)市場規(guī)模增長趨勢解析,并對未來計(jì)算機(jī)模塊(COM)市場發(fā)展趨勢做出合理預(yù)測。
按種類劃分,計(jì)算機(jī)模塊(COM)行業(yè)可細(xì)分為其他, 基于Arm計(jì)算機(jī)模塊, 基于X86計(jì)算機(jī)模塊。按最終用途劃分,計(jì)算機(jī)模塊(COM)可應(yīng)用于交通運(yùn)輸, 其他, 醫(yī)療, 工業(yè)自動化, 測試和測量, 游戲娛樂等領(lǐng)域。報告分析了各類型產(chǎn)品價格、市場規(guī)模、份額及發(fā)展趨勢;各應(yīng)用計(jì)算機(jī)模塊(COM)市場銷量、份額占比、及需求潛力。
中國計(jì)算機(jī)模塊(COM)市場主要企業(yè)有Congatec, Kontron, Portwell, 凌華科技, 北京眾達(dá)精電科技有限公司, 成都天玙興科技有限公司, 研華科技, 研揚(yáng)科技, 蘇州廣運(yùn)成智能控制有限公司等。報告包含對主要企業(yè)排行情況、市場占有率、經(jīng)營概況(涵蓋計(jì)算機(jī)模塊(COM)銷售量、銷售收入、價格、毛利、毛利率等)及業(yè)內(nèi)排行前三企業(yè)市占率的分析。
計(jì)算機(jī)模塊(COM)市場分析報告涵蓋了對計(jì)算機(jī)模塊(COM)行業(yè)簡介、發(fā)展現(xiàn)狀、市場特征、發(fā)展環(huán)境、計(jì)算機(jī)模塊(COM)市場規(guī)模數(shù)據(jù)、細(xì)分領(lǐng)域情況、競爭格局和發(fā)展趨勢等方面的分析。報告分別從產(chǎn)品類型、下游應(yīng)用領(lǐng)域及地區(qū)多個層面詳細(xì)分析了各領(lǐng)域市場概況。具體來看,包括各細(xì)分產(chǎn)品價格走勢、銷售量、銷售額趨勢以及計(jì)算機(jī)模塊(COM)在各應(yīng)用領(lǐng)域市場消費(fèi)規(guī)模,并對各細(xì)分市場走勢進(jìn)行預(yù)測。
模塊上的計(jì)算機(jī)(CoM)有時被稱為模塊上的系統(tǒng),是嵌入式系統(tǒng)中單板計(jì)算機(jī)的一種替代方案,它提供了一種更加靈活和輪廓化的解決方案。它被設(shè)計(jì)成插入載體或基板,通常是一個具有CPU和標(biāo)準(zhǔn)I/O能力的小型處理器模塊。通過使用CoM功能和自定義基板,避免了與設(shè)計(jì)CPU子系統(tǒng)相關(guān)的復(fù)雜工作。
主要企業(yè):
Congatec
Kontron
Portwell
凌華科技
北京眾達(dá)精電科技有限公司
成都天玙興科技有限公司
研華科技
研揚(yáng)科技
蘇州廣運(yùn)成智能控制有限公司
產(chǎn)品分類:
其他
基于Arm計(jì)算機(jī)模塊
基于X86計(jì)算機(jī)模塊
應(yīng)用領(lǐng)域:
交通運(yùn)輸
其他
醫(yī)療
工業(yè)自動化
測試和測量
游戲娛樂
報告著眼于中國華北、華中、華南、華東等重點(diǎn)地區(qū),并依次展開調(diào)研分析,涵蓋了各個地區(qū)計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展現(xiàn)狀與政策動向,也囊括了區(qū)域內(nèi)阻礙計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展的各類因素等。通過這些有利信息,業(yè)內(nèi)企業(yè)、相關(guān)公司及相關(guān)部門能夠準(zhǔn)確把握計(jì)算機(jī)模塊(COM)行業(yè)在不同地區(qū)的發(fā)展?jié)摿Γ_定最具潛力的市場并調(diào)整布局。
計(jì)算機(jī)模塊(COM)行業(yè)報告各章節(jié)核心內(nèi)容:
第一章:計(jì)算機(jī)模塊(COM)行業(yè)概述、市場規(guī)模及國內(nèi)外行業(yè)發(fā)展綜述;
第二章:產(chǎn)業(yè)競爭格局、集中度、及國內(nèi)外企業(yè)生態(tài)布局分析;
第三章:中國計(jì)算機(jī)模塊(COM)行業(yè)進(jìn)出口現(xiàn)狀、影響因素、及面臨的挑戰(zhàn)與對策分析;
第四章:中國華北、華中、華南、華東地區(qū)計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析與主要政策解讀;
第五、六章:中國計(jì)算機(jī)模塊(COM)各細(xì)分類型與計(jì)算機(jī)模塊(COM)在各細(xì)分應(yīng)用領(lǐng)域的市場銷售量、銷售額及增長率;
第七章:對計(jì)算機(jī)模塊(COM)產(chǎn)業(yè)內(nèi)重點(diǎn)企業(yè)發(fā)展概況、核心業(yè)務(wù)、市場布局、經(jīng)營狀況、市場份額變化、產(chǎn)品與服務(wù)、融資及合作動態(tài)等方面進(jìn)行分析;
第八、九章:中國計(jì)算機(jī)模塊(COM)各細(xì)分類型與計(jì)算機(jī)模塊(COM)在各細(xì)分應(yīng)用領(lǐng)域的市場銷售量、銷售額及增長率預(yù)測;
第十章:宏觀經(jīng)濟(jì)形勢、政策走向與可預(yù)見風(fēng)險分析;
第十一、十二章:中國計(jì)算機(jī)模塊(COM)市場規(guī)模預(yù)測、挑戰(zhàn)與機(jī)遇、問題及發(fā)展建議。
出版商: 湖南貝哲斯信息咨詢有限公司
目錄
第一章 計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展概述
1.1 計(jì)算機(jī)模塊(COM)行業(yè)概述
1.1.1 計(jì)算機(jī)模塊(COM)的定義及特點(diǎn)
1.1.2 計(jì)算機(jī)模塊(COM)的類型
1.1.3 計(jì)算機(jī)模塊(COM)的應(yīng)用
1.2 2019-2024年中國計(jì)算機(jī)模塊(COM)行業(yè)市場規(guī)模
1.3 國內(nèi)外計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展綜述
1.3.1 行業(yè)發(fā)展歷程
1.3.2 行業(yè)驅(qū)動因素
1.3.3 產(chǎn)業(yè)鏈結(jié)構(gòu)分析
1.3.4 技術(shù)發(fā)展?fàn)顩r
1.3.5 行業(yè)收購動態(tài)
第二章 產(chǎn)業(yè)競爭格局分析
2.1 產(chǎn)業(yè)競爭結(jié)構(gòu)分析
2.1.1 現(xiàn)有企業(yè)間競爭
2.1.2 潛在進(jìn)入者分析
2.1.3 替代品威脅分析
2.1.4 供應(yīng)商議價能力
2.1.5 客戶議價能力
2.2 產(chǎn)業(yè)集中度分析
2.2.1 市場集中度分析
2.2.2 區(qū)域集中度分析
2.3 國內(nèi)外重點(diǎn)企業(yè)計(jì)算機(jī)模塊(COM)生態(tài)布局
2.3.1 企業(yè)競爭現(xiàn)狀
2.3.2 行業(yè)分布情況
第三章 中國計(jì)算機(jī)模塊(COM)行業(yè)進(jìn)出口情況分析
3.1 計(jì)算機(jī)模塊(COM)行業(yè)出口情況分析
3.2 計(jì)算機(jī)模塊(COM)行業(yè)進(jìn)口情況分析
3.3 影響計(jì)算機(jī)模塊(COM)行業(yè)進(jìn)出口的因素
3.3.1 貿(mào)易摩擦對進(jìn)出口的影響
3.3.2 新冠疫情對進(jìn)出口的影響
3.3.3 俄羅斯和烏克蘭事件對進(jìn)出口的影響
3.4 計(jì)算機(jī)模塊(COM)行業(yè)進(jìn)出口面臨的挑戰(zhàn)及對策
第四章 中國重點(diǎn)地區(qū)計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.1 2019-2024年華北計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.1.1 2019-2024年華北計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.1.2 2019-2024年華北計(jì)算機(jī)模塊(COM)行業(yè)主要政策解讀
4.2 2019-2024年華中計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.2.1 2019-2024年華中計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.2.2 2019-2024年華中計(jì)算機(jī)模塊(COM)行業(yè)主要政策解讀
4.3 2019-2024年華南計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.3.1 2019-2024年華南計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.3.2 2019-2024年華南計(jì)算機(jī)模塊(COM)行業(yè)主要政策解讀
4.4 2019-2024年華東計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.4.1 2019-2024年華東計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展?fàn)顩r分析
4.4.2 2019-2024年華東計(jì)算機(jī)模塊(COM)行業(yè)主要政策解讀
第五章 2019-2024年中國計(jì)算機(jī)模塊(COM)細(xì)分類型市場運(yùn)營分析
5.1 計(jì)算機(jī)模塊(COM)行業(yè)產(chǎn)品分類標(biāo)準(zhǔn)
5.2 2019-2024年中國市場計(jì)算機(jī)模塊(COM)主要類型價格走勢
5.3 影響中國計(jì)算機(jī)模塊(COM)行業(yè)產(chǎn)品價格波動的因素
5.4 中國市場計(jì)算機(jī)模塊(COM)主要類型銷售量、銷售額
5.5 2019-2024年中國市場計(jì)算機(jī)模塊(COM)主要類型銷售量分析
5.5.1 2019-2024年其他市場銷售量分析
5.5.2 2019-2024年基于Arm計(jì)算機(jī)模塊市場銷售量分析
5.5.3 2019-2024年基于X86計(jì)算機(jī)模塊市場銷售量分析
5.6 2019-2024年中國市場計(jì)算機(jī)模塊(COM)主要類型銷售額分析
第六章 2019-2024年中國計(jì)算機(jī)模塊(COM)終端應(yīng)用領(lǐng)域市場運(yùn)營分析
6.1 終端應(yīng)用領(lǐng)域的下游客戶端分析
6.2 中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域的市場潛力分析
6.3 中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域銷售量、銷售額
6.4 2019-2024年中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域銷售量分析
6.4.1 2019-2024年交通運(yùn)輸市場銷售量分析
6.4.2 2019-2024年其他市場銷售量分析
6.4.3 2019-2024年醫(yī)療市場銷售量分析
6.4.4 2019-2024年工業(yè)自動化市場銷售量分析
6.4.5 2019-2024年測試和測量市場銷售量分析
6.4.6 2019-2024年游戲娛樂市場銷售量分析
6.5 2019-2024年中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域銷售額分析
第七章 計(jì)算機(jī)模塊(COM)產(chǎn)業(yè)重點(diǎn)企業(yè)分析
7.1 Congatec
7.1.1 Congatec發(fā)展概況
7.1.2 企業(yè)核心業(yè)務(wù)
7.1.3 Congatec 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.1.4 Congatec業(yè)務(wù)經(jīng)營分析
7.1.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.1.6 企業(yè)融資狀況、合作動態(tài)
7.2 Kontron
7.2.1 Kontron發(fā)展概況
7.2.2 企業(yè)核心業(yè)務(wù)
7.2.3 Kontron 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.2.4 Kontron業(yè)務(wù)經(jīng)營分析
7.2.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.2.6 企業(yè)融資狀況、合作動態(tài)
7.3 Portwell
7.3.1 Portwell發(fā)展概況
7.3.2 企業(yè)核心業(yè)務(wù)
7.3.3 Portwell 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.3.4 Portwell業(yè)務(wù)經(jīng)營分析
7.3.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.3.6 企業(yè)融資狀況、合作動態(tài)
7.4 凌華科技
7.4.1 凌華科技發(fā)展概況
7.4.2 企業(yè)核心業(yè)務(wù)
7.4.3 凌華科技 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.4.4 凌華科技業(yè)務(wù)經(jīng)營分析
7.4.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.4.6 企業(yè)融資狀況、合作動態(tài)
7.5 北京眾達(dá)精電科技有限公司
7.5.1 北京眾達(dá)精電科技有限公司發(fā)展概況
7.5.2 企業(yè)核心業(yè)務(wù)
7.5.3 北京眾達(dá)精電科技有限公司 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.5.4 北京眾達(dá)精電科技有限公司業(yè)務(wù)經(jīng)營分析
7.5.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.5.6 企業(yè)融資狀況、合作動態(tài)
7.6 成都天玙興科技有限公司
7.6.1 成都天玙興科技有限公司發(fā)展概況
7.6.2 企業(yè)核心業(yè)務(wù)
7.6.3 成都天玙興科技有限公司 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.6.4 成都天玙興科技有限公司業(yè)務(wù)經(jīng)營分析
7.6.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.6.6 企業(yè)融資狀況、合作動態(tài)
7.7 研華科技
7.7.1 研華科技發(fā)展概況
7.7.2 企業(yè)核心業(yè)務(wù)
7.7.3 研華科技 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.7.4 研華科技業(yè)務(wù)經(jīng)營分析
7.7.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.7.6 企業(yè)融資狀況、合作動態(tài)
7.8 研揚(yáng)科技
7.8.1 研揚(yáng)科技發(fā)展概況
7.8.2 企業(yè)核心業(yè)務(wù)
7.8.3 研揚(yáng)科技 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.8.4 研揚(yáng)科技業(yè)務(wù)經(jīng)營分析
7.8.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.8.6 企業(yè)融資狀況、合作動態(tài)
7.9 蘇州廣運(yùn)成智能控制有限公司
7.9.1 蘇州廣運(yùn)成智能控制有限公司發(fā)展概況
7.9.2 企業(yè)核心業(yè)務(wù)
7.9.3 蘇州廣運(yùn)成智能控制有限公司 計(jì)算機(jī)模塊(COM)領(lǐng)域布局
7.9.4 蘇州廣運(yùn)成智能控制有限公司業(yè)務(wù)經(jīng)營分析
7.9.5 計(jì)算機(jī)模塊(COM)產(chǎn)品和服務(wù)介紹
7.9.6 企業(yè)融資狀況、合作動態(tài)
第八章 2024-2029年中國計(jì)算機(jī)模塊(COM)細(xì)分類型市場銷售趨勢預(yù)測分析
8.1 中國計(jì)算機(jī)模塊(COM)市場主要類型銷售量、銷售額預(yù)測
8.2 2024-2029年中國市場計(jì)算機(jī)模塊(COM)主要類型銷售量預(yù)測
8.3 2024-2029年中國市場計(jì)算機(jī)模塊(COM)主要類型銷售額預(yù)測
8.3.1 2024-2029年其他市場銷售額預(yù)測
8.3.2 2024-2029年基于Arm計(jì)算機(jī)模塊市場銷售額預(yù)測
8.3.3 2024-2029年基于X86計(jì)算機(jī)模塊市場銷售額預(yù)測
8.4 2024-2029年中國計(jì)算機(jī)模塊(COM)市場主要類型價格走勢預(yù)測
第九章 2024-2029年中國計(jì)算機(jī)模塊(COM)終端應(yīng)用領(lǐng)域市場銷售趨勢預(yù)測分析
9.1 中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域銷售量、銷售額預(yù)測
9.2 2024-2029年中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域銷售量預(yù)測
9.3 2024-2029年中國市場計(jì)算機(jī)模塊(COM)主要終端應(yīng)用領(lǐng)域銷售額預(yù)測分析
9.3.1 2024-2029年交通運(yùn)輸市場銷售額預(yù)測分析
9.3.2 2024-2029年其他市場銷售額預(yù)測分析
9.3.3 2024-2029年醫(yī)療市場銷售額預(yù)測分析
9.3.4 2024-2029年工業(yè)自動化市場銷售額預(yù)測分析
9.3.5 2024-2029年測試和測量市場銷售額預(yù)測分析
9.3.6 2024-2029年游戲娛樂市場銷售額預(yù)測分析
第十章 中國計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展環(huán)境預(yù)測
10.1 宏觀經(jīng)濟(jì)形勢分析
10.2 政策走向分析
10.3 計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展可預(yù)見風(fēng)險分析
第十一章 疫情影響下,計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展前景
11.1 2024-2029年中國計(jì)算機(jī)模塊(COM)行業(yè)市場規(guī)模預(yù)測
11.2 新冠疫情態(tài)勢
11.3 發(fā)展面臨挑戰(zhàn)
11.4 挑戰(zhàn)中的機(jī)遇
11.5 發(fā)展策略建議
11.6 相關(guān)行動項(xiàng)目
第十二章 中國計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展問題及相關(guān)建議
12.1 主要問題分析
12.2 產(chǎn)業(yè)發(fā)展瓶頸
12.3 行業(yè)發(fā)展建議
報告包含計(jì)算機(jī)模塊(COM)行業(yè)發(fā)展現(xiàn)狀、產(chǎn)業(yè)鏈結(jié)構(gòu)、進(jìn)出口情況、競爭格局及市場未來走勢和前景等。細(xì)分層面,報告對種類及應(yīng)用細(xì)分市場發(fā)展現(xiàn)狀和前景、行業(yè)當(dāng)前競爭格局及集中度、營收情況、中國重點(diǎn)地區(qū)發(fā)展前景等做出了詳盡的分析及判斷。以計(jì)算機(jī)模塊(COM)行業(yè)數(shù)據(jù)為基礎(chǔ),結(jié)合專家觀點(diǎn)與建議,輔以直觀明了圖表數(shù)據(jù)與透徹的文字分析,本報告提供全面準(zhǔn)確的市場數(shù)據(jù)以及市場關(guān)鍵驅(qū)因和市場潛力分析。
計(jì)算機(jī)模塊(COM)市場報告通過專業(yè)、客觀的行業(yè)深度研究,旨在幫助計(jì)算機(jī)模塊(COM)企業(yè)根據(jù)階段性市場動態(tài)調(diào)整發(fā)展戰(zhàn)略,有效促進(jìn)企業(yè)業(yè)務(wù)能力,提升行業(yè)競爭力,加快進(jìn)行發(fā)展戰(zhàn)略實(shí)施。