扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
根据我的猜测,有两种情况
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:域名注册、网页空间、营销软件、网站建设、英山网站维护、网站推广。
1.如果 void GetNum(char* s);是把输入的字符储存在 *s中的话:
#include WINDOWS.H
#include WINBASE.H
#include stdio.h
typedef void (*MYPROC)(char*); /* 这里要与GetNum的返回值和参数 对应 */
void main(){
HINSTANCE LibHandle;
MYPROC ProcAdd;
char ch;
char sysbuf[] = "GetNum"; /*过程名*/
LibHandle = LoadLibrary("GetNum.dll"); /*载入dll*/
ProcAdd = (MYPROC)GetProcAddress(
LibHandle, sysbuf); /*获取函数sysbuf的地址*/
ProcAdd(ch);
printf("%c\n",ch);
}
2.如果你的GetNum是
int GetNum(char* s)
{
int i=0,ans=0;
while(s[i]='0's[i]='9')
{
ans=s[i]-'0'+ans*10;
}
return ans;
}
#include WINDOWS.H
#include WINBASE.H
#include stdio.h
typedef int (*MYPROC)(char*); /* 改 */
void main(){
HINSTANCE LibHandle;
MYPROC ProcAdd;
char s[100]; /* 改 */
char sysbuf[] = "GetNum"; /*过程名*/
LibHandle = LoadLibrary("GetNum.dll"); /*载入dll*/
ProcAdd = (MYPROC)GetProcAddress(
LibHandle, sysbuf); /*获取函数sysbuf的地址*/
scanf("%s",s); /* 改 */
printf("%d\n", ProcAdd(s) ); /* 这里调用 并输出结果 */
}
(1)编写程序时,你要包含(#include "什么.h") dll文件作者提供 的 头文件(.h文件) 。
程序里,便和普通函数调用一样,去调用它的函数。
(2)程序编译时,你要链接 dll文件作者提供 的 (.lib文件) 库文件。
当然,你可以在源程序里把.lib 名字 写上,编译时自动去链接,例子:
#pragma comment (lib, "什么.lib")
(3)执行时,要有 .dll 文件. 放在当前文件夹或系统文件夹里。
其实在.net开发中确实存在第三种类型——指针。但仅限于unsafe开发时使用!
对于dll调用问题,如果是pe格式的dll考虑使用PInvoke(平台调用)。而平台调用跟具体的unsafe开发是没有任何关联的,并不是说平台调用一定会用到非安全开发!由于.net本身隐藏了指针的使用,你不必要再去开辟任何指针了!所以这里显然是平台调用。
解答一个疑问:数组指针or指针数组?
数组指针指的一个指针,该指针指向一个数组。
指针数组是一个数组,一个数组里全是指针。
显然这使用是有区别,但这里并不涉及,在safe开发.net都不会涉及!
IntPtr是一种特殊的指针,这种针指的特殊性在于它向了设备!所以嘛,他也有一个名词——名柄,IntPtr多种在PInvoke时,传递设备句柄时使用(设备并不是硬件,有时一个文件也算是一个句柄的)。当然,这里也用不到!
现在说一下平台调用(PInvoke)中最重要的一个部分!数据封送!
也就是你写dll导入时应用使用的类型!
在C语言中声明与赋值分开写的时候多了去了!他生成文件时写一起与分开写并没有任何区别!所以分开写无所谓的!但是,多数时间你会发现,其实他分开写是为了你阅读的方便!为什么?因为声明时使用的类型是PUSHORT,而赋值时使用了USHORT的类型,这两句写在一块的话,很多人不会注意到赋值时使用的基础类型!所以分开写只是相当于想写一个注释而已!
平台对应的数据封送就在这里!USHORT在C/CPP中对就的一个byte!一定要注意,所以你知道的,他们使用的new USHORT[n]只是开辟了n个无符号数的字节!在.net平台封送时如何传递数据?当然用byte去接值,他是数组,你就用byte[]去接!
这里有一个问题你可能不知道,在.net中,最小的数是byte,而不是short和ushort!在.net中可以直接使用byte与其他数值进行运算!而short/ushort在.net中却是一个16bit数!占用两个字节!而C/Cpp为了确定类型,所以byte是byte,short/ushort是数值,但是short/ushort却是一个8bit数字!当然了,C/Cpp中的long才与.net中的int对应!
如果你明白这个,那么该用int[]还是该用short[]还是该用byte[]你自己就知道了!
第二个问题——你一直在想指针,其实你要传入的数组是地址而不是副本!换句话来说,在安全模式中我们是有ref/out修饰的!在dllImport时你传入byte[]是没有任何错误的!但是你能不能在从dll中将byte[]修改后的值带回就是两个字了!
一般情况下byte[]等数组本身就是引址的方式,所以不使用ref/out声明也是没有问题的!但是如果封装层在有一定的疑问存在时,最好还是声明成ref的方式较为合适。经常性的规律是,如果我们看到Cpp的header中说明是指针时,我们会使用ref声明,不是指针形式时可以不用ref/out声明,如果是**形式时,平台封送最好使用数组指针。这种情况并不多见!
但是,由于string类型也是引用形的,由于其特点,返回值一定要用ref/out(不管你看到Cpp的header中是什么类型),或者我们可以使用一个说明长度的byte[]也是可行的!
换句话来说,虽然我们可以说C#中的byte可以接short, short可以接int等等的对应关系!但其实在不改变内存字节长度的情况下,PInvoke对类型的要求并没有那么严格!比如你可以用string接收dll中传入的字符串,用byte[]也行的!用stringbuilder还行的!并没有严格上的限制!但是这里边一定要注意的是长度!换句话来说,对方使用的是USHORT[n],你用byte[n]能接回来。
是不是一定要有这个对应关系?不一定!
这种情况往往出来在自定类中。比如Cpp header中说明某个参数一个结构!这个结构是由一个short,一个字符串组成的!用C#时可以使用byte实现结构中对应的short,也可以用short实现对应的short,但在声明必须使用特定的长度说明,说明其只有一位!当然用,无论你用int还是long都可以,在结构中必须要说明长度只有一位!那个字符串也是一样!所以你完全可以看到,数据类型并不重要,平台封送中不用管具体的类型,要的只是长度!长度必须对应!
换句话来说,你用char[]来处理Cpp中的ushort也是可以的!
平台数据封送中重要一部分就是共长度(类型?开什么玩笑,Cpp不会认识.net中的类型的,当然.net也不会认识Cpp中的类型)。只有长度一致才能接到值!
cpp中肯定是哪种类型方便用哪种了,当然.net中也是哪种方便用哪种了!那么平台数据是如何进行传递的呢?答案就在于基本类型和结构!
基本类型不说了,当然是ushort只是一个byte,你用int无用谓!反正在.net中byte类型会自然转换成int。如果是ushort[],那么你必须是byte[]或char[]!你用int[]绝对会出现错误的!为了帮助理解,我这里说的是重点是结构:
假定一个结构有n个项(你可以理解为字段)组成。那么在数据封送时就是这n个项每一段的组合。经如有字段是USHORT类型,一个字段是Byte[10]类型,还有一个是Int类型,在平台封送时,我们要封装一段内存,长度为13,第一个长度为1表示第一字段,接下来10个表示第二字段,接下来2个表示 第三字段。
当然我们在.net方面也要构造一个对应的结构!第一个字段你可以声明成byte/short/int/long都可以(必须说明这个在内存中只有一个字节),如果你用了short却不说明,很显然你.net会接到前两字节,破坏了要接收的结构!你自己肯定拿不到数据的!
原理就这么多!慢慢看懂了就知道平台封送是怎么回事了!至于你的问题,抄个近路告诉你结果:dllImport声明时使用
static bool Read(short h, byte[] buffer, short n);
或者
static bool Read(short h, ref byte[] buffer, short n);
当然,也可以使用int,我并不反对
static bool Read([MarshalAs(UnManagerType.USHORT)] int h, byte[] buffer, short n);
换句话来说,你可以使用MarshalAsAttribute来指针其非托类型,结构时还可以直接使用长度说明。主要是你学会MarshalAsAttribute的使用,就知道参数如何传递了!
一.动态链接库(dll)结构
——dll中定义有两种函数:导出函数(export
function)和内部函数
(internal
function),导出函数可以被其他模块调用,内部函数只能在dll内部使用。我们在用c++定制dll文件时,需要编写的就是包含导出函数表的模块定义文件(.def)和实现导出函数功能的c++文件。下面以sample.dll为例介绍def文件和实现文件的结构:
——1.模块定义文件(.def)是由一个或者多个用于描述dll属性的模块语
句组成的文本文件,每个.def文件至少必须包含以下模块定义语句:
第一个语句必须是library语句,指出dll的名字。
exports语句列出被导出函数的名字。
可以使用description语句描述dll的用途(此句可选)。
";"对一行进行注释(可选)
——2.实现文件(.cpp文件为例)
——实现入口表函数的.cpp文件中,包含dll入口点处理的api函数和导出
函数的代码。
//dll
#include windows.h
extern "C" //保持C语言文件
void _declspec ( dllexport ) tryProc() //定义函数
{
MessageBox(NULL,_T("a"),_T("a"),MB_OK);
}
-------------------------------------
//C
#include windows.h
int main()
{
HMOUDLE dll = LoadLibrary(/*DLL文件名*/);
if(dll != NULL)
{
FARPROC try = GetProcAddress(dll,"tryProc");
if(try != NULL)
{
tryProc();//假如函数有返回值,可以用try()得到返回值
/*_asm call tryProc*/
}
}
return 0;
}
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流