導航:首頁 > 源碼編譯 > 編譯時怎麼寫框架

編譯時怎麼寫框架

發布時間:2022-11-24 23:52:39

『壹』 寫一個學生信息的管理系統,用C語言和數據結構,我想請問一下基本的框架怎麼寫請高手指點!!!

不知道你要的信息管理功能都要求實現什麼功能,只是我前兩天做的,可以給你參考一下,用結構做的,其中統計功能是我做的,其他部分是老師編好的
/*預編譯命令*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>

/*自定義數據類型*/
typedef struct student{
char xh[10];//學號
char xm[15];//姓名
int cj[3];//3門課程的成績
}STU,*STUP;

typedef struct stulist{
STUP elem;//結構體指針變數
int listsize;//容量
int length;//長度
}SqList;

/*自定義函數說明*/
int initSqlist(SqList &L,int n);
int insertSqlist(SqList &L,STU x,int i);
void creatSqlist(SqList &L,int m);
int deleteSqlist(SqList &L,int i);
int locateSqlist(SqList L,char *xhp);
void disp(SqList L);
int menu();
void tongji(int bh,SqList &m);

/*函數定義*/

/*菜單函數*/
int menu(){
int num;
while(1){
system("cls");//清屏
printf(" ------------學生成績管理系統-------------\n\n");
printf(" 1--創建學生成績表 2--插入學生成績\n\n");
printf(" 3--刪除學生成績 4--按學號查找\n\n");
printf(" 5--顯示學生成績表 6--成績統計\n\n");
printf(" 0--退出\n\n");
printf(" -----------------------------------------\n");
printf("請輸入0-6:");
scanf("%d",&num);
fflush(stdin);
if(num<0||num>6){
printf("重新選擇!按任意鍵繼續!");
getch();}
else
break;
}
return num;
}

/*初始化函數*/
int initSqlist(SqList &L,int n){
L.elem=new STU[n];
if(L.elem==NULL){
printf("申請空間失敗!\n");
exit(0);
}
L.listsize=n;
L.length=0;
return 1;
}

/*插入函數*/
int insertSqlist(SqList &L,STU x,int i){
int j;
if(i>L.listsize){
printf("溢出!\n");
printf("重新選擇!按任意鍵繼續!");
getch();
return 0;
}
if(i<=0||i>L.length+1){
printf("位置不合理!\n");
printf("重新選擇!按任意鍵繼續!");
getch();
return 0;
}
for(j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j];
L.elem[i-1]=x;
L.length++;
return 1;
}

/*創建函數*/
void creatSqlist(SqList &L,int m){
int i,j; STU x;
for(i=1;i<=m;i++){
printf("請輸入第%d個學生的數據:\n",i);
printf("學號:");
scanf("%s",x.xh);
for(j=0;j<L.length;j++){
if(strcmp(x.xh,L.elem[j].xh)==0){
printf("該學號已存在,請重新輸入:");
scanf("%s",x.xh);
}
}
printf("姓名:");
scanf("%s",x.xm);
printf("高數 C語言 英語(以空格隔開):");
scanf("%d%d%d",&x.cj[0],&x.cj[1],&x.cj[2]);
insertSqlist(L,x,i);
}
fflush(stdin);
}

/*顯示函數*/
void disp(SqList L){
int i;
printf("%10s%15s%7s%7s%7s\n","學號","姓名","高數","C語言","英語");
for(i=0;i<L.length;i++)
printf("%10s%15s%7d%7d%7d\n",L.elem[i].xh,L.elem[i].xm,
L.elem[i].cj[0],L.elem[i].cj[1],L.elem[i].cj[2]);
printf("按任意鍵繼續!");
getch();
}

/*刪除函數*/
int deleteSqlist(SqList &L,int i){
int j;
if(i<=0||i>L.length){
printf("位置不合理,請重新輸入!");
getch();
return 0;
}
for(j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j];
L.length--;
return 1;
}

/*查找函數*/
int locateSqlist(SqList L,char *xhp){
int i;
for(i=0;i<L.length;i++)
if(strcmp(xhp,L.elem[i].xh)==0)break;
if(i<L.length)return i+1;
else return 0;
}

/*統計函數*/
void tongji(int bh,SqList &m){
int i,max,min;
double sum;
switch(bh){
case 1: if(m.length<2){
printf("當前人數少於2個,無法完成統計,按任意鍵繼續:");
getch();
break;
}
sum=0;
for(i=0;i<m.length;i++){
sum=sum+m.elem[i].cj[0];
}
max=m.elem[0].cj[0];
for(i=1;i<m.length;i++){
if(max<m.elem[i].cj[0])
max=m.elem[i].cj[0];
}
min=m.elem[0].cj[0];
for(i=1;i<m.length;i++){
if(min>m.elem[i].cj[0])
min=m.elem[i].cj[0];
}
printf("最高分:%d\n\n最低分:%d\n\n平均分:%.2lf\n",max,min,sum/m.length);
printf("按任意鍵繼續:");
getch();
break;
case 2: if(m.length<2){
printf("當前人數少於2個,無法完成統計,按任意鍵繼續:");
getch();
break;
}
sum=0;
for(i=0;i<m.length;i++){
sum=sum+m.elem[i].cj[1];
}
max=m.elem[0].cj[1];
for(i=1;i<m.length;i++){
if(max<m.elem[i].cj[1])
max=m.elem[i].cj[1];
}
min=m.elem[0].cj[1];
for(i=1;i<m.length;i++){
if(min>m.elem[i].cj[1])
min=m.elem[i].cj[1];
}
printf("最高分:%d\n\n最低分:%d\n\n平均分:%.2lf\n",max,min,sum/m.length);
printf("按任意鍵繼續:");
getch();
break;
case 3: if(m.length<2){
printf("當前人數少於2個,無法完成統計,按任意鍵繼續:");
getch();
break;
}
sum=0;
for(i=0;i<m.length;i++){
sum=sum+m.elem[i].cj[2];
}
max=m.elem[0].cj[2];
for(i=1;i<m.length;i++){
if(max<m.elem[i].cj[2])
max=m.elem[i].cj[2];
}
min=m.elem[0].cj[2];
for(i=1;i<m.length;i++){
if(min>m.elem[i].cj[2])
min=m.elem[i].cj[2];
}
printf("最高分:%d\n\n最低分:%d\n\n平均分:%.2lf\n",max,min,sum/m.length);
printf("按任意鍵繼續:");
getch();
break;
}
}

