澳门新葡亰网址下载将C++ DLL Wrap后供.NET 调用

by admin on 2020年1月28日

__declspec(dllexport卡塔尔(قطر‎是报告编写翻译器用来导出函数的,在代码中不另作表明了

extern “C” __declspec(dllexport) int __stdcall testfunc(char*
astr,int* a);
 

What you would do is providing call stubs from your DLL that then are
accessible via PInvoke, e.g.

extern “C”的情趣就是用C的方法来导出函数,为啥要用C的法子来导出呢.
因为C++中有重载,编写翻译器会对函数名张开改动,修饰成唯意气风发的函数名.
__stdcall告诉编写翻译器函数调用格局.这一点能够参照别的小说,
本身预测也会在blog中写上后生可畏篇有关函数调用情势.

extern ”C”

//wrapper.cpp
#include “manufacturer.h”
#pragma comment(lib,”manufacturer.lib”)

C++编写的DLL
extern “C” __declspec(dllexport) int  Max(int x,int y)
…{
    return x>y?x:y;
}
__declspec(dllexport) int __stdcall Min(int x,int y)
…{
    return x<y?x:y;
}
__declspec(dllexport) double Min(double x,double y)
…{
    return x<y?x:y;
}
那是生龙活虎段代码,使用参数和重返值为int和double是有目的的
在VC8下int是32位的double是64位.
使用重载也可以有目标的.
编译命令如下
cl /c dlltest.cpp
link /DLL dlltest.obj
编写翻译后选用Depends查看dll中的内容.能见到dll中有3个函数.
?Min@@YANNN@Z
?Min@@YGHHH@Z
Max
中间的?Min@@YANNN@Z和?Min@@YGHHH@Z正是重载五个Min函数.
能够行使运营库中未公开函数__unDNameEx见到相呼应的函数评释.
那八个名字便是C++函数和二进制文件中的函数名相对应的.
重载的时候依照参数差别链接到不一样的函数名字
C要是使用的话得动态加载函数

  平常来讲,C++编写翻译器可能会改换函数和变量的名字,进而以致严重的链接程序难题。比如,要是使用C++编写叁个DLL,当成立DLL时,Microsoft的编写翻译器就能够变动函数的名字。函数大将被设置贰个指引下划线,再增进二个@符号的前缀,后随三个数字,表示作为参数字传送递给函数的字节数。比方,下边包车型客车函数是当作DLL的输出节中的_MyFunc@8输出的:

extern “C” __declspec(dllexport) int WrapperCallManufacturerFunc1(int
a, int b)
{
    return ManufacturerFunc1(a,b);
}

接下去看什么传递指针类型.在以下的部分都使用C可以应用函数
于是将extern “C” __declspec(dllexport卡塔尔(قطر‎定义为二个宏

__declspec(dllexport) LONG __stdcall MyFunc(int a, int b);
  假如用另贰个代理商的工具创立了贰个可试行模块,它将设法链接到四个号称MyFunc的函数,该函数在Microsoft编写翻译器原来就有的DLL中并不真实,由此链接将失利。

extern “C” __declspec(dllexport) char*
WrapperCallManufacturerFunc2(char* pString)
{
    return ManufacturerFunc2(pString);
}

#define DLLEXPORT extern “C” __declspec(dllexport)
DLLEXPORT int swap(int* x,int& y)
…{
    int z = *x;
    *x = y ;
    y = z;
    return 0;
}
/**//*
那和前边的例子重复了,首要用以调用的例证
*/
DLLEXPORT double __stdcall Max_d(double x,double y)
…{
    return x>y?x:y;
}
接下去是运用布局体的,由于组织体会对成员开展对齐,
据此在调用的时候须要在意和这里的布局体有相符的内存布局

  使用extern
“C”关键字可以使编译器遵照C语言的办法编写翻译DLL文件,即编写翻译时不改换函数名。

extern “C” __declspec(dllexport) double
WrapperCallManufacturerFunc3(double d)
{
    return ManufacturerFunc3(d);
}

#include<string.h>
struct testStruct
…{
    char a;
    int b;
    double c;
    char sz[5];
};
DLLEXPORT int __stdcall UseStruct(testStruct* p)
…{
    p->a = ‘a’;
    p->b = 20;
    p->c = 1.234;
    strcpy( p->sz , “abcd” );
    return sizeof(testStruct);
}
/**//*那是修正了内部存款和储蓄器对齐的结构体使用,首要在调用的时候有分别*/
#pragma pack(push)
#pragma pack( 1 )
struct testStruct2
…{
    char a;
    int b;
    double c;
    char sz[5];
};
#pragma pack(pop)
DLLEXPORT int __stdcall UseStruct2(testStruct2* p)
…{
    p->a = ‘a’;
    p->b = 20;
    p->c = 1.234;
    strcpy( p->sz , “abcd” );
    return sizeof(testStruct2);
}
那是采纳回调函数的例子,这里想成功调用主要照旧要看怎样调用.
DLLEXPORT int __stdcall UserCallBackFunc( const char* lp, int (__stdcall *p)(const char*) )
…{
    return p( lp );
}
def文件内容如下
EXPORTS
    Max
    swap
    Max_澳门新葡亰网址下载,d
    UseStruct
    UseStruct2
    UserCallBackFunc
那边的def文件不是必得的,假如不使用def文件编写翻译,则dll中的一些函数名前面会被抬高_前边加上参数大小
?Min@@YANNN@Z ?Min@@YGHHH@Z Max _Max_d@16 _UseStruct2@4 _UseStruct@4 _UserCallBackFunc@8 swap
当然假如只是C使用的话不接收def文件也是能够的,但固然别的语言的话就有豆蔻梢头对不便于了也得使用_Max_d@16
如此那般的函数名字,有些不美观.在这间扩展def文件也正是化名的意思

 

extern “C” __declspec(dllexport) void WrapperCallManufacturerFunc4()
{
    ManufacturerFunc4();
}

编写翻译方法如下
cl /c dlltest.cpp
link /DLL /DEF:dlltest.def dlltest.obj
动用了def文件后dll中所导出的函数如下
?Min@@YANNN@Z ?Min@@YGHHH@Z Max Max_d UseStruct2 UseStruct UserCallBackFunc swap
除开重载的四个函数,凡是使用extern “C”的函数都得以符合规律现实

__declspec(dllexport)

That’s basically it. The ‘extern “C”‘ statement is used to tell the
compiler to not apply C++ name mangling, i.e. the functions names are
not decorated and exported ‘as is’. The return types resemble the return
types of the functions in the .lib you want to call, except for ‘void’
functions where your function is ‘void’ also, yet the ‘return’ statement
is not used. Then you can access the wrapper functions like

C/C++使用方法
分别使用.c和.cpp的扩大名编写翻译,便是C的调用方法和C++的调用方法了
#ifdef __cplusplus
    extern “C” __declspec(dllimport) int  Max(int x,int y);
    int __stdcall Min(int x,int y);
    double Min(double x,double y);
#else
    __declspec(dllimport) int Max(int x,int y);
#endif

  在 32 位编写翻译器版本中,能够选用__declspec(dllexport卡塔尔(قطر‎关键字从DLL导出多少、函数、类或类成员函数。__declspec(dllexport卡塔尔(قطر‎会将导出指令增多到目的文件中,由此不须要使用.def文件。

Declare Auto Function ManufacturerFunc1  Lib “wrapper.dll” Alias
“WrapperCallManufacturerFunc1 ” ( _
    ByVal a As Integer, _
    ByVal b As Integer) _
    As Integer

#ifdef __cplusplus
#    define DLLIMPORT extern “C” __declspec(dllimport)
#else
#    define DLLIMPORT __declspec(dllimport)
#endif

  若要导出函数,__declspec(dllexport卡塔尔(قطر‎关键字必需出以后调用约定关键字的左臂(尽管钦定了要害字)。举例:

or

DLLIMPORT int swap(int* x,int* y);
DLLIMPORT double __stdcall Max_d(double x,double y);

__declspec(dllexport) void __cdecl Function1(void);
 

Imports System.Runtime.InteropServices
Public Class Win32
    Declare Auto Function WrapperCallManufacturerFunc1 Lib “wrapper.dll”
_
       (ByVal a As Integer, _
        ByVal b As Integer) As Integer
End Class

#include<string.h>
typedef struct __testStruct
…{
    char a;
    int b;
    double c;
    char sz[5];
}testStruct;
DLLIMPORT int __stdcall UseStruct(testStruct* p);
#pragma pack(push)
#pragma pack( 1 )
typedef struct __testStruct2
…{
    char a;
    int b;
    double c;
    char sz[5];
} testStruct2;
#pragma pack(pop)
DLLIMPORT int __stdcall UseStruct2(testStruct2* p);
DLLIMPORT int __stdcall UserCallBackFunc( const char* lp, int (__stdcall *p)(const char*) );

__stdcall

See also
(“Walkthrough: Calling Windows APIs”) and

(“Creating Prototypes in Managed Code”)

#include<stdio.h>
#pragma comment(lib,”dlltest.lib”)
#include<windows.h>

  评释被调用方清理仓库。

int __stdcall CallBackFunc(const char*lp)
…{
    return printf(“%s “,lp);
}
int main()
…{
    int x=2,y=3;
    testStruct s1;
    testStruct2 s2;
#ifdef __cplusplus
    printf(“%d “,Min( 2,3 ) );
    printf(“%f “,Min( 2.0,3.0 ) );
#else
    int(__stdcall *pMin)(int,int)=0;
    double(*pMin_d)(double,double)=0;
    HMODULE hDll = GetModuleHandle(“dlltest.dll”);
    pMin_d = (double(*)(double,double)) GetProcAddress( hDll , “?Min@@YANNN@Z” );
    if( pMin_d )
        printf(“%f “,pMin_d(3.0,5.0 ) );
    pMin = (int(__stdcall*)(int,int)) GetProcAddress( hDll , “?Min@@YGHHH@Z” );
    if( pMin )
        printf(“%d “,pMin( 3 , 5 ) );
#endif
    swap( &x,&y );
    printf(“swap = %d,%d “,x,y);
    printf( “%d ” , Max( 2,4 ) );
    printf( “%f ” , Max_d( 2.0,4.0 ) );
    UseStruct(&s1);
    UseStruct2( &s2 );
    printf( “%c,%d,%f,%s “,s1.a,s1.b,s1.c,s1.sz);
    printf( “%c,%d,%f,%s “,s2.a,s2.b,s2.c,s2.sz);
    UserCallBackFunc(“abcdef”,CallBackFunc);

 

    return 0;
};

C#中的函数表明
using System.Runtime.InteropServices;
      …

Delphi的选用方法.
program test;
…{$APPtype CONSOLE}
uses
  SysUtils,Classes,Math,Windows;

public class Program
{
[DllImport(@”E:Projectstestdlldebugtestdll.dll”)]
public static extern int testfunc(StringBuilder abuf,ref int a);
}

type
  testStruct = record
    a:Char;
    b:Integer;
    c:Double;
    sz:array[0..4] of char;
  end;
…{$A1}…{定义record使之和 改过了对齐的结构体有同黄金时代的内部存储器构造 }
  testStruct2 = record
    a:Char;
    b:Integer;
    c:Double;
    sz:array[0..4] of char;
  end;
…{$A8}
  CallBackFunc type = function(x:PChar):Integer;stdcall;
  function Max( x:Integer;y:Integer ):Integer;cdecl;external ‘dlltest.dll’ name ‘Max’;
  function Max_d( x:Double;y:Double):Double    ;stdcall;external ‘dlltest.dll’ name ‘Max_d’;
  function swap(var x:Integer;var y:Integer):Integer;stdcall;external ‘dlltest.dll’ name ‘swap’ ;
  function UseStruct(var x:testStruct):Integer;stdcall;external ‘dlltest.dll’ name ‘UseStruct’ ;
  function UseStruct2(var x:testStruct2):Integer;stdcall;external ‘dlltest.dll’ name ‘UseStruct2’ ;
  function UserCallBackFunc( lp:PChar ; F:CallBackFunctype ):Integer;stdcall;external ‘dlltest.dll’ name ‘UserCallBackFunc’ ;
  function CallFunc(lp:PChar):Integer;stdcall;
  begin
    writeln( ‘CallFunc=’ , lp );
    result := 0 ;   
  end;
…{
    这里是运用重载函数
}
  function Min(x:Integer;y:Integer):Integer;stdcall;external ‘dlltest.dll’ name ‘?Min@@YGHHH@Z’;
   
  function Min_d(x:Double;y:Double):Double;cdecl;external ‘dlltest.dll’ name ‘?Min@@YANNN@Z’;

using System.Runtime.InteropServices;

  procedure test_overland;
  begin
    writeln( ‘Min(1,2)=’ , Min( 1,2 ) );
    writeln( ‘Min_d(1.0,2.1)=’ , Min_d( 1.0,2.1 ) );
  end;
 
var
  x,y:Integer;
  a_struct:testStruct;
  b_struct:testStruct2;
begin
  writeln( ‘Max(1,2)=’ , Max( 1,2 ) );
  writeln( ‘Max(1.0,2.0)=’ , Max_d( 1.0,2.0 ) );
  writeln( ‘x=1,y=2’ );
  x :=1;y :=2 ;
  swap( x, y);
  writeln( ‘swap(x,y)=’ , x , ‘ ‘, y );
 
  writeln( ‘UseStruct( a_struct ) result=’ , UseStruct( a_struct ) , ‘,sizeof=’ , sizeof(a_struct) );
  writeln( ‘UseStruct=’ , a_struct.a, ‘ ‘ , a_struct.b, ‘ ‘  , a_struct.c , ‘ ‘ ,a_struct.sz );
 
  writeln( ‘UseStruct2( b_struct ) result=’ , UseStruct2( b_struct ) , ‘,sizeof=’ , sizeof(b_struct) );
  writeln( ‘UseStruct2=’ , b_struct.a, ‘ ‘ , b_struct.b, ‘ ‘  , b_struct.c , ‘ ‘ ,b_struct.sz );
 
  UserCallBackFunc( PChar(‘abcdef’) , CallFunc );
 
  test_overland;
 
  readln; 
end.
VB6的使用方法
出于VB6只好利用__stdcall格局的函数,所以只有部分函数能被VB6所调用,
Public Declare Sub CopyMemory()Sub CopyMemory Lib “kernel32” Alias “RtlMoveMemory” (Destination As Any, Source As Any, ByVal Length As Long)
Public Declare Function lstrlen()Function lstrlen Lib “kernel32” Alias “lstrlenA” (ByVal lpString As Long) As Long

  System.Runtime.Interop瑟维斯s 命名空间提供丰富多彩援救 COM interop
及阳台调用服务的积极分子,使程序能够与非托管代码举办相互操作。

Public Type testStruct
        a As Byte
        b As Long
        c As Double
        sz As String * 5
End Type
Public Type Temp
    sz As String * 5
End Type
Public Declare Function Max_d()Function Max_d Lib “dlltest” (ByVal a As Double, ByVal b As Double) As Double
Public Declare Function Min()Function Min Lib “dlltest” Alias “?Min@@YGHHH@Z” (ByVal a As Long, ByVal b As Long) As Long
Public Declare Function UseStruct()Function UseStruct Lib “dlltest” (ByRef a As testStruct) As Long
Public Declare Function UseStruct2()Function UseStruct2 Lib “dlltest” (ByRef a As Any) As Long
Public Declare Function UserCallBackFunc()Function UserCallBackFunc Lib “dlltest” (ByVal s As String, ByVal f As Long) As Long

 

Function CallBack()Function CallBack(ByVal lp As Long) As Long
    
    Dim L As Long
    L = lstrlen(lp)
    Dim s As String
    s = String$(L + 1, vbNullChar)
    CopyMemory s, lp, L
    MsgBox s & ” , ” & Str$(L)
    Debug.Print “CallBack”, s
    CallBack = 3
End Function

[DllImport(“dllfile path”)]

Sub Main()Sub Main()

  代码中DllImport关键字功能是报告编写翻译器入口点在哪个地方,并将打包函数捆绑在这里个类中。在注明的时候还足以增加多少个个性:

    Debug.Print Max_d(4, 5), Min(4, 6)

[DllImport(“MyDLL.dll”,
EntryPoint=”mySum”,
CharSet=CharSet.Auto,
CallingConvention=CallingConvention.StdCall)]
  EntryPoint: 钦赐要调用的 DLL 入口点。暗许入口点名称是托管方法的名称

  CharSet: 调节名称重新整建和封送 String 参数的不二秘诀 (暗许是UNICODE卡塔尔国
  CallingConvention提醒入口点的函数调用约定(暗中认可WINAPI卡塔尔

    Dim a As testStruct
    Debug.Print UseStruct(a)
    Debug.Print Chr(a.a), a.b, a.c, a.sz
    
    Dim buf(18) As Byte
    Debug.Print “—————-“
    Debug.Print UseStruct2(buf(0))
    
    Dim t As Byte
    CopyMemory t, buf(0), 1
    
    Dim L As Long
    CopyMemory L, buf(1), 4
    
    Dim d As Double
    CopyMemory d, buf(5), 8
    
    Dim s As Temp
    CopyMemory s, buf(13), 5
    Debug.Print Chr(t), L, d, s.sz

  注意:必得在标记为”static”和”extern”的章程上点名”DllImport”属性。

    Debug.Print UserCallBackFunc(“_测试asdasd中文sdfasdf”, AddressOf CallBack)
End Sub

 

VB版本需求当心的是lstrlen 的宣示 参数不是String而是Long类型,这是因为只就算String的话VB会对参数举行改建,将字符串指针转化为String类型,而自身这里无需更改,就需求二个土生土养的Long类型的指针.所以就改成了API的函数注解.以适应自个儿的须求

数量传递格局
1.着力数据类型的传递
  函数参数和重返值能够是C#和C++的各样基本数据类型,如int, float,
double, char(注意不是char*)等。
  示例:
  C#代码:

Delphi编写DLL

using System;
using System.Text;
using System.Runtime.InteropServices;

library dlltest;

class Program
{
    [DllImport(@”E:Projectstestdlldebugtestdll.dll”)]
    public static extern int testfunc(int a,float b,double c,char d);

uses
  SysUtils,
  Classes;

    static void Main(string[] args)
    {
        int a = 1;
        float b = 12;
        double c = 12.34;
        char d = ‘A‘;
        testfunc(a,b,c,d);
        Console.ReadKey();
    }
}
  C++代码:

function Max(X, Y:  Integer):  Integer; cdecl;
begin
  if X > Y  then Max := X else Max := Y;
end;

#include <iostream>
using namespace std;

function Min(X, Y:  Integer):  Integer;overload; stdcall;
begin
  if X < Y  then Min := X else Min := Y;
end;

extern “C”
{
 _declspec(dllexport) int __stdcall testfunc(int a,float b,double
c,char d)
 {
  cout<<a<<“, “<<b<<“, “<<c<<“,
“<<d<<endl;
  return 0;
 }
}

function Min(X, Y: Double): Double;overload;  cdecl;
begin
  if X < Y  then Min := X else Min := Y;
end;

2.向DLL传入字符串
  C#中利用string定义字符串,将字符串对象名传给DLL。
  注意:在DLL中纠正字符串的值,C#中的值也会校勘。
  短处:无法校订字符串的长度,提议使用第3种方法。
  C#代码:

function swap( var x: Integer; var y: Integer): Integer; cdecl;
var
  z:integer;
begin
  z := x;
  x := y;
  y := z;
  result := 1;
end;

using System;
using System.Text;
using System.Runtime.InteropServices;

function Max_d(X, Y:Double):Double;stdcall;
begin
  if X > Y  then Max_d := X else Max_d := Y;
end;

class Program
{
    [DllImport(@”E:Projectstestdlldebugtestdll.dll”)]
    public static extern int testfunc(string a);

type
  testStruct =  record
    a:Char;
    b: Integer;
    c:Double;
    sz: array[0..4]  of char;
   end;
function UseStruct( var x:testStruct): Integer;stdcall;
begin
  x.a := ‘a’ ;
  x.b := 20 ;
  x.c := 1.234 ;
  StrCopy( x.sz , PChar(‘abcd’) );
  UseStruct:=SizeOf(testStruct) ;
end;

    static void Main(string[] args)
    {
        string a=”Hello World!”;
        testfunc(a);
        Console.ReadKey();
    }
}  C++代码:

{$A1}
type
  testStruct2 =  record
    a:Char;
    b: Integer;
    c:Double;
    sz: array[0..4]  of char;
   end;
{$A8}
function UseStruct2( var x:testStruct2): Integer;stdcall;
begin
  x.a := ‘a’ ;
  x.b := 20 ;
  x.c := 1.234 ;
  StrCopy( x.sz , PChar(‘abcd’) );
  UseStruct2 := SizeOf(testStruct2) ;
end;

#include <iostream>
using namespace std;

type
  CallBackFuncType =  function(x:PChar): Integer;stdcall;
function UserCallBackFunc( lp:PChar ; F:CallBackFuncType ): Integer;stdcall;
begin
  F( lp );
  UserCallBackFunc:= StrLen( lp ) ;
end;

extern “C”
{
 _declspec(dllexport) int __stdcall testfunc(char* astr)
 {
  cout<<astr<<endl;
  *astr=‘A‘;//校正字符串的数目
  cout<<astr<<endl;
  return 0;
 }
}

exports
  Max,
  Min(X, Y:  Integer卡塔尔国 name ‘?Min@@YGHHH@Z’,//和C++导出的重载函数名字相近
  Min(X, Y: Double)name ‘?Min@@YANNN@Z’,
  swap,
  Max_d,
  UseStruct,
  UseStruct2,
  UserCallBackFunc;

3.DLL传播字符串
  C#中应用StringBuilder对象创造变长数组,并安装StringBuilder的Capacity为数组最大尺寸。将此目的名传递给DLL,使用char*接收。
  C#代码:

begin
end.Delphi导出的时候能够随便设置输出的名字,那样就足以写出来和C++写出来的DLL有相仿的导出表.
也正是说能够和C++写的dll互相替换着调用.
关于VB编写DLL
由于VB6不易于编写相仿成效的DLL的,重倘使法定未扶植.虽说有人发表相应的SDK.
但归根结蒂未有合法协理,应用也很难推广开来,所以在这里边说雅培(Abbott卡塔尔(NutrilonState of Qatar下,也就不提供VB6编写制定那样的DLL的.
猜度作者会写风流倜傥篇使用COM的方法落成多语言编制程序,到时候就足以见见VB6版的DLL.

using System;
using System.Text;
using System.Runtime.InteropServices;

总结一下
说来DLL要想能够在两种语言中都能够行使,那么就须要使用__stdcall格局调用(VB只帮衬那中艺术State of Qatar,
以至都有个别数据类型.那样就约束了,只好接纳整形(1字节char,2字节short ,4字节long,8字节的long long卡塔尔国,浮点型(4字节的float,8字节的doubleState of Qatar,以至指针类型(包含函数指针卡塔尔(قطر‎.正确的说只有CPU所支撑的连串,才轻巧形成无障碍的通用.

class Program
{
    [DllImport(@”E:Projectstestdlldebugtestdll.dll”)]
    public static extern int testfunc(StringBuilder abuf);

小技巧
鉴于C/C++程序在应用静态载入DLL的章程的时候,要求lib文件参与链接,此时能够自个儿成立贰个DLL
工程,然后导出原本DLL中的函数,函数达成为空就足以了,然后用那个lib参预链接,在调用你所要使用的DLL是足以的.

    static void Main(string[] args)
    {
        StringBuilder abuf=new StringBuilder();
        abuf.Capacity = 100;//设置字符串最大尺寸
        testfunc(abuf);
        Console.ReadKey();
    }
   
}  C++代码:

#include <iostream>
using namespace std;

extern “C”
{
 _declspec(dllexport) int __stdcall testfunc(char* astr)
 {
  *astr++=‘a‘;
  *astr++=‘b‘;//C#中abuf随astr改变
  *astr=‘‘;

  return 0;
 }
}

4.DLL传递布局体(须求在C#中重复定义,不引入应用)
  C#中使用StructLayout重新定义要求利用的构造体。
  注意:在DLL改造构造体成员的值,C#中随之改换。
  C#代码:

using System;
using System.Text;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public double x;
    public double y;
}

class Program
{
    [DllImport(@”E:Projectstestdlldebugtestdll.dll”)]
    public static extern int testfunc(Point p);

    static void Main(string[] args)
    {
        Point p;
        p.x = 12.34;
        p.y = 43.21;
        testfunc(p);
        Console.ReadKey();
    }   
}

C++代码:

#include <iostream>
using namespace std;

struct Point
{
    double x;
    double y;
};

extern “C”
{
 _declspec(dllexport) int __stdcall testfunc(Point p)
 {
  cout<<p.x<<“, “<<p.y<<endl;
  return 0;
 }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图