string strFile = "E:\\123.gif";
string strText = "abc";
Int32 iResult = MakeImage(100,100, strText, strFile);
C++ C#
=====================================
WORD ushort
DWORD uint
UCHAR int/byte 大部分情况都可以使用int代替,而如果需要严格对齐的话则应该用bytebyte
UCHAR* string/IntPtr
unsigned char* [MarshalAs(UnmanagedType.LPArray)]byte[]/?(Intptr)
char* string
LPCTSTR string
LPTSTR [MarshalAs(UnmanagedType.LPTStr)] string
long int
ulong uint
Handle IntPtr
HWND IntPtr
void* IntPtr
int int
int* ref int
*int IntPtr
unsigned int uint
COLORREF uint
遇到问题:
对 PInvoke 函数“Compress!Compress.Form1::SYUNCompress”的调用导致堆栈不对称。原因可能是托管
的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签
名是否匹配。
解决方法:[DllImport("SYCompression.dll", CallingConvention = CallingConvention.Cdecl)]
Cdecl StdCall Winapi取决于:
#define FUNCEXPORT extern "C" _declspec(dllexport)应该用Cdecl。
无法封送处理“return value”: 无效的托管/非托管类型组合。
原因是byte[]不能直接做为返回类型,必须用IntPtr类型。
[DllImport("SYCompression.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr SYUNCompress(byte[] srcBuf, int srcLen, out int tarLen); //返回参数必须用IntPtr,传递参数必须用byte[],都不能用string
loadCompressLib();
byte[] tarBuf = new byte[srcLen];
int lpByte = (int)SYUNCompress(data, srcLen, out tarLen);
byte[] vbyte = new byte[tarLen];
for (int i = 0; i < tarLen; i++)
{
vbyte[i] = (byte)Marshal.PtrToStructure((IntPtr)(lpByte++), typeof(byte));
}
WriteFile(vbyte, tarLen);
freeBuf();
(原文:http://blog.chinaunix.net/u/18297/showart_296028.html)
为了能用上原来的C++代码,只好研究下从C# 中调用DLL
首先必须要有一个声明,使用的是DllImport关键字:
包含DllImport所在的名字空间
using System.Runtime.InteropServices;
public class XXXX{
[DllImport(“MyDLL.dll")]
public static extern int mySum(int a,int b);
}
[DllImport(“MyDLL.dll")]
public static extern int mySum(int a,int b);
代码中DllImport关键字作用是告诉编译器入口点在哪里,并将打包函数捆绑在这个类中
在调用的时候
在类中的时候 直接 mySum(a,b);就可以了
在其他类中调用: XXXX. mySum(a,b);
[DllImport(“MyDLL.dll”)]在申明的时候还可以添加几个属性
[DllImport(“MyDLL.dll", EntryPoint=" mySum",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)
]
EntryPoint: 指定要调用的 DLL 入口点。默认入口点名称是托管方法的名称 。
CharSet: 控制名称重整和封送 String 参数的方式 (默认是UNICODE)
CallingConvention指示入口点的函数调用约定(默认WINAPI)(上次报告讲过的)
Cdecl | 调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。 | |
FastCall | 不支持此调用约定。 | |
StdCall | 被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。 | |
ThisCall | 第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。 | |
Winapi | 此成员实际上不是调用约定,而是使用了默认平台调用约定。例如,在 Windows 上默认为 StdCall,在 Windows CE.NET 上默认为 Cdecl。 |
一.C#调用DLL与应用程序
动态链接库(也称为DLL,即为“Dynamic Link Library”的缩写)是Microsoft Windows最重要的组成要素之一,打开Windows系统文件夹,你会发现文件夹中有很多DLL文件,Windows就是将一些主要的系统功能以DLL模块的形式实现。
动态链接库是不能直接执行的,也不能接收消息,它只是一个独立的文件,其中包含能被程序或其它DLL调用来完成一定操作的函数(方法。注:C#中一般称为“方法”),但这些函数不是执行程序本身的一部分,而是根据进程的需要按需载入,此时才能发挥作用。
DLL只有在应用程序需要时才被系统加载到进程的虚拟空间中,成为调用进程的一部分,此时该DLL也只能被该进程的线程访问,它的句柄可以被调用进程所使用,而调用进程的句柄也可以被该DLL所使用。在内存中,一个DLL只有一个实例,且它的编制与具体的编程语言和编译器都没有关系,所以可以通过DLL来实现混合语言编程。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。
下面列出了当程序使用 DLL 时提供的一些优点:
1)使用较少的资源
当多个程序使用同一个函数库时,DLL 可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其他在 Windows 操作系统上运行的程序。
2)推广模块式体系结构
DLL 有助于促进模块式程序的开发。这可以帮助您开发要求提供多个语言版本的大型程序或要求具有模块式体系结构的程序。模块式程序的一个示例是具有多个可以在运行时动态加载的模块的计帐程序。
3)简化部署和安装
当 DLL 中的函数需要更新或修复时,部署和安装 DLL 不要求重新建立程序与该 DLL 的链接。此外,如果多个程序使用 同一个 DLL,那么多个程序都将从该更新或修复中获益。当您使用定期更新或修复的第三方 DLL 时,此问题可能会更频繁地出现。
二.DLL的调用
每种编程语言调用DLL的方法都不尽相同,在此只对用C#调用DLL的方法进行介绍。首先,您需要了解什么是托管,什么是非托管。一般可以认为:非托管代码主要是基于win 32平台开发的DLL,activeX的组件,托管代码是基于.net平台开发的。如果您想深入了解托管与非托管的关系与区别,及它们的运行机制,请您自行查找资料,本文件在此不作讨论。
C#调用DLL中的非托管函数一般方法
首先,应该在C#语言源程序中声明外部方法,其基本形式是:
[DLLImport(“DLL文件”)]
修饰符 extern 返回变量类型 方法名称 (参数列表)
其中:
DLL文件:包含定义外部方法的库文件。修饰符: 访问修饰符,除了abstract以外在声明方法时可以使用的修饰符。返回变量类型:在DLL文件中你需调用方法的返回变量类型。方法名称:在DLL文件中你需调用方法的名称。参数列表:在DLL文件中你需调用方法的列表。
注意:需要在程序声明中使用System.Runtime.InteropServices命名空间。
DllImport只能放置在方法声明上。
DLL文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中Path所设置的路径)。
返回变量类型、方法名称、参数列表一定要与DLL文件中的定义相一致。
若要使用其它函数名,可以使用EntryPoint属性设置,如:
[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);
其它可选的 DllImportAttribute 属性:
◆CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;
◆SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;
◆ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;
◆PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;
◆CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;
本主题说明 DllImport 属性的常见用法。第一节讨论使用 DllImport 从托管应用程序调用本机代码的优点。第二节集中讨论封送处理和DllImport 属性的各个方面。
当在托管应用程序中重用现有的非托管代码时,DllImport 属性非常有用。例如,托管应用程序可能需要调用非托管 WIN32 API。
下面的代码示例说明此通用方案,此示例将调用 MessageBox(位于 User32.lib 中):
#usingusing namespace System::Runtime::InteropServices; // for DllImportAttribute namespace SysWin32 { [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)] int MessageBox(void* hWnd, wchar_t* lpText, wchar_t* lpCaption, unsigned int uType); } int main( ) { SysWin32::MessageBox( 0, L"Hello world!", L"Greetings", 0 ); }
主要注意包含 DllImport 的代码行。此代码行根据参数值通知编译器,使之声明位于 User32.dll 中的函数并将签名中出现的所有字符串(如参数或返回值)视为 Unicode 字符串。如果缺少EntryPoint
参数,则默认值为函数名。另外,由于 CharSet
参数指定 Unicode,因此公共语言运行库将首先查找称为MessageBoxW(有 W 是因为 Unicode 规范)的函数。如果运行库未找到此函数,它将根据调用约定查找MessageBox 以及相应的修饰名。受支持的调用约定只有 __cdecl 和 __stdcall。
当调用用户定义的 DLL 中所包含的函数时,有必要将 extern "C"
添加在 DLL 函数声明之前,如下所示:
// The function declaration in SampleDLL.h file extern "C" SAMPLEDLL_API int fnSampleDLL(void);
有关受支持的其他参数值的更多信息,请参见 DllImport。
除使用上述方法外,还可以使用另一种方法将托管参数(来自托管应用程序)封送处理为非托管参数(在非托管 DLL 中)。
以下代码示例说明封送处理技术:
#usingusing namespace System; // To bring System::String in using namespace System::Runtime::InteropServices; // for DllImportAttribute namespace SysWin32 { [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)] Int32 MessageBox( Int32 hWnd, String* lpText, String* lpCaption, UInt32 uType ); } int main( ) { SysWin32::MessageBox(0, S"Hello world!", S"Greetings", 0); }
完成实际的调用后,由于 CharSet
参数值的作用,所有参数字符串都自动转换为 wchar_t*。同样,所有Int32 参数类型都转换为非托管int,而 UInt32 参数类型转换为非托管unsigned int。
下表提供关于转换非托管和托管上下文的指导:
非托管代码 | C++ 的托管扩展 |
---|---|
int | Int32 |
unsigned int | UInt32 |
short | Int16 |
char* | 用于 [in] 参数的 String* (CharSet = Ansi),用于 [out] 参数或返回值的Text::StringBuilder*。 |
wchar_t* | 用于 [in] 参数的 String* (CharSet = Unicode),用于 [out] 参数或返回值的Text::StringBuilder*。 |
函数指针(回调) 限制:函数指针必须具有 __stdcall 调用约定,因为这是 DllImport 支持的唯一类型。 |
委托类型 |
数组(如 wchar_t*[]) 限制:CharSet 参数仅应用于函数参数的根类型。因此,无论 CharSet 的值是什么,String* __gc[] 都将被封送处理为wchar_t* []。 |
相应类型的托管数组(如 String*__gc[]) |
除简单类型外,运行库还提供了一种机制,可以将简单结构由托管上下文封送处理为非托管上下文。简单结构不包含任何内部数据成员指针、结构化类型的成员或其他元素。
例如,本主题显示如何调用本机 DLL 中具有以下签名的函数:
#includestruct S { char* str; int n; }; int __cdecl func( struct S* p ) { printf( "%s\n", p->str ); return p->n; }
若要创建此函数的托管包装,请将 StructLayout 属性应用到调用类。此属性确定封送处理结构时结构的组织方式。若要确保以传统的 C 格式组织结构,请指定顺序布局 (LayoutKind::Sequential
)。结果代码如下:
#usingusing namespace System; using namespace System::Runtime::InteropServices; // CharSet = Ansi(Unicode) means that everything that is a string // in this structure should be marshaled as Ansi(Unicode) // strings [StructLayout( LayoutKind::Sequential, CharSet=Ansi )] __gc class MS // To be compatible with the type in the native // code, this structure should have the members laid out in // the same order as those in the native struct { public: String* m_str; Int32 m_n; }; [DllImport("some.dll")] Int32 func( MS* ptr ); int main( ) { MS* p = new MS; p->m_str = S"Hello native!"; p->m_n = 7; Console::WriteLine(func(p)); // Should print 7 }
也可以在托管应用程序中使用 __nogc 关键字,以确保不发生封送处理:
#include#include #using using namespace System; using namespace System::Runtime::InteropServices; __nogc class UMS { public: char* m_str; int m_n; }; [DllImport("some.dll")] Int32 func( UMS* ptr ); int main( ) { UMS* p = new UMS; p->m_str = strdup( "Hello native!" ); p->m_n = 7; Console::WriteLine(func(p)); // Should print 7 free( p->m_str ); delete p; }
第二个方案是:
#includestruct S { wchar_t* str; int n; }; int __cdecl func( struct S p ) { printf( "%S\n", p.str ); return p.n; }
注意参数是通过值传递的。若要在托管应用程序中包装此调用,请使用值而不是 __gc 类型。结果代码如下:
#usingusing namespace System; using namespace System::Runtime::InteropServices; [StructLayout( LayoutKind::Sequential, CharSet=Unicode )] __value class VS { public: String* m_str; Int32 m_n; }; [DllImport( "some.dll" )] Int32 func( VS ptr ); int main( ) { VS v; v.m_str = S"Hello native!"; v.m_n = 7; Console::WriteLine(func(v)); // should print 7 also }