惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
Google DeepMind News
Google DeepMind News
Hugging Face - Blog
Hugging Face - Blog
D
Docker
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
J
Java Code Geeks
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Simon Willison's Weblog
Simon Willison's Weblog
S
Security Affairs
NISL@THU
NISL@THU
T
Tor Project blog
A
About on SuperTechFans
宝玉的分享
宝玉的分享
腾讯CDC
S
Schneier on Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Privacy & Cybersecurity Law Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
雷峰网
雷峰网
C
Cyber Attacks, Cyber Crime and Cyber Security
Vercel News
Vercel News
Cisco Talos Blog
Cisco Talos Blog
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
L
LINUX DO - 热门话题
Microsoft Security Blog
Microsoft Security Blog
Latest news
Latest news
C
Check Point Blog
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
云风的 BLOG
云风的 BLOG
SecWiki News
SecWiki News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
爱范儿
爱范儿
月光博客
月光博客
V
Vulnerabilities – Threatpost
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs

博客园 - wuhang

今天看到一个好的WEB进度条,博客园果然是富饶啊,先记录下来以备后用 c#后台写javascript前端调用 动态SQL语句根据数据库列来查询数据,动态的生成列名称 asp.net的自动跳转 【转载来的】关于句柄的一些知识 [转载来的]C# API 大全 [心情日記]亦夢亦幻 【转载来的】C# 2.0中P/Invoke支持的几处增强 [转载来的]P/Invoke使用Win32非托管函数3 [转载来的]P/Invoke使用Win32非托管函数1 转载来的WCF基础知识问与答 转载来的,线程的基础 转载来的线程池 转载来的WinForm多线程编程篇 还是转载的C# 插件构架实战 依旧是转载来的Thread and Sync In C# (C#中的线程与同步) 转载来的C# 2.0中P/Invoke支持的几处增强 还是转载来的P/Invoke使用Win32非托管函数 转载来的P/Invoke调用,来自MSDN
[转载来的]P/Invoke使用Win32非托管函数2
wuhang · 2008-05-29 · via 博客园 - wuhang
  

简单字符串

下面是一个接受字符串参数的函数的简单示例: 

BOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName,     // 根路径
LPDWORD lpSectorsPerCluster,  // 每个簇的扇区数
LPDWORD lpBytesPerSector,    // 每个扇区的字节数
LPDWORD lpNumberOfFreeClusters, // 可用的扇区数
LPDWORD lpTotalNumberOfClusters // 扇区总数
);

 

根路径定义为 LPCTSTR。这是独立于平台的字符串指针。 

由于不存在名为 GetDiskFreeSpace() 的函数,封送拆收器将自动查找“A”或“W”变体,并调用相应的函数。我们使用一个属性来告诉封送拆收器,API 所要求的字符串类型。 

以下是该函数的完整定义,就象我开始定义的那样: 

[DllImport("kernel32.dll")]
static extern bool GetDiskFreeSpace(
[MarshalAs(UnmanagedType.LPTStr)]
string rootPathName,
 ref int sectorsPerCluster,
 ref int bytesPerSector,
 ref int numberOfFreeClusters,
 ref int totalNumberOfClusters);

  

不幸的是,当我试图运行时,该函数不能执行。问题在于,无论我们在哪个平台上,封送拆收器在默认情况下都试图查找 API 的 Ansi 版本,由于  LPTStr 意味着在 Windows NT 平台上会使用 Unicode 字符串,因此试图用 Unicode 字符串来调用 Ansi 函数就 会失败。 

有两种方法可以解决这个问题:一种简单的方法是删除 MarshalAs 属性。如果这样做,将始终调用该函数的 A 版 本,如果在您所涉及的所有平台上都有这种版本,这是个很好的方法。但是,这会降低代码的执行速度,因为封送拆收器要将 .NET 字符串从  Unicode 转换为多字节,然后调用函数的 A 版本(将字符串转换回 Unicode),最后调用函数的 W 版本。 

要避免出现这种情况,您需要告诉封送拆收器,要它在 Win9x 平台上时查找 A 版本,而在 NT 平台上时查找 W 版本。要实现这一目的,可以将 CharSet 设置为 DllImport 属性的一部分: 

[DllImport("kernel32.dll", CharSet = CharSet.Auto)] 

在我的非正式计时测试中,我发现这一做法比前一种方法快了大约百分之五。 

对于大多数 Win32 API,都可以对字符串类型设置 CharSet 属性并使用 LPTStr。但是,还有一些不采用 A/W 机制的函数,对于这些函数必须采取不同的方法。

字符串缓冲区

.NET 中的字符串类型是不可改变的类型,这意味着它的值将永远保持不变。对于要将字符串值复制到字符串缓冲区的函数,字符串将无效。这样做至少会破 坏由封送拆收器在转换字符串时创建的临时缓冲区;严重时会破坏托管堆,而这通常会导致错误的发生。无论哪种情况都不可能获得正确的返回值。 

要解决此问题,我们需要使用其他类型。StringBuilder 类型就是被设计为用作缓冲区的,我们将使用它来代替字符串。下面是一个示例: 

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetShortPathName(
 [MarshalAs(UnmanagedType.LPTStr)]
 string path,
 [MarshalAs(UnmanagedType.LPTStr)]
 StringBuilder shortPath,
 int shortPathLength);

  

使用此函数很简单: 

StringBuilder shortPath = new StringBuilder(80);
int result = GetShortPathName(
@"d:"test.jpg", shortPath, shortPath.Capacity);
string s = shortPath.ToString();

  

请注意,StringBuilder 的 Capacity 传递的是缓冲区大小。 

具有内嵌字符数组的结构

某些函数接受具有内嵌字符数组的结构。例如,GetTimeZoneInformation() 函数接受指向以下结构的指针: 

typedef struct _TIME_ZONE_INFORMATION { 
LONG    Bias; 
WCHAR   StandardName[ 32 ]; 
SYSTEMTIME StandardDate; 
LONG    StandardBias; 
WCHAR   DaylightName[ 32 ]; 
SYSTEMTIME DaylightDate; 
LONG    DaylightBias; 
} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;

  

在 C# 中使用它需要有两种结构。一种是 SYSTEMTIME,它的设置很简单: 

struct SystemTime
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}

这里没有什么特别之处;另一种是 TimeZoneInformation,它的定义要复杂一些: 

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation
{
public int bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string standardName;
SystemTime standardDate;
public int standardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string daylightName;
SystemTime daylightDate;
public int daylightBias;
}

此定义有两个重要的细节。第一个是 MarshalAs 属性:

 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 

查看 ByValTStr 的文档,我们发现该属性用于内嵌的字符数组;另一个是 SizeConst,它用于设置数组的大小。 

我在第一次编写这段代码时,遇到了执行引擎错误。通常这意味着部分互操作覆盖了某些内存,表明结构的大小存在错误。我使用  Marshal.SizeOf() 来获取所使用的封送拆收器的大小,结果是 108 字节。我进一步进行了调查,很快回忆起用于互操作的默认字符类型 是 Ansi 或单字节。而函数定义中的字符类型为 WCHAR,是双字节,因此导致了这一问题。 

我通过添加 StructLayout 属性进行了更正。结构在默认情况下按顺序布局,这意味着所有字段都将以它们列出的顺序排列。CharSet 的值被设置为 Unicode,以便始终使用正确的字符类型。 

经过这样处理后,该函数一切正常。您可能想知道我为什么不在此函数中使用 CharSet.Auto。这是因为,它也没有 A 和 W 变体,而始终使用 Unicode 字符串,因此我采用了上述方法编码。