dll文件的c++制作
1、首先用vs2005建立一个c++的dll动态链接库文件,这时,
// DllTest.cpp : 定义 DLL 应用程序的入口点。
//
#include "stdafx.h"
//#include "DllTest.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
这段代码会自动生成,
2、自己建一个DllTest.h的头文件,和DllTest.def的块声明文件。
其中头文件是为了声明内部函数使用。块声明主要是为了在dll编译成功后固定好方法名。别忘记添加#include "DllTest.h"
3、在DllTest.h中加入如下代码
#ifndef DllTest_01
#define DllTest_01
#define EXPORT extern "C" __declspec(dllexport)
//两个参数做加法
EXPORT int _stdcall Add(int iNum1=0,int iNum2=0);
//两个参数做减法
EXPORT int _stdcall Subtraction(int iNum1=0,int iNum2=0,int iMethod=0);
#endif
4、在DllTest.def中加入如下代码
LIBRARY "DllTest"
EXPORTS
Add
Subtraction
5、在DllTest.cpp中写好代码为
// DllTest.cpp : 定义 DLL 应用程序的入口点。
//
#include "stdafx.h"
#include "DllTest.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
//加函数
int APIENTRY Add(int a,int b) // APIENTRY 此关键字不可少
{
return (a+b);
}
//减函数
int APIENTRY Subtraction(int a,int b,int i)
{
if(0==i)
return (a-b);
else
return (b-a);
}
6、这样编译生成就可以得到对应的DllTest.dll的文件了
二、C#调用dll文件
1、创建一个c#的控制台程序(当然其他也没有问题),自动生成以下代码
using System;
using System.Collections.Generic;
using System.Text;
//using System.Runtime.InteropServices;
namespace CSharpIncludeC__Dll
{
class Program
{
static void Main(string[] args)
{
}
}
}
2、添加命名空间using System.Runtime.InteropServices;
3、若要引用dll文件,首先吧dll文件自行拷贝到bin\debug,文件夹下,没有的话,先编译一下。
4、添加属性
[DllImp
static extern int Add(int iNum1, int iNum2);
5、最终产生代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace CSharpIncludeC__Dll
{
class Program
{
[DllImp
static extern int Add(int iNum1, int iNum2);
[DllImp
static extern int Subtraction(int iNum1,int iNum2,int iMethod);
static void Main(string[] args)
{
int iValue = Add(1, 2);
Console.WriteLine(iValue);
iValue = Subtraction(1, 2, 1);
Console.WriteLine(iValue);
Console.Read();
}
}
}
6、生成项目运行就可以了,结果是3和1
首先,我们写一个小小的例子
1.首先在VS2008中建立一个解决方案,在解决方案中新建一个项目,选择win32项目,再选择DLL,空项目。就建立了一个空的DLL项目,在头文件文件夹和源文件文件夹中分别建立firstdll.h和firstdll.cpp两个文件,我们将在firstdll.h文件中声明dll对外提供的函数的声明和类的定义。代码如下:
/*----------firstdll.h--------------------------------------------------------*/
#ifndef FIRSTDLL_H
#define FIRSTDLL_H
#ifdef DLLEXPORT
#define DLLOPTION _declspec(dllexport) //表明标有此宏定义的函数和类是dll文件的导出函数和类,是dll文件的对外接口
#else
#define DLLOPTION _declspec(dllimport) //表明标有此宏定义的函数和类的定义在dll文件中
#endif
class DLLOPTION CTest{
public:
virtual void sayHello(); //如果要在运行时动态链接导出类的成员函数必须声明为 virtual
};
extern "C" DLLOPTION CTest* getCTestInstance();
#endif
/*-----------firstdll.cpp-----------------------*/ //为firstdll.h中的导出函数和导出类的成员函数提供具体实现
#include <iostream>
#define DLLEXPORT //定义了预处理器变量 DLLEXPORT
#include "firstdll.h"
using std::cout;
using std::endl;
void CTest::sayHello(){
cout << "Hello i come from dll"<<endl;
return;
}
CTest* getCTestInstance(){
return new CTest();
}
到此为止一个简单的dll文件所需要代码都有了,在项目上右键,选择生成,就会在解决方案的Debug文件夹下产生一个firstdll.dll动态链接库文件。
2.运行时动态链接的实现
首先,在同一个解决方案中建立一个新的win32项目。将firstdll.dll复制到项目文件夹下,再在IDE中将头文件添加项目中。编辑firstdll.dll文件将导出函数中要导出的成员函数改为virtual void sayHello()=0;(如果你是在加载时动态链接dll文件则不需要这么麻烦)
下面我们要编写运行时动态链接的代码了。我们在项目的源文件目录中建一个test.cpp文件。代码如下:
/*----------------test.cpp----------------------------------------------------------*/
#include <windows.h>
#include <iostream>
#include "firstdll.h" //注意在导入firstdll.h文件之前,没有在声明DLLEXPORT 预处理器变量
#pragma comment(linker, "/subsystem:console ")
//告诉连接器,程序运行的方式是 win32 console. /subsystem:console 是连接器选项
using std::cout;
using std::endl;
int main(){
LPWSTR lpws = L"firstdll.dll";
typedef CTest* (*dllProc)(); //定义一个函数指针类型,将来会用该类型的指针调用CTest* getCTestInstance()函数
HINSTANCE hdll = LoadLibrary(lpws); //winapi 参数是dll文件的变量名/全路径名+变量名 。返回dll文件的句柄
if(hdll != NULL){
//winapi 利用dll句柄和导出函数的函数名得到函数的入口地址。返回 void* 所以要强转
dllProc pdp = (dllProc)GetProcAddress(hdll,"getCTestInstance");
CTest* pCTest = (pdp)(); //执行导出函数 返回指向CTest类的指针
pCTest->sayHello(); //利用类指针执行导出类的成员函数
delete pCTest;
FreeLibrary(hdll); //望名生义 此winapi的作用是释放被动态链接到程序中的dll文件
cout << "the result of test is successful !!" <<endl;
}else{
cout << "Can not get handle of classdll.dll" << endl;
}
system("pause");
return 0;
}
第一次写的话难免出错比如创建项目是没有创建成win32 console application 而是 创建成 win32 application 可能会导致程序找不到入口所以显式的使用:#pragma comment(linker, "/subsystem:console ")
本人第一次写的时候还导致过访问冲突,结果是因为返回dll文件句柄的函数返回的是空。
发现程序不对了,可以调用GetLastError() api 返回错误的编号,然后在msdn中查找错误的原因。
如果调用不到dll文件中的导出函数,有可能是dll文件有问题。可以用"C:\Program Files\Microsoft Visual Studio\COMMON\Tools\DEPENDS.EXE"来查看dll文件的内容,这个
软件的使用方法我不是懂,我也就是看看那个函数在dll中到底有没有。
一. 编写 DLL
用于声明导入导出函数
__declspec(dllexport)
__declspec(dllimport)
导出函式__declspec(dllexport)在dll中用
导入函式__declspec(dllimport)在要调用dll的程序中用
动态链接就不需要__declspec(dllimport)
#include "DllForm.h"
USERES("Dll.res");
USEFORM("DllForm.cpp", DllFrm);
class __declspec(dllexport) __stdcall MyDllClass { //导出类
};
TDllFrm* DllMyForm2;
extern "C" __declspec(dllexport) __stdcall void CreateFromFunct();//导出函数
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
}
//---------------------------------------------------------------------------
MyDllClass::MyDllClass()
{
}
void MyDllClass::CreateAForm()
{
}
//---------------------------------------------------------------------------
void __stdcall CreateFromFunct()
{
}
二. 静态调用 DLL
使用 $BCB path\Bin\implib.exe 生成 Lib 文件,加入到工程文件中
将该文件拷贝到当前目录,使用 implib MyDll.lib MyDll.dll 生成
// Unit1.h // TForm1 定义
#include "DllForm.h" // TDllFrm 定义
//---------------------------------------------------------------------------
__declspec(dllimport) class __stdcall MyDllClass {
};
extern "C" __declspec(dllimport) __stdcall void CreateFromFunct();
class TForm1 : public TForm{...}
// Unit1.cpp // TForm1 实现
void __fastcall TForm1::Button1Click(TObject *Sender)
{ // 导出类实现,导出类只能使用静态方式调用
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{ // 导出函数实现
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
}
三. 动态调用 DLL
// Unit1.h
class TForm1 : public TForm
{
...
private: // User declarations
void (__stdcall *CreateFromFunct)();
...
}
// Unit1.cpp // TForm1
HINSTANCE DLLInst = NULL;
void __fastcall TForm1::Button2Click(TObject *Sender)
{
}
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
}
四. DLL 作为 MDIChild (子窗体) 【只编写动态调用的例子】
要把调用程序的 Application 的 Handle 传递给 DLL 的 Application 即可;同时退出 DLL 时也要恢复
Application
// MDIChildPro.cpp // Dll 实现 CPP
#include "unit1.h" // TForm1 定义
TApplication *SaveApp = NULL;
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
}
extern "C" __declspec(dllexport) __stdcall void TestMDIChild(
{
}
注:上面的程序使用 BCB 3.0 编译成功
五. BCB 调用 VC 编写的 DLL
VC DLL 的代码如下:
extern "C" __declspec(dllexport) LPSTR __stdcall BCBLoadVCWin32Stdcall()
{
static char strRetStdcall[256] = "BCB Load VC_Win32 Dll by __stdcall mode is OK!";
return strRetStdcall;
}
extern "C" __declspec(dllexport) LPSTR __cdecl BCBLoadVCWin32Cdecl()
{
static char strRetCdecl[256] = "BCB Load VC_Win32 Dll by __cdecl mode is OK!";
return strRetCdecl;
}
extern "C" __declspec(dllexport) LPSTR __fastcall BCBLoadVCWin32Fastcall()
{
static char strRetFastcall[256] = "BCB Load VC_Win32 Dll by __fastcall mode is OK!";
return strRetFastcall;
}
void __fastcall TForm1::btnBLVCWin32DynClick(TObject *Sender)
{
}
Linker 提示不能找到函数的实现
LIBRARY
IMPORTS
对应的函数声明和实现如下:
extern "C" __declspec(dllimport) LPSTR __fastcall BCBLoadVCWin32Fastcall();
extern "C" __declspec(dllimport) LPSTR __cdecl BCBLoadVCWin32Cdecl();
extern "C" __declspec(dllimport) LPSTR __stdcall BCBLoadVCWin32Stdcall();
void __fastcall TfrmStatic::btnLoadDllClick(TObject *Sender)
{
}
注意:在 BCB 5.0 中,可能直接按下 F9 是不能通过 Linker 的,请先 Build 一次
注:上面的程序使用 BCB 5.0 与 VC6.0 编译成功
这个问题需要比较深入了解,要想成功释放非托管代码分配的内存,必须先确定非托管代码的内存分配方式,才能在互操作是选择正确的方法释放非托管内存,
在非托管代码中,有3种分配方式:
1、C语言:malloc 、free
2、C++:new、delete
3、COM:CoTaskMenAlloc、CoTaskMenFree
第三种方式是互操作默认的释放非托管内存的方法!也就是说,采用前两种方式分配的非托管内存,托管代码不能正确释放,必须由非托管方自己明确释放:
C++:
wchar_t* GetStringNew()
{
int iBufferSize = 128;
wchar_t* pBuffer = new wchar_t[iBufferSize ];
if(NULL != pBuffer)
{
wcscpy_s(pBuffer, iBufferSize/sizeof(wchar_t), L"String from New");
}
return pBuffer;
}
void FreeNewMemory(void* pBuffer)
{
printf("\n%d", pBuffer);
if(NULL != pBuffer)
{
delete pBuffer;
pBuffer = NULL;
}
}
C#:
[DllImport("NativeLib.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode )]
static extern string GetStringNew();
[DllImport("NativeLib.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode )]
static extern void FreeNewMemory(IntPtr pbuffer);
IntPtr strPtr=GetStringNew();
string str=Marshal.PtrToStringUni(strPtr);
FreeNewMemory(strPtr); //显示调用非托管释放内存函数释放内存,否则内存会泄露
如何将C#数组(如bytes[])传给具有IntPtr的托管代码函数?---非unsafe
问题:
我在C#里的数据存放在byte[]中,需要传到某一函数中去,如下:
byte[] byData = new byte[1024];
......对byData进行赋值
// 完毕后,将该数组中的数据传给该函数,该函数声明如下
MyFunction(int iNo, IntPtr pData, uint nDataSizeinBytes);
// 该函数原形声明中的pData就是用来存贮数据的
解决方法:
1:先申请一段非托管的内存空间,使用的方法是:
-
C# co
-
IntPtr System.Runtime.InteropServices.Marshal.AllocHGlobal( int size); IntPtr hglobal = System.Runtime.InteropServices.Marshal.AllocHGlobal( 100 );
该函数传入一个你要申请的空间大小,返回申请到的非托管内存指针hglobal。
2:把你的数据从byte[]复制到这段内存空间中。
复制字节流所使用的方法是:
-
C# co
-
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static void System.Runtime.InteropServices.Marshal.Copy( byte [] source, int startIndex, IntPtr destination, int length ) // 示例 System.Runtime.InteropServices.Marshal.Copy(byData, 0 ,hglobal,byData.Length);
3:把步骤1取得的指针传入你的非托管函数中。
-
C# co
-
MyFunction(iNo, hglobal,byData.Length);
注意:
非托管内存需要自行释放。你可以选择在托管程序(C#)中释放,方法是:
Marshal.FreeHGlobal(hglobal);
如果你在C#中释放需要采用一种机制来由你的函数通知C#程序内存已经使用完成,不然将会造成不小的风险。
当然你也可以在非托管的程序中释放,这就要看你的程序是否已经实现了释放或者可以修改。
MSDN的一个页面地址,中文的。
http://msdn.microsoft.com/zh-cn/magazine/cc164193.aspx
获取指定数组中指定索引处的元素的地址。
命名空间: System.Runtime.InteropServices
程序集: mscorlib(在 mscorlib.dll 中)
C#
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static IntPtr UnsafeAddrOfPinnedArrayElement( Array arr, int index )
有几种方法可以访问与字节数组相对应的 IntPtr。
第一种是使用不安全的代码块来访问直接指向字节数组的指针。
//C#
unsafe
{
byte[] test = new byte[5];
fixed (byte* p = &test[0])
{
*p = 0xff;
}
}
也可以使用 GCHandle 来获得对象。
//C#
using System.Runtime.InteropServices;
byte[] test = new byte[5];
GCHandle hObject = GCHandle.Alloc(test, GCHandleType.Pinned);
IntPtr pObject = hObject.AddrOfPinnedObject();
if(hObject.IsAllocated)
hObject.Free();
最后,可以这样实现:通过 LocalAlloc 创建内存块并将数据封送处理到该内存块。
//C#
[DllImport("coredll.dll",SetLastError=true)]
public static extern IntPtr LocalAlloc(uint uFlags, uint uBytes);
[DllImport("coredll.dll",SetLastError=true)]
public static extern IntPtr LocalFree(IntPtr hMem);
[DllImport("coredll.dll",SetLastError=true)]
public static extern IntPtr LocalReAlloc(IntPtr hMem, uint uBytes, uint fuFlags);
public const uint LMEM_FIXED = 0;
public const uint LMEM_MOVEABLE = 2;
public const uint LMEM_ZEROINIT = 0x0040;
byte[] test = new byte[5];
IntPtr p = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, (uint)test.Length);
if (p == IntPtr.Zero)
{
throw new OutOfMemoryException();
}
else
{
Marshal.Copy(test, 0, p, test.Length);
}