Inno Setup:添加GUI连接到SQL
在这里完成Inno Setup和Pascal新手。 我想在setup.exe
打包一个.sql
文件。
该设置将可供下载,因此我无法在安装项目中嵌入任何连接字符串。 该设置基本上通过运行一个简单的.sql
脚本将.NET CLR程序集(dll作为十六进制流)安装到提供的SQL Server数据库中
我在这里看到了关于连接到SQL服务器的其他文章,但没有解决硬编码连接字符串或复制/实现通用数据连接对话框的问题,该对话框在所有MS应用程序中弹出以建立数据库连接
理想情况下,连接字符串的创建应该通过数据连接对话框来处理,即MS已经发布了http://archive.msdn.microsoft.com/Connection的代码,但不知道如何链接它以在安装过程中弹出。
如果没有(太多)努力就不可能实现,那么另一种选择是在Inno安装程序中使用服务器名称路径文本框自定义减少对话屏幕版本。
用于指定是否使用Windows或SQL Server身份验证的复选框(选择SQL身份验证时启用了用户名/密码文本框)
此时尝试连接,并显示可用数据库的下拉列表。
我可以得到服务器实例文本框工作,但不知道如何实现Windows身份验证 SQL身份验证组合框和后续操作
提示?
编辑:谢谢TLama,MS提供的连接对话框用户界面似乎是一个不行。 我已经使用Inno Setup窗体向导获得了'外观'方面的权利,但是其中一些功能仍然让我感到困惑:
我不知道如何
当chkSQLAuth.selected
为true / false时,启用/禁用lblUser
, lblPassword
, txtUsername
, txtPassword
。
一旦txtServer
文本框中有内容,就启用lstDatabase
组合框和标签。
使用指定的凭据填充lstDatabase
组合框(连接到服务器,并在单击lstDatabase
执行"SELECT name FROM master.dbo.sysdatabases WHERE HAS_DBACCESS(name) = 1 ORDER BY name"
)。
然后在选择数据库时启用下一步按钮。
我认为一旦完成,我应该能够弄清楚如何针对选定的数据库执行我的SQL脚本!
[Setup]
AppName=test
AppVersion=1.0
LicenseFile=C:Program Files (x86)Inno Script StudioLicense.rtf
CreateAppDir=False
UsePreviousGroup=False
DisableProgramGroupPage=yes
Uninstallable=no
[Files]
Source: "C:Install Assembly.sql"; DestDir: "{tmp}"; Flags: dontcopy
[CustomMessages]
CustomForm_Caption=Connect to Database Server
CustomForm_Description=Enter the information required to connect to the database server
CustomForm_lblServer_Caption0=Server name:
CustomForm_lblAuthType_Caption0=Log on credentials
CustomForm_lblUser_Caption0=User name:
CustomForm_lblPassword_Caption0=Password:
CustomForm_lblDatabase_Caption0=Database:
CustomForm_chkSQLAuth_Caption0=Use SQL Server Authentication
CustomForm_chkWindowsAuth_Caption0=Use Windows Authentication
[Code]
var
lblServer: TLabel;
lblAuthType: TLabel;
lblUser: TLabel;
lblPassword: TLabel;
lblDatabase: TLabel;
chkSQLAuth: TRadioButton;
txtServer: TEdit;
chkWindowsAuth: TRadioButton;
txtUsername: TEdit;
txtPassword: TPasswordEdit;
lstDatabase: TComboBox;
var
Page: TWizardPage;
{ CustomForm_Activate }
procedure CustomForm_Activate(Page: TWizardPage);
begin
// enter code here...
end;
{ CustomForm_ShouldSkipPage }
function CustomForm_ShouldSkipPage(Page: TWizardPage): Boolean;
begin
Result := False;
end;
{ CustomForm_BackButtonClick }
function CustomForm_BackButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
{ CustomForm_NextkButtonClick }
function CustomForm_NextButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
{ CustomForm_CancelButtonClick }
procedure CustomForm_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean);
begin
// enter code here...
end;
{ CustomForm_CreatePage }
function CustomForm_CreatePage(PreviousPageId: Integer): Integer;
begin
Page := CreateCustomPage(
PreviousPageId,
ExpandConstant('{cm:CustomForm_Caption}'),
ExpandConstant('{cm:CustomForm_Description}')
);
{ lblServer }
lblServer := TLabel.Create(Page);
with lblServer do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblServer_Caption0}');
Left := ScaleX(24);
Top := ScaleY(8);
Width := ScaleX(68);
Height := ScaleY(13);
end;
{ txtServer }
txtServer := TEdit.Create(Page);
with txtServer do
begin
Parent := Page.Surface;
Left := ScaleX(112);
Top := ScaleY(8);
Width := ScaleX(273);
Height := ScaleY(21);
TabOrder := 0;
end;
{ lblAuthType }
lblAuthType := TLabel.Create(Page);
with lblAuthType do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblAuthType_Caption0}');
Left := ScaleX(24);
Top := ScaleY(48);
Width := ScaleX(87);
Height := ScaleY(13);
end;
{ chkWindowsAuth }
chkWindowsAuth := TRadioButton.Create(Page);
with chkWindowsAuth do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_chkWindowsAuth_Caption0}');
Left := ScaleX(32);
Top := ScaleY(64);
Width := ScaleX(177);
Height := ScaleY(17);
Checked := True;
TabOrder := 1;
TabStop := True;
end;
{ chkSQLAuth }
chkSQLAuth := TRadioButton.Create(Page);
with chkSQLAuth do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_chkSQLAuth_Caption0}');
Left := ScaleX(32);
Top := ScaleY(84);
Width := ScaleX(185);
Height := ScaleY(17);
TabOrder := 2;
end;
{ lblUser }
lblUser := TLabel.Create(Page);
with lblUser do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblUser_Caption0}');
Left := ScaleX(56);
Top := ScaleY(104);
Width := ScaleX(58);
Height := ScaleY(13);
Enabled := False;
end;
{ lblPassword }
lblPassword := TLabel.Create(Page);
with lblPassword do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblPassword_Caption0}');
Left := ScaleX(56);
Top := ScaleY(128);
Width := ScaleX(53);
Height := ScaleY(13);
Enabled := False;
end;
{ txtUsername }
txtUsername := TEdit.Create(Page);
with txtUsername do
begin
Parent := Page.Surface;
Left := ScaleX(120);
Top := ScaleY(104);
Width := ScaleX(241);
Height := ScaleY(21);
Enabled := False;
TabOrder := 3;
end;
{ txtPassword }
txtPassword := TPasswordEdit.Create(Page);
with txtPassword do
begin
Parent := Page.Surface;
Left := ScaleX(120);
Top := ScaleY(128);
Width := ScaleX(241);
Height := ScaleY(21);
Enabled := False;
TabOrder := 4;
end;
{ lblDatabase }
lblDatabase := TLabel.Create(Page);
with lblDatabase do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblDatabase_Caption0}');
Left := ScaleX(56);
Top := ScaleY(168);
Width := ScaleX(53);
Height := ScaleY(13);
end;
{ lstDatabase }
lstDatabase := TComboBox.Create(Page);
with lstDatabase do
begin
Parent := Page.Surface;
Left := ScaleX(120);
Top := ScaleY(168);
Width := ScaleX(145);
Height := ScaleY(21);
TabOrder := 5;
end;
with Page do
begin
OnActivate := @CustomForm_Activate;
OnShouldSkipPage := @CustomForm_ShouldSkipPage;
OnBackButtonClick := @CustomForm_BackButtonClick;
OnNextButtonClick := @CustomForm_NextButtonClick;
OnCancelButtonClick := @CustomForm_CancelButtonClick;
end;
Result := Page.ID;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = Page.ID then
WizardForm.NextButton.Enabled := False;
end;
{ CustomForm_InitializeWizard }
procedure InitializeWizard();
begin
CustomForm_CreatePage(wpWelcome);
end;
得到它的工作。 这是为了防止别人想要类似的功能。 只需更改许可证和.sql
文件引用的链接即可
[Setup]
AppName=test
AppVersion=1.0
LicenseFile=C:setup demoLicense.rtf
CreateAppDir=False
UsePreviousGroup=False
DisableProgramGroupPage=yes
Uninstallable=no
[Files]
Source: "C:setup demoscript 2008R2.sql"; Flags: dontcopy
Source: "C:setup demoscript 2012.sql"; Flags: dontcopy
[CustomMessages]
CustomForm_Caption=Connect to Database Server
CustomForm_Description=Enter the information required to connect to the database server
CustomForm_lblServer_Caption0=Server name:
CustomForm_lblAuthType_Caption0=Log on credentials
CustomForm_lblUser_Caption0=User name:
CustomForm_lblPassword_Caption0=Password:
CustomForm_lblDatabase_Caption0=Database:
CustomForm_lblVersion_Caption0=SQL Version:
CustomForm_chkSQLAuth_Caption0=Use SQL Server Authentication
CustomForm_chkWindowsAuth_Caption0=Use Windows Authentication
CustomForm_lstVersion_Line0=2008 R2
CustomForm_lstVersion_Line1=2012
[Code]
const
adCmdUnspecified = $FFFFFFFF;
adCmdUnknown = $00000008;
adCmdText = $00000001;
adCmdTable = $00000002;
adCmdStoredProc = $00000004;
adCmdFile = $00000100;
adCmdTableDirect = $00000200;
adOptionUnspecified = $FFFFFFFF;
adAsyncExecute = $00000010;
adAsyncFetch = $00000020;
adAsyncFetchNonBlocking = $00000040;
adExecuteNoRecords = $00000080;
adExecuteStream = $00000400;
adExecuteRecord = $00000800;
var
lblVersion: TLabel;
lstVersion: TComboBox;
lblServer: TLabel;
lblAuthType: TLabel;
lblUser: TLabel;
lblPassword: TLabel;
lblDatabase: TLabel;
chkSQLAuth: TRadioButton;
txtServer: TEdit;
chkWindowsAuth: TRadioButton;
txtUsername: TEdit;
txtPassword: TPasswordEdit;
lstDatabase: TComboBox;
bIsNextEnabled: Boolean;
var
Page: TWizardPage;
{ Used to generate error code by sql script errors }
procedure ExitProcess(exitCode:integer);
external 'ExitProcess@kernel32.dll stdcall';
{ Version drop down defaults to blank. Enable server textbox once a version is selected. This forces user to select the version first. }
Procedure VersionOnChange (Sender: TObject);
begin
lblServer.Enabled := True;
txtServer.Enabled := True;
end;
{ enable/disable child text boxes & functions when text has been entered into Server textbox. Makes no sense to populate child items unless a value exists for server. }
Procedure ServerOnChange (Sender: TObject);
begin
lstDatabase.Items.Clear;
lstDatabase.Text := '';
bIsNextEnabled := False;
WizardForm.NextButton.Enabled := bIsNextEnabled;
if Length(txtServer.Text) > 0 then
begin
lblAuthType.Enabled := True;
lblDatabase.Enabled := True;
lstDatabase.Enabled := True;
chkWindowsAuth.Enabled := True;
chkSQLAuth.Enabled := True;
end
else
begin
lblAuthType.Enabled := False;
lblDatabase.Enabled := False;
lstDatabase.Enabled := False;
chkWindowsAuth.Enabled := False;
chkSQLAuth.Enabled := False;
end
end;
{ enable/disable user/pass text boxes depending on selected auth type. A user/pass is only required for SQL Auth }
procedure AuthOnChange (Sender: TObject);
begin
if chkSQLAuth.Checked then
begin
lblUser.Enabled := true;
lblPassword.Enabled := true;
txtUsername.Enabled := true;
txtPassword.Enabled := true;
end
Else
begin
lblUser.Enabled := false;
lblPassword.Enabled := false;
txtUsername.Enabled := false;
txtPassword.Enabled := false;
end
end;
{ Enable next button once a database name has been entered. }
Procedure DatabaseOnChange (Sender: TObject);
begin
if (Length(lstDatabase.Text) > 0) and (lstDatabase.Enabled) then
begin
bIsNextEnabled := True;
WizardForm.NextButton.Enabled := bIsNextEnabled;
end
else
begin
bIsNextEnabled := False;
WizardForm.NextButton.Enabled := bIsNextEnabled;
end
end;
{ Retrieve a list of databases accessible on the server with the credentials specified. }
{ This list is shown in the database dropdown list }
procedure RetrieveDatabaseList(Sender: TObject);
var
ADOCommand: Variant;
ADORecordset: Variant;
ADOConnection: Variant;
begin
lstDatabase.Items.Clear;
try
{ create the ADO connection object }
ADOConnection := CreateOleObject('ADODB.Connection');
{ build a connection string; for more information, search for ADO }
{ connection string on the Internet }
ADOConnection.ConnectionString :=
'Provider=SQLOLEDB;' + { provider }
'Data Source=' + txtServer.Text + ';' + { server name }
'Application Name=' + '{#SetupSetting("AppName")}' + ' DB List;'
if chkWindowsAuth.Checked then
ADOConnection.ConnectionString := ADOConnection.ConnectionString +
'Integrated Security=SSPI;' { Windows Auth }
else
ADOConnection.ConnectionString := ADOConnection.ConnectionString +
'User Id=' + txtUsername.Text + ';' + { user name }
'Password=' + txtPassword.Text + ';'; { password }
{ open the connection by the assigned ConnectionString }
ADOConnection.Open;
try
{ create the ADO command object }
ADOCommand := CreateOleObject('ADODB.Command');
{ assign the currently opened connection to ADO command object }
ADOCommand.ActiveConnection := ADOConnection;
{ assign text of a command to be issued against a provider }
ADOCommand.CommandText := 'SELECT name FROM master.dbo.sysdatabases WHERE HAS_DBACCESS(name) = 1 ORDER BY name';
{ this property setting means, that you're going to execute the }
{ CommandText text command; it does the same, like if you would }
{ use only adCmdText flag in the Execute statement }
ADOCommand.CommandType := adCmdText;
{ this will execute the command and return dataset }
ADORecordset := ADOCommand.Execute;
{ get values from a dataset using 0 based indexed field access; }
{ notice, that you can't directly concatenate constant strings }
{ with Variant data values }
while not ADORecordset.eof do
begin
lstDatabase.Items.Add(ADORecordset.Fields(0));
ADORecordset.MoveNext;
end
finally
ADOConnection.Close;
end;
except
MsgBox(GetExceptionMessage, mbError, MB_OK);
end;
end;
{ Execute files specified in [files] section (hardcoded) against the user defined server.database }
procedure DeploySQL();
var
Script2008R2: AnsiString;
Script2012: AnsiString;
ADOCommand: Variant;
ADOConnection: Variant;
begin
{ extract required version of script }
if lstVersion.Text='2008 R2' then
ExtractTemporaryFile('Script 2008R2.sql')
if lstVersion.Text='2012' then
ExtractTemporaryFile('Script 2012.sql');
try
{ create the ADO connection object }
ADOConnection := CreateOleObject('ADODB.Connection');
{ build a connection string; for more information, search for ADO }
{ connection string on the Internet }
ADOConnection.ConnectionString :=
'Provider=SQLOLEDB;' + { provider }
'Data Source=' + txtServer.Text + ';' + { server name }
'Initial Catalog=' + lstDatabase.Text + ';' + { server name }
'Application Name=' + '{#SetupSetting("AppName")}' + ' Execute SQL;' ;
if chkWindowsAuth.Checked then
ADOConnection.ConnectionString := ADOConnection.ConnectionString +
'Integrated Security=SSPI;' { Windows Auth }
else
ADOConnection.ConnectionString := ADOConnection.ConnectionString +
'User Id=' + txtUsername.Text + ';' + { user name }
'Password=' + txtPassword.Text + ';'; { password }
{ open the connection by the assigned ConnectionString }
ADOConnection.Open;
try
{ create the ADO command object }
ADOCommand := CreateOleObject('ADODB.Command');
{ assign the currently opened connection to ADO command object }
ADOCommand.ActiveConnection := ADOConnection;
{ load a script from file into variable. Exclusive OR because both versions should never exist at the same time. }
if (LoadStringFromFile(ExpandConstant('{tmp}Script 2012.sql'), Script2012)) xor (LoadStringFromFile(ExpandConstant('{tmp}Script 2008R2.sql'), Script2008R2)) then
begin
{ assign text of a command to be issued against a provider. Append all 3 because one of the install assembly strings will always be empty. }
ADOCommand.CommandText := Script2008R2 + Script2012;
{ this will execute the script; the adCmdText flag here means }
{ you're going to execute the CommandText text command, while }
{ the adExecuteNoRecords flag ensures no data row will be get }
{ from a provider, what should improve performance }
ADOCommand.Execute(NULL, NULL, adCmdText or adExecuteNoRecords);
end
else
begin
MsgBox('Installation files missing.', mbError, MB_OK);
ExitProcess(7);
end
finally
ADOConnection.Close;
end;
except
MsgBox(GetExceptionMessage, mbError, MB_OK);
ExitProcess(5);
end;
end;
{ CustomForm_NextkButtonClick }
{ try to connect to supplied db. Dont need to catch errors/close conn on error because a failed connection is never opened. }
function CustomForm_NextButtonClick(Page: TWizardPage): Boolean;
var
ADOConnection: Variant;
begin
{ create the ADO connection object }
ADOConnection := CreateOleObject('ADODB.Connection');
{ build a connection string; for more information, search for ADO }
{ connection string on the Internet }
ADOConnection.ConnectionString :=
'Provider=SQLOLEDB;' + { provider }
'Data Source=' + txtServer.Text + ';' + { server name }
'Initial Catalog=' + lstDatabase.Text + ';' + { server name }
'Application Name=' + '{#SetupSetting("AppName")}' + ' Execute SQL;' ;
if chkWindowsAuth.Checked then
ADOConnection.ConnectionString := ADOConnection.ConnectionString +
'Integrated Security=SSPI;' { Windows Auth }
else
ADOConnection.ConnectionString := ADOConnection.ConnectionString +
'User Id=' + txtUsername.Text + ';' + { user name }
'Password=' + txtPassword.Text + ';'; { password }
{ open the connection by the assigned ConnectionString }
ADOConnection.Open;
Result := True;
end;
{ CustomForm_CreatePage }
function CustomForm_CreatePage(PreviousPageId: Integer): Integer;
begin
Page := CreateCustomPage(
PreviousPageId,
ExpandConstant('{cm:CustomForm_Caption}'),
ExpandConstant('{cm:CustomForm_Description}')
);
{ lblVersion }
lblVersion := TLabel.Create(Page);
with lblVersion do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblVersion_Caption0}');
Left := ScaleX(24);
Top := ScaleY(8);
Width := ScaleX(61);
Height := ScaleY(13);
end;
{ lstVersion }
lstVersion := TComboBox.Create(Page);
with lstVersion do
begin
Parent := Page.Surface;
Left := ScaleX(112);
Top := ScaleY(8);
Width := ScaleX(145);
Height := ScaleY(21);
Style := csDropDownList;
DropDownCount := 2;
TabOrder := 0;
Items.Add(ExpandConstant('{cm:CustomForm_lstVersion_Line0}'));
Items.Add(ExpandConstant('{cm:CustomForm_lstVersion_Line1}'));
OnChange:= @VersionOnChange;
end;
{ lblServer }
lblServer := TLabel.Create(Page);
with lblServer do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblServer_Caption0}');
Left := ScaleX(24);
Top := ScaleY(32);
Width := ScaleX(68);
Height := ScaleY(13);
Enabled := False;
end;
{ txtServer }
txtServer := TEdit.Create(Page);
with txtServer do
begin
Parent := Page.Surface;
Left := ScaleX(112);
Top := ScaleY(32);
Width := ScaleX(273);
Height := ScaleY(21);
TabOrder := 1;
Enabled := False;
OnChange := @ServerOnChange;
end;
{ lblAuthType }
lblAuthType := TLabel.Create(Page);
with lblAuthType do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblAuthType_Caption0}');
Left := ScaleX(24);
Top := ScaleY(72);
Width := ScaleX(87);
Height := ScaleY(13);
Enabled := False;
end;
{ chkWindowsAuth }
chkWindowsAuth := TRadioButton.Create(Page);
with chkWindowsAuth do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_chkWindowsAuth_Caption0}');
Left := ScaleX(32);
Top := ScaleY(88);
Width := ScaleX(177);
Height := ScaleY(17);
Checked := True;
TabOrder := 2;
TabStop := True;
OnClick := @AuthOnChange;
Enabled := False;
end;
{ chkSQLAuth }
chkSQLAuth := TRadioButton.Create(Page);
with chkSQLAuth do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_chkSQLAuth_Caption0}');
Left := ScaleX(32);
Top := ScaleY(108);
Width := ScaleX(185);
Height := ScaleY(17);
TabOrder := 3;
OnClick := @AuthOnChange;
Enabled := False;
end;
{ lblUser }
lblUser := TLabel.Create(Page);
with lblUser do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblUser_Caption0}');
Left := ScaleX(56);
Top := ScaleY(128);
Width := ScaleX(58);
Height := ScaleY(13);
Enabled := False;
end;
{ lblPassword }
lblPassword := TLabel.Create(Page);
with lblPassword do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblPassword_Caption0}');
Left := ScaleX(56);
Top := ScaleY(152);
Width := ScaleX(53);
Height := ScaleY(13);
Enabled := False;
end;
{ txtUsername }
txtUsername := TEdit.Create(Page);
with txtUsername do
begin
Parent := Page.Surface;
Left := ScaleX(120);
Top := ScaleY(128);
Width := ScaleX(241);
Height := ScaleY(21);
Enabled := False;
TabOrder := 4;
end;
{ txtPassword }
txtPassword := TPasswordEdit.Create(Page);
with txtPassword do
begin
Parent := Page.Surface;
Left := ScaleX(120);
Top := ScaleY(152);
Width := ScaleX(241);
Height := ScaleY(21);
Enabled := False;
TabOrder := 5;
end;
{ lblDatabase }
lblDatabase := TLabel.Create(Page);
with lblDatabase do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:CustomForm_lblDatabase_Caption0}');
Left := ScaleX(56);
Top := ScaleY(192);
Width := ScaleX(53);
Height := ScaleY(13);
Enabled := False;
end;
{ lstDatabase }
lstDatabase := TComboBox.Create(Page);
with lstDatabase do
begin
Parent := Page.Surface;
Left := ScaleX(120);
Top := ScaleY(192);
Width := ScaleX(145);
Height := ScaleY(21);
Enabled := False;
TabOrder := 6;
OnDropDown:= @RetrieveDatabaseList;
OnChange:= @DatabaseOnChange;
end;
with Page do
begin
OnNextButtonClick := @CustomForm_NextButtonClick;
end;
Result := Page.ID;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
{ set initial status of next button. Should be disabled when page is first loaded, but should be enabled if user clicked back. }
if CurPageID = Page.ID then
WizardForm.NextButton.Enabled := bIsNextEnabled;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
{ The preinstall step seems like the best time to do the actual install. The problem is that this is not a traditional install. Nothing is copied to the users' pc }
if CurStep = ssInstall then
DeploySQL;
end;
procedure InitializeWizard();
begin
bIsNextEnabled := False;
CustomForm_CreatePage(wpLicense);
end;
链接地址: http://www.djcxy.com/p/44519.html