/*主函數*/
void main(){
int menunum,n,m,pos,i; SqList L; STU x; char xh[10];
while(1){
menunum=menu();
switch(menunum){
case 0: exit(0);

case 1: printf("請輸入學生總人數:");
scanf("%d",&n);
if(initSqlist(L,n)){
printf("請輸入現在需要創建的學生人數:");
scanf("%d",&m);
creatSqlist(L,m);
}
break;

case 2: printf("\n請輸入待插入學生的數據:\n");
printf("學號:");
scanf("%s",x.xh);
for(i=0;i<L.length;i++){
if(strcmp(x.xh,L.elem[i].xh)==0){
printf("該學號已存在,請重新輸入:");
scanf("%s",x.xh);
}
}
printf("姓名:");
scanf("%s",x.xm);
printf("高數 C語言 英語:");
scanf("%d%d%d",&x.cj[0],&x.cj[1],&x.cj[2]);
while(1){
printf("請輸入待插入學生的位置(1-%d):",L.length+1);
scanf("%d",&pos);
if(insertSqlist(L,x,pos))break;
}
break;

case 3: while(1){
printf("請輸入待刪除學生的位置(1-%d):",L.length);
scanf("%d",&pos);
if(deleteSqlist(L,pos))break;
}
break;

case 4: printf("請輸入要查找的學號:");
scanf("%s",xh);
pos=locateSqlist(L,xh);
if(pos!=0){
printf("該學生的成績如下:\n");
printf("%10s%15s%7s%7s%7s\n","學號","姓名","高數","C語言","英語");
printf("%10s%15s%7d%7d%7d\n",L.elem[pos-1].xh,L.elem[pos-1].xm,
L.elem[pos-1].cj[0],L.elem[pos-1].cj[1],L.elem[pos-1].cj[2]);
}
else
printf("該學生的成績不存在\n");
printf("按任意鍵繼續!");
getch();
break;
case 5: disp(L);
break;
case 6: int bianhao1;
printf("請輸入需要統計的課程編號1-3:\n\n");
printf("1--高數 2--C語言 3--英語 \n\n");
scanf("%d",&bianhao1);
fflush(stdin);
while(bianhao1<1||bianhao1>3){
printf("編號錯誤,請重新輸入:\n");
scanf("%d",&bianhao1);
}
tongji(bianhao1,L);

}//switch

}//while
}

『貳』 論文框架怎麼寫

1、摘要 寫論文大綱首先要明確論文的格式,最重要的是先要把摘要弄好,論文摘要就是要明確寫出論文的主旨是什麼,用三百至五百個字描述清楚論文的大概內容,再用幾個詞總結出來。

2、目錄 論文大綱還需要一個很重要的內容就是目錄,目錄一定要清晰。

3、前言 論文大綱還要有的內容就是前言,交代寫論文的目的以及選題背景。

4、選題背景及方法 詳細具體的寫出論文的背景和研究方法。

5、歸納出現的問題 把在做論文時發現的問題寫出來,再寫出解決問題的方法,做到發現問題就要解決問題。

6、提出的建議 論文大綱承接上部分所出現的問題就是提出的建議,因為能夠發現問題說明有一定的判斷能力,但是能夠實際的提出建議,才能看出具體解決問題的能力。基本結構的寫作更多是對文章的規劃,可以避免很多寫作中的常見問題,如字數超過規定字數,或者寫不夠規定字數,對寫作是很有益處的

『叄』 如何編譯CNTK框架

如何編譯CNTK框架
現階段不可能。

.NET 下你用的所有 CLR 方法也好類也好都是封裝在一個個 .NET Framework 的 DLL 里的,程序運行時必須這些文件,除非你一個 CLR 類和方法都沒用過(那不就是純 C 語言而且是只有標准庫的 C 了么)。

有些類似於 Remote Soft's Salamander .NET Native Compiler、Infralution Globalizer 這些工具,也不是把代碼編譯成機器語言了,而是把需要的類庫等直接寫入了 PE,會導致程序體積大大增加,而且只支持 .NET 2.0,最重要的是還是收費軟體。

微軟自己倒是新推出了個 .NET Native,不過可惜的是目前只支持 .NET 4.5 下的 Windows 8 App 程序。

『肆』 如何寫一個編譯時註解框架

1、註解

我們日常使用的很多開源庫都有註解的實現,主要有運行時註解和編譯時註解兩種。
運行時註解:主要作用就是得到註解的信息
Retrofit:
調用

@GET("/users/{username}")
User getUser(@Path("username") String username);

定義
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {
String value();
}

編譯時註解:主要作用動態生成代碼

Butter Knife

調用
@InjectView(R.id.user)
EditText username;

定義
@Retention(CLASS)
@Target(FIELD)
public @interface InjectView {
int value();
}

2、編譯時註解

要實現編譯時註解需要3步:

1、定義註解(關於註解的定義可以參考下面引用的博客)
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;

@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Seriable
{

}

2、編寫註解解析器
@SupportedAnnotationTypes("annotation.Seriable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectProcessor extends AbstractProcessor {

@Override
public boolean process(Set annotations,
RoundEnvironment roundEnv) {

for (Element ele : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
if(ele.getKind() == ElementKind.FIELD){
//todo
}

return true;
}

@SupportedAnnotationTypes,定義要支持註解的完整路徑,也可以通過getSupportedAnnotationTypes方法來定義
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(InjectView.class.getCanonicalName());

return types;
}

@SupportedSourceVersion(SourceVersion.RELEASE_6)表示支持的jdk的版本
3、創建制定文件resources/META-INF/services/javax.annotation.processing.Processor,並填寫註解解析器的類路徑,這樣在編譯的時候就能自動找到解析器

看上去實現編譯時註解還是很容易的,但是真要完整的實現一個類似Butter Knife的框架,這還只是開始。
Butter Knife是專注View的注入,在使用註解的類編譯後,查看編譯後的class文件,會發現多出了文件,如:
SimpleActivity$$ViewInjector.java
SimpleActivity$$ViewInjector.java,就是通過編譯時註解動態創建出來的,查看SimpleActivity$$ViewInjector.java的內容
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife;

import android.view.View;

import butterknife.ButterKnife.Finder;

public class SimpleActivity$$ViewInjector {

public static void inject(Finder finder, final com.example.butterknife.SimpleActivity target, Object source) {
View view;
view = finder.findRequiredView(source, 2131230759, "field 'title'");
target.title = (android.widget.TextView) view;
view = finder.findRequiredView(source, 2131230783, "field 'subtitle'");
target.subtitle = (android.widget.TextView) view;
view = finder.findRequiredView(source, 2131230784, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = (android.widget.Button) view;
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override
public void doClick(
android.view.View p0
) {
target.sayHello();
}
});
view.setOnLongClickListener(
new android.view.View.OnLongClickListener() {
@Override
public boolean onLongClick(
android.view.View p0
) {
return target.sayGetOffMe();
}
});
view = finder.findRequiredView(source, 2131230785, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = (android.widget.ListView) view;
((android.widget.AdapterView) view).setOnItemClickListener(
new android.widget.AdapterView.OnItemClickListener() {
@Override
public void onItemClick(
android.widget.AdapterView p0,
android.view.View p1,
int p2,
long p3
) {
target.onItemClick(p2);
}
});
view = finder.findRequiredView(source, 2131230786, "field 'footer'");
target.footer = (android.widget.TextView) view;
}

public static void reset(com.example.butterknife.SimpleActivity target) {
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
}
}

inject方法進行初始化,reset進行釋放。inject都是調用Finder的方法與android系統的findViewById等方法很像,再來看Finder類,只截取部分。

public enum Finder {
public T findRequiredView(Object source, int id, String who) {
T view = findOptionalView(source, id, who);
if (view == null) {
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
return view;
}

public T findOptionalView(Object source, int id, String who) {
View view = findView(source, id);
return castView(view, id, who);
}

@Override protected View findView(Object source, int id) {
return ((View) source).findViewById(id);
}

@SuppressWarnings("unchecked") // That's the point.
public T castView(View view, int id, String who) {
try {
return (T) view;
} catch (ClassCastException e) {
if (who == null) {
throw new AssertionError();
}
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}

findRequiredView方法實際上就是我們常用findViewById的實現,其動態幫我們添加了這些實現。view注入的原理我們就清楚了。
雖然編譯時創建了這個類,運行的時候如何使用這個類呢,這里我用自己實現的一個類來描述
public class XlViewInjector {

static final Map, AbstractInjector> INJECTORS = new LinkedHashMap, AbstractInjector>();

public static void inject(Activity activity){
AbstractInjector injector = findInjector(activity);
injector.inject(Finder.ACTIVITY, activity, activity);
}

public static void inject(Object target, View view){
AbstractInjector injector = findInjector(target);
injector.inject(Finder.VIEW, target, view);
}

private static AbstractInjector findInjector(Object target){
Class clazz = target.getClass();
AbstractInjector injector = INJECTORS.get(clazz);
if(injector == null){
try{
Class injectorClazz = Class.forName(clazz.getName()+"$$"+ProxyInfo.PROXY);
injector = (AbstractInjector) injectorClazz.newInstance();
INJECTORS.put(clazz, injector);
}catch(Exception e){
e.printStackTrace();
}
}

return injector;
}
}
XlViewInjector與ButterKnife,比如調用時我們都會執行XlViewInjector.inject方法,通過傳入目標類的名稱獲得封裝後的類實例就是SimpleActivity$$ViewInjector.java,再調用它的inject,來初始化各個view。
總結一下整個實現的流程:
1、通過編譯時註解動態創建了一個包裝類,在這個類中已解析了註解,實現了獲取view、設置監聽等代碼。
2、執行時調用XlViewInjector.inject(object)方法,實例化object類對應的包裝類,並執行他的初始化方法inject;
因此我們也能明白為什麼XlViewInjector.inject(object)方法一定要在setContentView之後執行。

3、實現註解框架時的坑

解析有用到android api,因此需要創建Android工程,但是android library並沒有javax的一些功能,
在eclipse環境下,右鍵build-path add library把jdk加進來
在android studio下,需要先創建java Library功能,實現與view無關的解析,再創建一個android library功能引用這個工程並實現餘下的解析。

『伍』 如何編譯生成xposedbridge

installer3.0版本之後,傳統的xposed框架的使用方法是從官網上下載xposed installer.apk以及xposed-arm-sdk22.zip包。然後具體的使用方法是將xposed installer.apk安裝到手機,然後在手機上進入recovery模式,將下載的xposed-arm-sdk22.zip刷入手機。完成之後xposed installer才可以正常使用。本文寫了另一種xposed框架的使用辦法:編譯xposed源碼,生成xposed相關的應用程序和so庫等文件,然後將這些應用程序和so庫文件集成到android系統中,重新打包生成鏡像文件,燒入手機。
文檔主要用於xposed源碼的編譯以及xposed的集成工作。在編譯過程中,主要是通過Xposed Tools進行編譯;在集成過程中,將xposed的編譯之後生成的文件替換到android系統中,打包之後重新生成鏡像文件,將鏡像文件刷入到手機查看日誌信息來判斷是否成功。
由於在集成過程中是將xposed的編譯生成文件打包到android5.1系統中重新生成鏡像文件,然後將鏡像文件燒入到nexus5手機,所以需要對android源碼進行編譯,並在執行lunch選擇對應源碼代號時選擇nexus5對應的android源碼代號。
環境配置

『陸』 寫出一個匯編語言的框架程序,內容2個段,數據段和代碼段

呵呵,建議你下了解一下殺毒軟體的工作機制。
金山,360,瑞星等殺毒軟體,都是驅動保護的,bat因為比它高層,所以做不到關閉殺毒軟體。否則連批處理都可輕易攻破,那早就有無數病毒泛濫了。只有匯編語言這樣的底層語言才可以,(其實C語言也能做到)。當然,bat也不是不行,可以用bat匯編,叫做ASCIICoding技術,挺難的。2007年以前國內還沒有一個會的(除非會但不說,那我當然就不知道了)。也建議你學習Rootkit Hook技術,可以讀一讀這篇文章。

瘟神的尾行--Rootkit技術發展史
作者:小金
一. 無法驅逐的「助手」
網管小張正在手忙腳亂的尋找他的手工殺毒工具包,因為他在安裝一個網管工具的時候無意中走了神,點擊了「下一步」按鈕後才驚覺安裝程序的界面里一個不引人注目的角落裡寫著「安裝CNNIC網路實名」這一行小字,而且最開頭部分有一個小小的勾。於是著名的「中國網民的得力助手」便理所當然的在他的機器里安了家。
心裡把廠商罵了十八遍的小張終於翻出了他外出修機時最得意的工具IceSword和超級巡警,果然在進程列表和SSDT列表裡發現了紅色警報,小張笑了笑,對付這些一般用戶無法卸載的惡意流氓,自己可謂經驗豐富了,當下便三下五除二的把CNNIC的進程給終結了,SSDT也給恢復了初始狀態,然後小張去刪除注冊表啟動項——突然發出的一個錯誤提示聲音把小張嚇了一跳,再定睛一看,他的笑容凝固了:「刪除項時出錯」。不會吧?小張急忙去刪除CNNIC目錄,結果徹底愣在了那裡,系統彈出的錯誤提示很明確的告訴他,「無法刪除文件,文件可能正在被使用」。怎麼回事?小張一下子沒了頭緒……
達爾文的進化論告訴我們,「物競天擇,適者生存」,同樣,在這安全與入侵的網路世界裡,也在進行著這樣一場選擇的過程……

二. 被AIDS糾纏的互聯網
。。。。。。。。。
。。。。。。。。。。。。。
。。。。。。。。。。。。。。(網路長度限制。。。想看全篇自己搜吧)
三. 結語

雖然到處都在提倡和諧網路的普及,但是,「健康上網」僅僅是指代那些黃賭毒而已嗎?在利益面前,開發者的正義感越發渺小起來,我們的網路世界,是被瘟神緊緊跟隨著的。技術的斗爭越發激烈,但是用戶的電腦知識是不會跟著時代發展而自動填充的,最終,大眾上網的人民成了這一切技術較量的受害者。
這個荒謬的發展方向,何時才能休止呢?

還有這篇:

對大多數的Windows開發者來說,如何在Win32系統中對API函數的調用進行攔截一直是項極富挑戰性的課題,因為這將是對你所掌握的計算機知識較為全面的考驗,尤其是一些在如今使用RAD進行軟體開發時並不常用的知識,這包括了操作系統原理、匯編語言甚至是機器指令(聽上去真是有點恐怖,不過這是事實)。

當前廣泛使用的Windows操作系統中,像Win 9x和Win NT/2K,都提供了一種比較穩健的機制來使得各個進程的內存地址空間之間是相互獨立,也就是說一個進程中的某個有效的內存地址對另一個進程來說是無意義的,這種內存保護措施大大增加了系統的穩定性。不過,這也使得進行系統級的API攔截的工作的難度也大大加大了。

當然,我這里所指的是比較文雅的攔截方式,通過修改可執行文件在內存中的映像中有關代碼,實現對API調用的動態攔截;而不是採用比較暴力的方式,直接對可執行文件的磁碟存儲中機器代碼進行改寫。

二、API鉤子系統一般框架
通常,我們把攔截API的調用的這個過程稱為是安裝一個API鉤子(API Hook)。一個API鉤子基本是由兩個模塊組成:一個是鉤子伺服器(Hook Server)模塊,一般為EXE的形式;一個是鉤子驅動器(Hook Driver)模塊,一般為DLL的形式。

鉤子伺服器主要負責向目標進程注入鉤子驅動器,使得鉤子驅動器運行在目標進程的地址空間中,這是關鍵的第一步,而鉤子驅動器則負責實際的API攔截處理工作,以便在我們所關心的API函數調用的之前或之後能做一些我們所希望的工作。一個比較常見的API鉤子的例子就是一些實時翻譯軟體(像金山詞霸)中必備的的功能:屏幕抓詞。它主要是對一些Win32 API中的GDI函數進行了攔截,獲取它們的輸入參數中的字元串,然後在自己的窗口中顯示出來。

針對上述關於API鉤子的兩個部分,有以下兩點需要我們重點考慮的: 選用何種DLL注入技術,以及採用何種API攔截機制。

三、注入技術的選用
由於在Win32系統中各個進程的地址是互相獨立的,因此我們無法在一個進程中對另一個進程的代碼進行有效的修改,但如果你要完成API鉤子的工作又必須如此。因此,我們必須採取某種獨特的手段,使得API鉤子(准確的說是鉤子驅動器)能夠成為目標進程中的一部分,才有較大的可能來對目標進程數據和代碼進行有控制的修改。

通常可採用的幾種注入方式:

1.利用注冊表
如果我們准備攔截的進程連接了User32.dll,也就是使用了 User32.dll中的API(一般圖形界面的應用程序都是符合這個條件),那麼就可以簡單把你的鉤子驅動器DLL的名字作為值添加在下面注冊表的鍵下: HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs 值的形式可以為單個DLL的文件名,或者是一組DLL的文件名,相鄰的名稱之間用逗號或空格間隔。所有由該值標識的DLL將在符合條件的應用程序啟動的時候裝載。這是一個操作系統內建的機制,相對其他方式來說危險性較小,但它也有一些比較明顯的缺點:該方法僅適用於NT/2K操作系統,顯然看看鍵的名稱就可以明白;如果需要激活或停止鉤子的注入,只有重新啟動Windows,這個就似乎太不方便了;最後一點也很顯然,不能用此方法向沒有使用User32.dll的應用程序注入DLL,例如控制台應用程序等。另外,不管是否為你所希望,鉤子DLL將注入每一個GUI應用程序,這將導致整個系統性能的下降!

2.建立系統范圍的Windows鉤子
要向某個進程注入DLL,一個十分普遍也是比較簡單的方法就是建立在標準的Windows鉤子的基礎上。Windows鉤子一般是在DLL中實現的,這是一個全局性的Windows鉤子的基本要求,這也很符合我們的需要。當我們成功地調用SetWindowsHookEx函數之後,便在系統中安裝了某種類型的消息鉤子,這個鉤子可以是針對某個進程,也可以是針對系統中的所有進程。一旦某個進程中產生了該類型的消息,操作系統會自動把該鉤子所在的DLL映像到該進程的地址空間中,從而使得消息回調函數(在 SetWindowsHookEx的參數中指定)能夠對此消息進行適當的處理,在這里,我們所感興趣的當然不是對消息進行什麼處理,因此在消息回調函數中只需把消息鉤子向後傳遞就可以了,但是我們所需的DLL已經成功地注入了目標進程的地址空間,從而可以完成後續工作。

我們知道,不同的進程之間是不能直接共享數據的,因為它們活動在不同的地址空間中。但在Windows鉤子 DLL中,有一些數據,例如Windows鉤子句柄HHook,這是由SetWindowsHookEx函數返回值得到的,並且作為參數將在 CallNextHookEx函數和UnhookWindoesHookEx函數中使用,顯然使用SetWindowsHookEx函數的進程和使用 CallNextHookEx函數的進程一般不會是同一個進程,因此我們必須能夠使句柄在所有的地址空間中都是有效的有意義的,也就是說,它的值必須必須在這些鉤子DLL所掛鉤的進程之間是共享的。為了達到這個目的,我們就應該把它存儲在一個共享的數據區域中。

在VC++中我們可以採用預編譯指令#pragma data_seg在DLL文件中創建一個新的段,並且在DEF文件中把該段的屬性設置為"shared",這樣就建立了一個共享數據段。對於使用 Delphi的人來說就沒有這么幸運了:沒有類似的比較簡單的方法(或許是有的,但我沒有找到)。不過我們還是可以利用內存映像技術來申請使用一塊各進程可以共享的內存區域,主要是利用了CreateFileMapping和MapViewOfFile這兩個函數,這倒是一個通用的方法,適合所有的開發語言,只要它能直接或間接的使用Windows的API。

在Borland的BCB中有一個指令#pragma codeseg與VC++中的#pragma data_seg指令有點類似,應該也能起到一樣的作用,但我試了一下,沒有沒有效果,而且BCB的聯機幫助中對此也提到的不多,不知怎樣才能正確的使用(或許是另外一個指令,呵呵)。

一旦鉤子DLL載入進入目標進程的地址空間後,在我們調用UnHookWindowsHookEx函數之前是無法使它停止工作的,除非目標進程關閉。

這種DLL注入方式有兩個優點: 這種機制在Win 9x/Me和Win NT/2K中都是得到支持的,預計在以後的版本中也將得到支持;鉤子DLL可以在不需要的時候,可由我們主動的調用 UnHookWindowsHookEx來卸載,比起使用注冊表的機制來說方便了許多。盡管這是一種相當簡潔明了的方法,但它也有一些顯而易見的缺點:首先值得我們注意的是,Windows鉤子將會降低整個系統的性能,因為它額外增加了系統在消息處理方面的時間;其次,只有當目標進程准備接受某種消息時,鉤子所在的DLL才會被系統映射到該進程的地址空間中,鉤子才能真正開始發揮作用,因此如果我們要對某些進程的整個生命周期內的API調用情況進行監控,用這種方法顯然會遺漏某些API的調用 。

3.使用 CreateRemoteThread函數
在我看來這是一個相當棒的方法,然而不幸的是,CreateRemoteThread這個函數只能在Win NT/2K系統中才得到支持,雖然在Win 9x中這個API也能被安全的調用而不出錯,但它除了返回一個空值之外什麼也不做。該注入過程也十分簡單:我們知道,任何一個進程都可以使用 LoadLibrary來動態地載入一個DLL。但問題是,我們如何讓目標進程(可能正在運行中)在我們的控制下來載入我們的鉤子DLL(也就是鉤子驅動器)呢?有一個API函數CreateRemoteThread,通過它可在一個進程中可建立並運行一個遠程的線程--這個好像和注入沒什麼關系嘛?往下看!

調用該API需要指定一個線程函數指針作為參數,該線程函數的原型如下: Function ThreadProc(lpParam: Pointer): DWORD,我們再來看一下LoadLibrary的函數原型: Function LoadLibrary(lpFileName: PChar): HMole。發現了吧!這兩個函數原型幾乎是一樣的(其實返回值是否相同關系不大,因為我們是無法得到遠程線程函數的返回值的),這種類似使得我們可以把直接把LoadLibrary當做線程函數來使用,從而在目標進程中載入鉤子DLL。

與此類似,當我們需要卸載鉤子DLL時,也可以FreeLibrary作為線程函數來使用,在目標進程中卸載鉤子DLL,一切看來是十分的簡潔方便。通過調用GetProcAddress函數,我們可以得到LoadLibrary函數的地址。由於 LoadLibrary是Kernel32中的函數,而這個系統DLL的映射地址對每一個進程來說都是相同的,因此LoadLibrary函數的地址也是如此。這點將確保我們能把該函數的地址作為一個有效的參數傳遞給CreateRemoteThread使用。 FreeLibrary也是一樣的。

AddrOfLoadLibrary := GetProcAddress(GetMoleHandle(『Kernel32.dll'), 『LoadLibrary');

HRemoteThread := CreateRemoteThread(HTargetProcess, nil, 0, AddrOfLoadLibrary, HookDllName, 0, nil);

要使用CreateRemoteThread,我們需要目標進程的句柄作為參數。當我們用 OpenProcess函數來得到進程的句柄時,通常是希望對此進程有全權的存取操作,也就是以PROCESS_ALL_ACCESS為標志打開進程。但對於一些系統級的進程,直接這樣顯然是不行的,只能返回一個的空句柄(值為零)。為此,我們必須把自己設置為擁有調試級的特權,這樣將具有最大的存取許可權,從而使得我們能對這些系統級的進程也可以進行一些必要的操作。

4.通過BHO來注入DLL
有時,我們想要注入DLL的對象僅僅是Internet Explorer,很幸運,Windows操作系統為我們提供了一個簡單的歸檔方法(這保證了它的可靠性!)―― 利用Browser Helper Objects(BHO)。一個BHO是一個在 DLL中實現的COM對象,它主要實現了一個IObjectWithSite介面,而每當IE運行時,它會自動載入所有實現了該介面的COM對象。

四、攔截機制
在鉤子應用的系統級別方面,有兩類API攔截的機制――內核級的攔截和用戶級的攔截。內核級的鉤子主要是通過一個內核模式的驅動程序來實現,顯然它的功能應該最為強大,能捕捉到系統活動的任何細節,但難度也較大,不在本文的探討范圍之內(尤其對我這個使用Delphi的人來說,還沒涉足這塊領域,因此也無法探討,呵呵)。

而用戶級的鉤子則通常是在普通的DLL中實現整個API的攔截工作,這才是此次重點關注的。攔截API函數的調用,一般可有以下幾種方法:

1. 代理DLL(特洛伊木馬
一個容易想到的可行的方法是用一個同名的DLL去替換原先那個輸出我們准備攔截的API所在的DLL。當然代理DLL也要和原來的一樣,輸出所有函數。但如果想到DLL中可能輸出了上百個函數,我們就應該明白這種方法的效率是不高的,估計是要累死人的。另外,我們還不得不考慮DLL的版本問題,很是麻煩。

2.改寫執行代碼
有許多攔截的方法是基於可執行代碼的改寫,其中一個就是改變在CALL指令中使用的函數地址,這種方法有些難度,也比較容易出錯。它的基本思路是檢索出在內存中所有你所要攔截的API的CALL指令,然後把原先的地址改成為你自己提供的函數的地址。

另外一種代碼改寫的方法的實現方法更為復雜,它的主要的實現步驟是先找到原先的API函數的地址,然後把該函數開始的幾個位元組用一個JMP指令代替(有時還不得不改用一個INT指令),從而使得對該API函數的調用能夠轉向我們自己的函數調用。實現這種方法要牽涉到一系列壓棧和出棧這樣的較底層的操作,顯然對我們的匯編語言和操作系統底層方面的知識是一種考驗。這個方法倒和很多文件型病毒的感染機制相類似。

3.以調試器的身份進行攔截
另一個可選的方法是在目標函數中安置一個調試斷點,使得進程運行到此處就進入調試狀態。然而這樣一些問題也隨之而來,其中較主要的是調試異常的產生將把進程中所有的線程都掛起。它也需要一個額外的調試模塊來處理所有的異常,整個進程將一直在調試狀態下運行,直至它運行結束。

4.改寫PE文件的輸入地址表
這種方法主要得益於現如今Windows系統中所使用的可執行文件(包括EXE文件和DLL文件)的良好結構――PE文件格式(Portable Executable File Format),因此它相當穩健,又簡單易行。要理解這種方法是如何運作的,首先你得對PE文件格式有所理解。

一個PE文件的結構大致如下所示:一般PE文件一開始是一段DOS程序,當你的程序在不支持Windows的環境中運行時,它就會顯示"This Program cannot be run in DOS mode"這樣的警告語句;接著這個DOS文件頭,就開始真正的PE文件內容了,首先是一段稱為"IMAGE_NT_HEADER"的數據,其中是許多關於整個PE文件的消息,在這段數據的尾端是一個稱為Data Directory的數據表,通過它能快速定位一些PE文件中段(section)的地址;在這段數據之後,則是一個"IMAGE_SECTION_HEADER"的列表,其中的每一項都詳細描述了後面一個段的相關信息;接著它就是PE文件中最主要的段數據了,執行代碼、數據和資源等等信息就分別存放在這些段中。

在所有的這些段里,有一個被稱為".idata"的段(輸入數據段)值得我們去注意,該段中包含著一些被稱為輸入地址表(IAT,Import Address Table)的數據列表,每個用隱式方式載入的API所在的DLL都有一個IAT與之對應,同時一個API的地址也與IAT中一項相對應。當一個應用程序載入到內存中後,針對每一個API函數調用,相應的產生如下的匯編指令:

JMP DWORD PTR [XXXXXXXX]

如果在VC++中使用了_delcspec(import),那麼相應的指令就成為:

CALL DWORD PTR [XXXXXXXX]。

不管怎樣,上述方括弧中的總是一個地址,指向了輸入地址表中一個項,是一個DWORD,而正是這個 DWORD才是API函數在內存中的真正地址。因此我們要想攔截一個API的調用,只要簡單的把那個DWORD改為我們自己的函數的地址,那麼所有關於這個API的調用將轉到我們自己的函數中去,攔截工作也就宣告順利的成功了。這里要注意的是,自定義的函數的調用約定應該是API的調用約定,也就是 stdcall,而Delphi中默認的調用約定是register,它們在參數的傳遞方法等方面存在著較大的區別。

另外,自定義的函數的參數形式一般來講和原先的API函數是相同的,不過這也不是必須的,而且這樣的話在有些時候也會出現一些問題,我在後面將會提到。因此要攔截API的調用,首先我們就要得到相應的IAT的地址。系統把一個進程模塊載入到內存中,其實就是把 PE文件幾乎是原封不動的映射到進程的地址空間中去,而模塊句柄HMole實際上就是模塊映像在內存中的地址,PE文件中一些數據項的地址,都是相對於這個地址的偏移量,因此被稱為相對虛擬地址(RVA,Relative Virtual Address)。

於是我們就可以從HMole開始,經過一系列的地址偏移而得到IAT的地址。不過我這里有一個簡單的方法,它使用了一個現有的API函數ImageDirectoryEntryToData,它幫助我們在定位IAT時能少走幾步,省得把偏移地址弄錯了,走上彎路。不過純粹使用RVA從HMole開始來定位IAT的地址其實並不麻煩,而且這樣還更有助於我們對PE文件的結構的了解。上面提到的那個API 函數是在DbgHelp.dll中輸出的(這是從Win 2K才開始有的,在這之前是由ImageHlp.dll提供的),有關這個函數的詳細介紹可參見MSDN。

在找到IAT之後,我們只需在其中遍歷,找到我們需要的API地址,然後用我們自己的函數地址去覆蓋它,下面給出一段對應的源碼:

procere RedirectApiCall; var ImportDesc:PIMAGE_IMPORT_DESCRIPTOR; FirstThunk:PIMAGE_THUNK_DATA32; sz:DWORD;
begin

//得到一個輸入描述結構列表的首地址,每個DLL都對應一個這樣的結構 ImportDesc:=ImageDirectoryEntryToData(Pointer(HTargetMole), true, IMAGE_DIRECTORY_ENTRY_IMPORT, sz);

while Pointer(ImportDesc.Name)<>nil do
begin //判斷是否是所需的DLL輸入描述

if StrIComp(PChar(DllName),PChar(HTargetMole+ImportDesc.Name))=0 then begin

//得到IAT的首地址
FirstThunk:=PIMAGE_THUNK_DATA32(HTargetMole+ImportDesc.FirstThunk);

while FirstThunk.Func<>nil do
begin

if FirstThunk.Func=OldAddressOfAPI then
begin

//找到了匹配的API地址 ......
//改寫API的地址

break;

end;

Inc(FirstThunk);

end;

end;

Inc(ImportDesc);

end;

end;

最後有一點要指出,如果我們手工執行鉤子DLL的退出目標進程,那麼在退出前應該把函數調用地址改回原先的地址,也就是API的真正地址,因為一旦你的DLL退出了,改寫的新的地址將指向一個毫無意義的內存區域,如果此時目標進程再使用這個函數顯然會出現一個非法操作。

五、替換函數的編寫
前面關鍵的兩步做完了,一個API鉤子基本上也就完成了。不過還有一些相關的東西需要我們研究一番的,包括怎樣做一個替換函數。 下面是一個做替換函數的步驟: 首先,不失一般性,我們先假設有這樣的一個API函數,它的原型如下:

function SomeAPI(param1: Pchar;param2: Integer): DWORD;

接著再建立一個與之有相同參數和返回值的函數類型:

type FuncType= function (param1: Pchar;param2: Integer): DWORD;

然後我們把SomeAPI函數的地址存放在OldAddress指針中。接著我們就可以著手寫替換函數的代碼了:

function DummyFunc(param1: Pchar;param2: Integer): DWORD; begin ......

//做一些調用前的操作

//調用被替換的函數,當然也可以不調用
result := FuncType(OldAddress) (param1 , param2);

//做一些調用後的操作

end;

我們再把這個函數的地址保存到NewAddress中,接著用這地址覆蓋掉原先API的地址。這樣當目標進程調用該API的時候,實際上是調用了我們自己的函數,在其中我們可以做一些操作,然後在調用原先的API函數,結果就像什麼也沒發生過一樣。當然,我們也可以改變輸入參數的值,甚至是屏蔽調這個API函數的調用。

盡管上述方法是可行的,但有一個明顯的不足:這種替換函數的製作方法不具有通用性,只能針對少量的函數。如果只有幾個API要攔截,那麼只需照上述說的重復做幾次就行了。但如果有各種各樣的API要處理,它們的參數個數和類型以及返回值的類型是各不相同的,仍然採用這種方法就太沒效率了。

的確是的,上面給出的只是一個最簡單最容易想到的方法,只是一個替換函數的基本構架。正如我前面所提到的,替換函數的與原先的API函數的參數類型不必相同,一般的我們可以設計一個沒有調用參數也沒有返回值的函數,通過一定的技巧,使它能適應各種各樣的API 函數調用,不過這得要求你對匯編語言有一定的了解。

首先,我們來看一下執行到一個函數體內前的系統堆棧情況(這里函數的調用方式為stdcall),函數的調用參數是按照從右到左的順序壓入堆棧的(堆棧是由高端向低端發展的),同時還壓入了一個函數返回地址。在進入函數之前,ESP正指向返回地址。因此,我們只要從ESP+4開始就可以取得這個函數的調用參數了,每取一個參數遞增4。另外,當從函數中返回時,一般在EAX中存放函數的返回值。

了解了上述知識,我們就可以設計如下的一個比較通用的替換函數,它利用了Delphi的內嵌式匯編語言的特性。

Procere DummyFunc;

asm add esp,4 mov eax,esp//得到第一個參數

mov eax,esp+4//得到第二個參數 ......

//做一些處理,這里要保證esp在這之後恢復原樣

call OldAddress //調用原先的API函數 ......

//做一些其它的事情

end;

當然,這個替換函數還是比較簡單的,你可以在其中調用一些純粹用OP語言寫的函數或過程,去完成一些更復雜的操作(要是都用匯編來完成,那可得把你忙死了),不過應該把這些函數的調用方式統一設置為stdcall方式,這使它們只利用堆棧來傳遞參數,因此你也只需時刻掌握好堆棧的變化情況就行了。如果你直接把上述匯編代碼所對應的機器指令存放在一個位元組數組中,然後把數組的地址當作函數地址來使用,效果是一樣的。

六、後記
做一個API鉤子的確是件不容易的事情,尤其對我這個使用Delphi的人來說,為了解決某個問題,經常在OP、C++和匯編語言的資料中東查西找,在程序調試中還不時的發生一些意想不到的事情,弄的自己是手忙腳亂。不過,好歹總算做出了一個 API鉤子的雛形,還是令自己十分的高興,對計算機系統方面的知識也掌握了不少,受益非淺。當初在寫這篇文章之前,我只是想翻譯一篇從網上Down下來的英文資料(網址為 ,文章名叫"API Hook Revealed",示例源代碼是用VC++寫的,這里不得不佩服老外的水平,文章寫得很有深度,而且每個細節都講的十分詳細)。

『柒』 論文框架怎麼寫

論文一般的組成部分包括以下內容:題名、作者、摘要、關鍵詞、正文、參考文獻和附錄等部分組成。

其中部分組成(例如附錄)可有可無。

論文題目要求准確、簡練、醒目、新穎。

目錄是論文中主要段落的簡表。(短篇論文不必列目錄)

內容提要是文章主要內容的摘錄,要求短、精、完整。

關鍵詞是從論文的題名、提要和正文中選取出來的,是對表述論文的中心內容有實質意義的詞彙。關鍵詞是用作計算機系統標引論文內容特徵的詞語,便於信息系統匯集,以供讀者檢索。每篇論文一般選取3-8個詞彙作為關鍵詞,另起一行,排在「提要」的左下方。

主題詞是經過規范化的詞,在確定主題詞時,要對論文進行主題分析,依照標引和組配規則轉換成主題詞表中的規范詞語。(參見《漢語主題詞表》和《世界漢語主題詞表》)。

論文正文

(1)引言:引言又稱前言、序言和導言,用在論文的開頭。引言一般要概括地寫出作者意圖,說明選題的目的和意義, 並指出論文寫作的范圍。引言要短小精悍、緊扣主題。

(2)論文正文:正文是論文的主體,正文應包括論點、論據、論證過程和結論。主體部分包括以下內容:

a.提出問題-論點;
b.分析問題-論據和論證;
c.解決問題-論證方法與步驟;
d.結論。

參考文獻

一篇論文的參考文獻是將論文在研究和寫作中可參考或引證的主要文獻資料,列於論文的末尾。參考文獻應另起一頁,標注方式按《GB7714-87文後參考文獻著錄規則》進行。

論文裝訂

論文的有關部分全部抄清完了,經過檢查,再沒有什麼問題,把它裝成冊,再加上封面。論文的封面要樸素大方,要寫出論文的題目、學校、科系、指導教師姓名、作者姓名、完成年月日。論文的題目的作者姓名一定要寫在表皮上,不要寫裡面的補頁上。

(7)編譯時怎麼寫框架擴展閱讀

學位論文

學位申請者為申請學位而提出撰寫的學術論文叫學位論文。這種論文是考核申請者能否被授予學位的重要條件。

學位申請者如果能通過規定的課程考試,而論文的審查和答辯合格,那麼就給予學位。如果說學位申請者的課程考試通過了,但論文在答辯時被評為不合格,那麼就不會授予他學位。

有資格申請學位並為申請學位所寫的那篇畢業論文就稱為學位論文,學士學位論文。學士學位論文既是學位論文又是畢業論文。

學術論文

中華人民共和國國家標准VDC 001.81、GB 7713-87號文件給學術論文的定義為:

學術論文是某一學術課題在實驗性、理論性或觀測性上具有新的科學研究成果或創新見解的知識和科學記錄;或是某種已知原理應用於實際中取得新進展的科學總結,用以提供學術會議上宣讀、交流或討論;或在學術刊物上發表;或作其他用途的書面文件。

在社會科學領域,人們通常把表達科研成果的論文稱為學術論文。

學術論文具有四大特點:①學術性 ②科學性 ③創造性 ④理論性

『捌』 論文結構框架怎麼寫

1、題目:題目是論文內容的概括,向讀者說明研究的主要問題。一個好的學術論文題目應當是准確概括論文內容,文字簡練、新穎,范圍明確,便於分類的
2、前言:前言又或者序言、導言、緒論,寫在正文之前,用於說明寫作目的、問題的提出、研究的意義等。
3、正文:正文部分佔全文大部分篇幅。這部分必須對研究內容進行全面的闡述和論證。寫作時以觀點為軸心,貫穿全文用材料說明觀點,使觀點與材料相統一,用觀點去表現主題,使觀點與主題相一致。
4、結論:結論是經反復研究後形成的總體論點。結論應指出所得的結果是否支持假設,或指出哪些問題已經解決了,還有什麼問題尚待進一步探討。
5、參考文獻:這部分包括參考的文章、書目等,附在論文的末尾。
論文結構框架,主要包括題目、摘要、關鍵詞、引言、正文、致謝、參考文獻。

(1)題目的寫作技巧

①題目應簡明、確切,不要太長太籠統;

②題目可省去定冠詞和不定冠詞;

③題目中不應列入非公知公用的符號、代號,以及數學公式、化學 結構式等。

(2)摘要的寫作技巧

①使用短而簡單的句子,表達要准確、簡潔、清楚;

②注意表述的邏輯性,盡量使用指示性的詞語來表達論文的不同部 分(層次);

③不應出現公式、圖表、參考文獻的序號;

④用過去時態敘述自己的工作,用現在時態敘述自己的結論;

⑤盡量用主動語態代替被動語態。

(3)關鍵詞的寫作技巧

①論文所屬科學名稱

②成果名稱

③所用方法名稱

④研究對象

⑤便於文獻檢索利用的名稱

(4)引言的寫作技巧

①採取適當的方式強調研究中最重要的發現或貢獻,讓讀者順著邏 輯的演進閱讀論文。

②解釋或定義專門術語或縮寫詞,以幫助編輯、審稿人和讀者閱讀 稿件。

③適當地使用「I」,「We」或「Our」,以明確地指示自己的工作。

④敘述前人工作的欠缺以強調自己研究的創新時,應慎重且留有餘地。

(5)正文的寫作技巧

①思路清晰,邏輯性強,層次清晰。

②引用已有的方法及結論要標明所出文獻及其編號。

③推導及論證過程簡潔而准確,實驗數據及結論准確無誤。

④力求避免中國式英語和論證思路,多參看外文文獻及相關外文教材。

(6)致謝的寫作技巧

致謝是對整個過程中給予幫助的個人或團體的感謝,內容應盡量具體、用詞要恰當、格式要遵從擬投稿期刊的習慣和相關規定。

(7)參考文獻的寫作技巧

①參考文獻要精選;

②參考文獻的所在期刊、出版的年月及卷期要准確無誤,確保與文中引用參考文獻的一一對應,其書寫格式應參考

閱讀全文

與編譯時怎麼寫框架相關的資料

熱點內容
怎麼降為安卓10 瀏覽:986
javaweb程序設計郭 瀏覽:247
gm聲望命令 瀏覽:484
pdf轉換器電腦版免費 瀏覽:41
解壓歌曲什麼歌最好 瀏覽:151
諾貝爾pdf 瀏覽:967
雲伺服器快速安裝系統原理 瀏覽:788
蘋果騰訊管家如何恢復加密相冊 瀏覽:115
手機軟體反編譯教程 瀏覽:858
sqlserver編程語言 瀏覽:650
gpa國際標准演算法 瀏覽:238
伺服器編程語言排行 瀏覽:947
怎麼下載快跑app 瀏覽:966
小紅書app如何保存視頻 瀏覽:172
如何解開系統加密文件 瀏覽:811
linux切換root命令 瀏覽:283
c編譯之後界面一閃而過怎麼辦 瀏覽:881
怎麼看ic卡是否加密 瀏覽:726
lgplc編程講座 瀏覽:809
cnc手動編程銑圓 瀏覽:724