AI muốn lập trình DLL cho Excel và các loại bằng Delphi thì xem video này nhé! (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

Nguyễn Duy Tuân

Nghị Hách
Thành viên danh dự
Tham gia
13/6/06
Bài viết
4,840
Được thích
10,336
Giới tính
Nam
Nghề nghiệp
Giáo viên, CEO tại Bluesofts
Lần chỉnh sửa cuối:
Mình decompile code hàm TransArr ra và phát hiện chổ chậm nó rồi, cũng như hàm GetSumRange. Cách fix cũng tương tự cách fix cho hàm GetSumRange ở trên.
Nội cái dòng:
Result[i, j] := ssArr[j, i];
Delphi sinh ra code ASM như vầy đây:
1635845780357.png
Chờ mình code cho xem vd
 
Upvote 0
C++:
{$DEFINE TEST}

{$IFDEF TEST}
program Test;
{$APPTYPE CONSOLE}
{$ELSE}
library Test;
{$ENDIF}

uses
  System.Variants;

function FastSumRange(const arr: Variant): Integer; stdcall;
var
  I, J: Integer;
  LB1, LB2, UB1, UB2: Integer;
  PElem: PVariant;
begin
  Result := 0;
  LB1 := VarArrayLowBound(arr, 1);
  UB1 := VarArrayHighBound(arr, 1);
  LB2 := VarArrayLowBound(arr, 2);
  UB2 := VarArrayHighBound(arr, 2);
  PElem := VarArrayLock(arr);
  try
    for I := LB1 to UB1 do
      for J := LB2 to UB2 do
      begin
        Result := Result + PVarData(PElem).VInteger;
        Inc(PElem);
      end;
  finally
    VarArrayUnlock(arr);
  end;
end;

exports
  FastSumRange;

{$IFDEF TEST}
var
  vArr: Variant;
  I, J: Integer;
  Sum: Integer;
begin
  vArr := VarArrayCreate([0, 3, 0, 5], varVariant);
  for I := 0 to 3 do
    for J := 0 to 5 do
      vArr[I, J] := I + J;
  Sum := FastSumRange(vArr);
  WriteLn(Sum);
  ReadLn;
{$ENDIF}
end.
Đây là đoạn code mình code minh họa cho các bạn cách dùng Pointer, truy xuất trực tiếp tới từng memory của từng phần tử trong 1 array của Variant/xxx bất kỳ.
Dựa vào đây các bạn có thể độ chế lại theo yêu cầu của riêng mình.
Bao fast và vé ri vé ri nhanh nhé các bạn, vì tránh được _VarArrayGet và _VarArrayPut.
Bài đã được tự động gộp:

Mã ASM mà Delphi compiler sinh ra, sau khi qua decompiler ngược lại.
Các bạn thấy trong vòng lặp không còn _VarArrayGet và _VarArrayPut.
Chỉ là thao tác công và dịch con trỏ PLem lên sizeof(Variant)
1635905309402.png
Các bạn chú ý dòng này, điểm quan trong:
Result := Result + PVarData(PElem).VInteger;
Nếu các bạn viết:
Result := Result + PElem^
Thì Delphi compiler sẽ sinh mã gọi các hàm internal để convert 1 giá trị Variant to kiểu của biến Result. Ở đây là Integer thì sẽ là _VarAsInteger, và linh tinh nữa, sẽ kéo tốc độ xuống.
Vì Variant là kiểu union TVarData nên mình ép kiểu pointer của PElem từ con trỏ PVariant với PVarData, lấy trực tiếp field của nó luôn.
Nên tránh luôn được các hàm internal mà Delphi compiler chèn vô.
 
Lần chỉnh sửa cuối:
Upvote 0
C++:
{$DEFINE TEST}

{$IFDEF TEST}
program Test;
{$APPTYPE CONSOLE}
{$ELSE}
library Test;
{$ENDIF}

uses
  System.Variants;

function FastSumRange(const arr: Variant): Integer; stdcall;
var
  I, J: Integer;
  LB1, LB2, UB1, UB2: Integer;
  PElem: PVariant;
begin
  Result := 0;
  LB1 := VarArrayLowBound(arr, 1);
  UB1 := VarArrayHighBound(arr, 1);
  LB2 := VarArrayLowBound(arr, 2);
  UB2 := VarArrayHighBound(arr, 2);
  PElem := VarArrayLock(arr);
  try
    for I := LB1 to UB1 do
      for J := LB2 to UB2 do
      begin
        Result := Result + PVarData(PElem).VInteger;
        Inc(PElem);
      end;
  finally
    VarArrayUnlock(arr);
  end;
end;

exports
  FastSumRange;

{$IFDEF TEST}
var
  vArr: Variant;
  I, J: Integer;
  Sum: Integer;
begin
  vArr := VarArrayCreate([0, 3, 0, 5], varVariant);
  for I := 0 to 3 do
    for J := 0 to 5 do
      vArr[I, J] := I + J;
  Sum := FastSumRange(vArr);
  WriteLn(Sum);
  ReadLn;
{$ENDIF}
end.
Đây là đoạn code mình code minh họa cho các bạn cách dùng Pointer, truy xuất trực tiếp tới từng memory của từng phần tử trong 1 array của Variant/xxx bất kỳ.
Dựa vào đây các bạn có thể độ chế lại theo yêu cầu của riêng mình.
Bao fast và vé ri vé ri nhanh nhé các bạn, vì tránh được _VarArrayGet và _VarArrayPut.
Bài đã được tự động gộp:

Mã ASM mà Delphi compiler sinh ra, sau khi qua decompiler ngược lại.
Các bạn thấy trong vòng lặp không còn _VarArrayGet và _VarArrayPut.
Chỉ là thao tác công và dịch con trỏ PLem lên sizeof(Variant)
View attachment 268661
Các bạn chú ý dòng này, điểm quan trong:
Result := Result + PVarData(PElem).VInteger;
Nếu các bạn viết:
Result := Result + PElem^
Thì Delphi compiler sẽ sinh mã gọi các hàm internal để convert 1 giá trị Variant to kiểu của biến Result. Ở đây là Integer thì sẽ là _VarAsInteger, và linh tinh nữa, sẽ kéo tốc độ xuống.
Vì Variant là kiểu union TVarData nên mình ép kiểu pointer của PElem từ con trỏ PVariant với PVarData, lấy trực tiếp field của nó luôn.
Nên tránh luôn được các hàm internal mà Delphi compiler chèn vô.
ÍT ngày nữa rảnh Mạnh áp dụng vào hàm chuyển mảng kia xem tình hình sao mới biết được
Thử 1 cái Array có 10 triệu dòng x 50 cột xem sao có nhanh hơn hay ko ???

Cảm ơn lắm lắm
 
Upvote 0
KKK, tui không có nói lý thuyết suông nhe bạn Mạnh. Nhớ đó. Tui hiểu chắc, sâu cái gì tui mới nói.
Để xem bạn áp dụng vào hàm TransArr của bạn có đúng không ?
Tui đang rảnh, có thể code, sửa ngay hàm đó cho bạn. Bạn code còn bug nhiều lắm.
Nhưng không, để cho bạn tự làm, rồi bạn sẽ hiểu ra nhiều, sâu hơn.
Tốt hơn cho bạn.
 
Upvote 0
Mới xem lại code của mạnh 1 năm trước thì ra có sử dụng rồi .... ít ngày nữa xử lý xong công việc ... quậy bank xác các kiểu ra xem nó ra cái gì .... mạnh cảm ơn

1635907799671.png

Bạn nào iu thích delphi có thể tham khảo thêm link sau
 
Lần chỉnh sửa cuối:
Upvote 0
Mình đo thử trên dữ liệu mình thử tạo là 1 triệu dòng, 5 cột, thì đáng buồn là không nhanh hơn được bao nhiêu.
Delphi 10.4.2 64bit, Excel 2016 64bit.
Chứng tỏ code VBA được compile và execute rất tốt.
Hàm FastSumRange mình sửa lại 1 chút, từ 2 vòng for thành 1 vòng, cải tiến thêm được 1 chút tốc độ (giảm bớt số lệnh, lần CPU nhảy)
Mã:
function FastSumRange(const arr: Variant): Double; stdcall;
var
  I, Count: Integer;
  LB1, LB2, UB1, UB2: Integer;
  PElem: PVariant;
begin
  Result := 0;
  LB1 := VarArrayLowBound(arr, 1);
  UB1 := VarArrayHighBound(arr, 1);
  LB2 := VarArrayLowBound(arr, 2);
  UB2 := VarArrayhighBound(arr, 2);
  Count := (UB1 - LB1 + 1) * (UB2 - LB2 + 1);
  PElem := VarArrayLock(arr);
  try
    for I := 0 to Count - 1 do
    begin
      Result := Result + PVarData(PElem).VDouble;
      Inc(PElem);
    end;
  finally
    VarArrayUnlock(arr);
  end;
end;
Đúng là đua với MS coder không dễ :(
 
Lần chỉnh sửa cuối:
Upvote 0
Mình đo thử trên dữ liệu mình thử tạo là 1 triệu dòng, 5 cột, thì đáng buồn là không nhanh hơn được bao nhiêu.
Delphi 10.4.2 64bit, Excel 2016 64bit.
Chứng tỏ code VBA được compile và execute rất tốt.
Hàm FastSumRange mình sửa lại 1 chút, từ 2 vòng for thành 1 vòng, cải tiến thêm được 1 chút tốc độ (giảm bớt số lần nhảy)
Mã:
function FastSumRange(const arr: Variant): Double; stdcall;
var
  I, Count: Integer;
  LB1, LB2, UB1, UB2: Integer;
  PElem: PVariant;
begin
  Result := 0;
  LB1 := VarArrayLowBound(arr, 1);
  UB1 := VarArrayHighBound(arr, 1);
  LB2 := VarArrayLowBound(arr, 2);
  UB2 := VarArrayhighBound(arr, 2);
  Count := (UB1 - LB1 + 1) * (UB2 - LB2 + 1);
  PElem := VarArrayLock(arr);
  try
    for I := 0 to Count - 1 do
    begin
      Result := Result + PVarData(PElem).VDouble;
      Inc(PElem);
    end;
  finally
    VarArrayUnlock(arr);
  end;
end;
Đúng là đua với MS coder không dễ :(

Anh so sánh với code VBA nào?
 
Upvote 0
Em có test thử hàm anh @ThangCuAnh chia sẻ thì em thử viết dll và COM đều lỗi khi sử dụng VarArrayLowBound(arr, 1);
TH: em gán mảng từ OleVariant và mảng thuần của delphi -> chạy code được nó bị ra giá trị 0, còn bỏ qua các bước sử dụng địa chỉ bộ nhớ thì lại chậm hơn vba.
1635934821940.png

Code delphi
Mã:
function FastSumRange(arr: Variant): Integer; stdcall;
var
  I, Count: Integer;
  LB1, LB2, UB1, UB2: Integer;
  PElem: PVariant;
begin
  Result := 0;
  LB1 := VarArrayLowBound(arr, 1);
  UB1 := VarArrayHighBound(arr, 1);
  LB2 := VarArrayLowBound(arr, 2);
  UB2 := VarArrayhighBound(arr, 2);
  Count := (UB1 - LB1 + 1) * (UB2 - LB2 + 1);
  PElem := VarArrayLock(arr);
  try
    for I := 0 to Count - 1 do
    begin
      Result := Result + PVarData(PElem).VInteger;
      Inc(PElem);
    end;
  finally
    VarArrayUnlock(arr);
  end;
end;

exports
  FastSumRange;

[/ICODE]
VBA
Mã:
#If VBA7 Then
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Private Declare PtrSafe Function FastSumRange Lib "BnAddin.dll" (ByVal Rng As Variant) As Long
#Else
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Function FastSumRange Lib "BnAddin.dll" (ByVal Rng As Variant) As Long
#End If

Sub testFastSumRange2()
    Dim t As Double
    t = GetTickCount
    'Dim Com As New BnAddIn.CoBnJson
    Range("C1").Value2 = FastSumRange(Range("A1:A1000000"))
    Range("D1").Value2 = GetTickCount - t
End Sub
 
Upvote 0
Em có test thử hàm anh @ThangCuAnh chia sẻ thì em thử viết dll và COM đều lỗi khi sử dụng VarArrayLowBound(arr, 1);
TH: em gán mảng từ OleVariant và mảng thuần của delphi -> chạy code được nó bị ra giá trị 0, còn bỏ qua các bước sử dụng địa chỉ bộ nhớ thì lại chậm hơn vba.
View attachment 268704

Code delphi
Mã:
function FastSumRange(arr: Variant): Integer; stdcall;
var
  I, Count: Integer;
  LB1, LB2, UB1, UB2: Integer;
  PElem: PVariant;
begin
  Result := 0;
  LB1 := VarArrayLowBound(arr, 1);
  UB1 := VarArrayHighBound(arr, 1);
  LB2 := VarArrayLowBound(arr, 2);
  UB2 := VarArrayhighBound(arr, 2);
  Count := (UB1 - LB1 + 1) * (UB2 - LB2 + 1);
  PElem := VarArrayLock(arr);
  try
    for I := 0 to Count - 1 do
    begin
      Result := Result + PVarData(PElem).VInteger;
      Inc(PElem);
    end;
  finally
    VarArrayUnlock(arr);
  end;
end;

exports
  FastSumRange;

[/ICODE]
VBA
Mã:
#If VBA7 Then
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Private Declare PtrSafe Function FastSumRange Lib "BnAddin.dll" (ByVal Rng As Variant) As Long
#Else
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Function FastSumRange Lib "BnAddin.dll" (ByVal Rng As Variant) As Long
#End If

Sub testFastSumRange2()
    Dim t As Double
    t = GetTickCount
    'Dim Com As New BnAddIn.CoBnJson
    Range("C1").Value2 = FastSumRange(Range("A1:A1000000"))
    Range("D1").Value2 = GetTickCount - t
End Sub

Lỗi sơ đẳng quá. Em đang truyền vào hàm Fast trong VBA là object, trong khi nó đang cần phải kaf array/variant.
 
Upvote 0
Code này nè Tuân
Mã:
Option Explicit

Declare PtrSafe Function FastSumRange Lib "Test.dll" (ByRef arr As Variant) As Double
Declare PtrSafe Function LoadLibraryA Lib "kernel32.dll" (ByVal DllName As String) As LongPtr

Sub VBACode()
    Dim lr As Long, arr As Variant, tong As Double, i As Long, j As Long
    Dim sStart As Single, sEnd As Single
    sStart = Timer
    With Sheet1
        lr = .Range("A1000000").End(3).Row
        arr = .Range("A1:K" & lr).Value
    End With
    For i = LBound(arr) To UBound(arr)
        For j = LBound(arr, 2) To UBound(arr, 2)
            tong = tong + arr(i, j)
        Next j
    Next i
    sEnd = Timer
    MsgBox tong, , sEnd - sStart
End Sub

Sub DelphiCode()
    Dim sStart As Single, sEnd As Single, lr As Long, arr As Variant, tong As Double
    Dim hDll As LongPtr
    hDll = LoadLibraryA("test.dll")
    sStart = Timer
    With Sheet1
        lr = .Range("A1000000").End(3).Row
        arr = .Range("A1:K" & lr).Value
    End With
    tong = FastSumRange(arr)
    sEnd = Timer
    MsgBox tong, , sEnd - sStart
End Sub
Bạn Bảo Ninh thiếu keyword const trong hàm FastSumRange nhé. Không tự ý bỏ const đi được đâu.
 
Upvote 0
Code này nè Tuân
Mã:
Option Explicit

Declare PtrSafe Function FastSumRange Lib "Test.dll" (ByRef arr As Variant) As Double
Declare PtrSafe Function LoadLibraryA Lib "kernel32.dll" (ByVal DllName As String) As LongPtr

Sub VBACode()
    Dim lr As Long, arr As Variant, tong As Double, i As Long, j As Long
    Dim sStart As Single, sEnd As Single
    sStart = Timer
    With Sheet1
        lr = .Range("A1000000").End(3).Row
        arr = .Range("A1:K" & lr).Value
    End With
    For i = LBound(arr) To UBound(arr)
        For j = LBound(arr, 2) To UBound(arr, 2)
            tong = tong + arr(i, j)
        Next j
    Next i
    sEnd = Timer
    MsgBox tong, , sEnd - sStart
End Sub

Sub DelphiCode()
    Dim sStart As Single, sEnd As Single, lr As Long, arr As Variant, tong As Double
    Dim hDll As LongPtr
    hDll = LoadLibraryA("test.dll")
    sStart = Timer
    With Sheet1
        lr = .Range("A1000000").End(3).Row
        arr = .Range("A1:K" & lr).Value
    End With
    tong = FastSumRange(arr)
    sEnd = Timer
    MsgBox tong, , sEnd - sStart
End Sub
Bạn Bảo Ninh thiếu keyword const trong hàm FastSumRange nhé. Không tự ý bỏ const đi được đâu.
Mạnh thử Call nó từ COM thấy lần đầu chạy có vẻ chậm hơn tí teo .... lần 2 to n là o có nghĩa nhanh hơn lần đầu
đoán là COM mất cái khúc Load 1 tí

1635942472013.png

Mã:
Sub DelphiCode()
    Dim sStart As Single, sEnd As Single
    Dim lr As Long, arr As Variant, tong As Double
 
    Dim aSum As New MyLibrary.VBLib
    sStart = Timer
    With Sheet1
        lr = .Range("A1000000").End(3).Row
        arr = .Range("A1:K" & lr).Value
    End With
    tong = aSum.SumRange(arr)
    sEnd = Timer
    MsgBox tong, , sEnd - sStart
End Sub

Mạnh cảm ơn ... ngày mát trời có thêm 1 Hàm vào mục tiện Ích COM class

1635942938305.png

Thử gõ trên Cells thấy chạy cũng thế
Mã:
Function FastSumRange(ByVal DataArray As Range) As Double
    Dim Arr As Variant
    Dim aSum As New MyLibrary.VBLib
    Arr = DataArray.Value
    FastSumRange = aSum.SumRange(Arr)
End Function
 
Lần chỉnh sửa cuối:
Upvote 0
Ban ngày em hơi bận nên chưa có time test, cảm ơn các anh đã chỉ dẫn, em đã test sửa lại theo hướng dẫn của anh @Nguyễn Duy Tuân và anh @ThangCuAnh :
Phạm vi: 5,242,880 cells
TH1: Code dll theo cách của anh @ThangCuAnh -> 484 ms
TH2: truyền trực tiếp Arr xử lý tính toán Arr trong delphi -> 672 ms
TH3: truyền vào range -> trong delphi gán từ rng mới gán sang mảng 532ms
TH4: VBA lỗi OverFlow
TH5: Code COM theo cách của anh @ThangCuAnh -> 250ms
TH6: Code hàm SUM Excel truyền vào Range (không phải array) -> 31 ms
TH7: Code hàm SUM Excel truyền vào Arr được lấy từ range -> 293 ms
Em thấy hiệu suất cách viết của các anh chỉ thì hàm xử lý mảng đã xử lý ở mức độ rất tốt, nếu hàm SUM của excel không phải là tính theo tọa độ mà tính theo Array thì tốc độ gần như tương đương các anh chỉ rồi!

Em muốn hỏi chút các anh là nếu ta sử dụng PElem := VarArrayLock(Rng); -> returns a pointer to the data, vậy nếu em muốn tìm tọa độ chính xác PElem tương tự như PElem[I,J] thì có cách nào không ạ

1635960375900.png
Mã:
Option Explicit
Public comBnFunction As New BnAddIn.coBNSQLFunction
Public comBnJson As New BnAddIn.CoBnJson

Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Declare Function FastSumRange_TH1 Lib "BnAddin.dll" (ByRef Arr As Variant) As Double
Declare Function FastSumRange_TH2 Lib "BnAddin.dll" (ByRef Arr As Variant) As Double
Declare Function FastSumRange_TH3 Lib "BnAddin.dll" (ByRef Arr As Variant) As Double

Sub DelphiTestSum_Excel()
    Dim t As Double
    t = GetTickCount
    Dim Arr
    Arr = Range("A1:E1048576").Value2
    Range("G6").Value2 = WorksheetFunction.Sum(Arr)
    Range("H6").Value2 = GetTickCount - t
End Sub

Sub DelphiTestSumCOM()
    Dim t As Double
    t = GetTickCount
    Dim Arr As Variant
    Arr = Range("A1:E1048576").Value2
    Range("G5").Value2 = comBnJson.BN_FastSumRange(Arr)
    Range("H5").Value2 = GetTickCount - t
End Sub

Sub DelphiTestSum_TH1()
    Dim t As Double
    t = GetTickCount
    'Dim Com As New BnAddIn.CoBnJson
    Dim Arr As Variant
    Arr = Range("A1:E1048576")
    Range("G1").Value2 = FastSumRange_TH1(Arr)
    Range("H1").Value2 = GetTickCount - t
End Sub

Sub DelphiTestSum_TH2()
    Dim t As Double
    t = GetTickCount
    Dim Arr As Variant
    Arr = Range("A1:E1048576")
   
    Range("G2").Value2 = FastSumRange_TH2(Arr)
    Range("H2").Value2 = GetTickCount - t
End Sub

Sub DelphiTestSum_TH3()
    Dim t As Double
    t = GetTickCount
    Range("G3").Value2 = FastSumRange_TH3(Range("A1:E1048576"))
    Range("H3").Value2 = GetTickCount - t
End Sub

Sub TestFastSumRangeVBA()
    Dim t As Double
    t = GetTickCount
    Dim Arr
    Arr = Range("A1:A1048576").Value2
    Dim i, j As Long
    Dim LB1, LB2, UB1, UB2, KQ As Long
    LB1 = LBound(Arr, 1)
    UB1 = UBound(Arr, 1)
    LB2 = LBound(Arr, 2)
    UB2 = UBound(Arr, 2)
    For i = LB1 To UB1
        For j = LB2 To UB2
            KQ = KQ + Arr(i, j)
        Next j
    Next i
    Range("G4").Value2 = KQ
    Range("H4").Value2 = GetTickCount - t
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Bản thân code FastSumRange đã nhanh hết mức có thể rồi, chỉ còn cách dùng multithread/parallel. Nhưng overhead cho sinh thread cũng rất lớn.
Nên chỉ dùng cho các vùng data thật lớn.
Overhead ở đây là do VBA load dll và DllFunctionCall gọi hàm FastSumRange rất lớn, tốn nhiều time.
Nên ở VBA code của mình, mình đã force nó LoadLibraryA trước rồi mới bắt đầu timer.
Bạn Mạnh nên chú ý điểm này, prototype của hàm TransArr là return 1 variant array.
Tức là VBA code sẽ sinh code để copy cái array dll bạn trả về vào biến của VBA. Sẽ take time rất lớn ở đây, nên đo không chính xác.
Mã:
Dim arr1 as Variant, arr2 as Variant
.....
arr1 = TransArr(arr2)
Sẽ take time rất lớn ở phép gán arr1 =
Bạn nên sữa prototype của TransArr lại
VD: function TransArr(const arrSrc: Variant; var arrDst: Variant): Boolean;
Trans thẳng từ arrSrc vào arrDst luôn
 
Lần chỉnh sửa cuối:
Upvote 0
Bản thân code FastSumRange đã nhanh hết mức có thể rồi, chỉ còn cách dùng multithread/parallel. Nhưng overhead cho sinh thread cũng rất lớn.
Nên chỉ dùng cho các vùng data thật lớn.
Overhead ở đây là do VBA load dll và DllFunctionCall gọi hàm FastSumRange rất lớn, tốn nhiều time.
Nên ở VBA code của mình, mình đã force nó LoadLibraryA trước rồi mới bắt đầu timer.
Bạn Mạnh nên chú ý điểm này, prototype của hàm TransArr là return 1 variant array.
Tức là VBA code sẽ sinh code để copy cái array dll bạn trả về vào biến của VBA. Sẽ take time rất lớn ở đây, nên đo không chính xác.
Mã:
Dim arr1 as Variant, arr2 as Variant
.....
arr1 = TransArr(arr2)
Sẽ take time rất lớn ở phép gán arr1 =
Bạn nên sữa prototype của TransArr lại
VD: function TransArr(const arrSrc: Variant; var arrDst: Variant): Boolean;
Trans thẳng từ arrSrc vào arrDst luôn
Em muốn hỏi chút các anh là nếu ta sử dụng PElem := VarArrayLock(Rng); -> returns a pointer to the data, vậy nếu em muốn tìm tọa độ chính xác PElem tương tự như PElem[I,J] thì có cách nào không ạ
 
Upvote 0
Bản thân code FastSumRange đã nhanh hết mức có thể rồi, chỉ còn cách dùng multithread/parallel. Nhưng overhead cho sinh thread cũng rất lớn.
Nên chỉ dùng cho các vùng data thật lớn.
Overhead ở đây là do VBA load dll và DllFunctionCall gọi hàm FastSumRange rất lớn, tốn nhiều time.
Nên ở VBA code của mình, mình đã force nó LoadLibraryA trước rồi mới bắt đầu timer.
Bạn Mạnh nên chú ý điểm này, prototype của hàm TransArr là return 1 variant array.
Tức là VBA code sẽ sinh code để copy cái array dll bạn trả về vào biến của VBA. Sẽ take time rất lớn ở đây, nên đo không chính xác.
Mã:
Dim arr1 as Variant, arr2 as Variant
.....
arr1 = TransArr(arr2)
Sẽ take time rất lớn ở phép gán arr1 =
Bạn nên sữa prototype của TransArr lại
VD: function TransArr(const arrSrc: Variant; var arrDst: Variant): Boolean;
Trans thẳng từ arrSrc vào arrDst luôn
Rảnh code dùm cho mạnh cái hàm kia TransArr ... loay hoay nguyên tối qua tới sáng nay chưa ra
Code đó mức độ rất khó rồi ... vượt ngoài tầm hiểu + xử lý của Mạnh
Xin cảm ơn
 
Upvote 0
Em muốn hỏi chút các anh là nếu ta sử dụng PElem := VarArrayLock(Rng); -> returns a pointer to the data, vậy nếu em muốn tìm tọa độ chính xác PElem tương tự như PElem[I,J] thì có cách nào không ạ
Seek con trỏ đó em. Mãng 2 chiều thôi. Hàng và cột.
Vd: mãng arr[A..B, C..D] of Variant;
PElem: Pointer;
PElem := @arr[A, C]; // tới đầu vùng nhớ của arr, phần tử đầu tiên[A, C]
Thì @arr[I, J] = @arr[A, C] + ((D - C + 1) * I + J) * sizeof(Variant)
Mạnh post file Excel và code VBA lên để mình code hàm TransArr thử xem tốc độ Delphi so với VBA lần này ra sao !!!???
 
Lần chỉnh sửa cuối:
Upvote 0
Seek con trỏ đó em. Mãng 2 chiều thôi. Hàng và cột.
Vd: mãng arr[A..B, C..D] of Variant;
PElem: Pointer;
PElem := @arr[A, C]; // tới đầu vùng nhớ của arr, phần tử đầu tiên[A, C]
Thì @arr[I, J] = @arr[A, C] + ((D - C + 1) * I + J) * sizeof(Variant)
Mạnh post file Excel và code VBA lên để mình code hàm TransArr thử xem tốc độ Delphi so với VBA lần này ra sao !!!???
Cách sau mạnh đang dùng là cảm thấy nhanh nhất
Mã:
Private Sub Transpose_Data()
    Dim tmpArray() As Variant
    Dim tmpArray2() As Variant
    Dim Cnn As Object, Rs As Object
    Dim strCon As String
    Dim ExcelPath As String
    Dim srtQry As String
    Rem ========== Khai bao mo ket noi
    Set Cnn = CreateObject("ADODB.Connection")
    Set Rs = CreateObject("ADODB.Recordset")
    ExcelPath = ThisWorkbook.Path & "\Data.xlsb"
    strCon = ("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & ExcelPath _
            & ";Extended Properties=""Excel 12.0 Xml;HDR=Yes;"";")
    Rem ========== Tuy chon Lay du lieu SQL
    Rem srtQry = "Select *" & "From [" & Data_Nhap$ & "]"
    srtQry = "SELECT * FROM [Data_Nhap$]"
    Cnn.Open strCon
    Set Rs = Cnn.Execute(srtQry)
    tmpArray = Rs.GetRows
    Cnn.Close
    Rem ========== Thuc hien chuyen mang 2dArray len Sheet
    Call Transpose_Array(tmpArray, tmpArray2)   ''Chuyen Mang tmpArray Sang tmpArray2
    With Sheets("ADO").Range("A2")
        .Resize(UBound(tmpArray2, 1), UBound(tmpArray2, 2)).ClearContents
        .Resize(UBound(tmpArray2, 1), UBound(tmpArray2, 2)).Value = tmpArray2
    End With
End Sub
Private Sub Transpose_Array(ByRef InputArr() As Variant, ByRef ReturnArray() As Variant)
    Rem Khai bao Dim tmpArray(), tmpArray2() As Variant     ''Tang Toc 50%
    Rem Su Dung Call Transpose_Array (tmpArray, tmpArray2)  ''Chuyen Mang tmpArray Sang tmpArray2
    Dim RowNdx As Long, ColNdx As Long
    Dim LB1 As Long, LB2 As Long
    Dim UB1 As Long, UB2 As Long
    LB1 = LBound(InputArr, 1)
    LB2 = LBound(InputArr, 2)
    UB1 = UBound(InputArr, 1)
    UB2 = UBound(InputArr, 2)
    ReDim ReturnArray(LB2 To UB2, LB1 To UB1)

    For RowNdx = LB2 To UB2
    For ColNdx = LB1 To UB1
        ReturnArray(RowNdx, ColNdx) = InputArr(ColNdx, RowNdx)
    Next ColNdx, RowNdx ''Viet gon lai Bo Next
    Erase InputArr
End Sub
 
Upvote 0
Data, file đâu trời ?
 
Upvote 0
Tôi code DLL trong Delphi theo hướng giải quyết khác với anh "ThangCuAnh" và kết quả DLL tôi viết trong Delphi nhanh hơn VBA. Các bạn thử nghiệm trên máy tính của các bạn rồi cho kết quả nhé. Sự so sánh có thể khác nhau giữa các Office 32 hay 64-bit.

"tuanfastcode.dll" là thư viện lập trình bằng Delphi, xuất các hàm APIs gồm
1. CopyArray: Copy hai mảng 2D với nhau
2. TransArray: Đảo chiều mảng 2D
3. FastSumArray: Tổng trong mảng 2D

Để chạy các hàm này cần copy thư viện như sau:
+ Nếu Windows 64 bit
Copy x86\tuanfastcode.dll vào C:\Windows\SysWow64\
Copy x64\tuanfastcode.dll vào C:\Windows\System32\

+ Nếu Windows 32 bit
Copy x86\tuanfastcode.dll vào C:\Windows\System32\

So sánh hàm viết trong DLL này với cách viết tương tự trong VBA.
Chạy mở file "TestTuanFastCode.xlsm", vào VBA chạy code để so sánh.

Tôi đã test với Windows 64-bit, Office365 32-bit
Tốc độ các hàm tôi viết trong Delphi đều nhanh hơn VBA, trong đó hàm FastSumArray nhanh gấp 4 lần. Các bạn có thể test để xem kết quả ra sao.


FastCode.png

Download
 
Upvote 0
Upvote 0
Tôi code DLL trong Delphi theo hướng giải quyết khác với anh "ThangCuAnh" và kết quả DLL tôi viết trong Delphi nhanh hơn VBA. Các bạn thử nghiệm trên máy tính của các bạn rồi cho kết quả nhé. Sự so sánh có thể khác nhau giữa các Office 32 hay 64-bit.

"tuanfastcode.dll" là thư viện lập trình bằng Delphi, xuất các hàm APIs gồm
1. CopyArray: Copy hai mảng 2D với nhau
2. TransArray: Đảo chiều mảng 2D
3. FastSumArray: Tổng trong mảng 2D

Để chạy các hàm này cần copy thư viện như sau:
+ Nếu Windows 64 bit
Copy x86\tuanfastcode.dll vào C:\Windows\SysWow64\
Copy x64\tuanfastcode.dll vào C:\Windows\System32\

+ Nếu Windows 32 bit
Copy x86\tuanfastcode.dll vào C:\Windows\System32\

So sánh hàm viết trong DLL này với cách viết tương tự trong VBA.
Chạy mở file "TestTuanFastCode.xlsm", vào VBA chạy code để so sánh.

Tôi đã test với Windows 64-bit, Office365 32-bit
Tốc độ các hàm tôi viết trong Delphi đều nhanh hơn VBA, trong đó hàm FastSumArray nhanh gấp 4 lần. Các bạn có thể test để xem kết quả ra sao.



Download
Máy Mạnh Office 365_x64 + Window10x64

1636020727406.png
 
Upvote 0
Tôi mới Test thử chạy code các kiểu ... API hay COM viết = Delphi tốc độc chạy như nhau cùng một cách viết

Có 1 điểm chung nữa là là lần đầu tiên chạy code thời gian gần gấp đôi những lần sau ( đoán là load API hay COM ) nên nó tăng cái thời gian xử lý khi load hàm lần đầu lên gần gấp đôi thời gian những lần chạy code sau đó

lần ở đây hiểu là khi mở Files lên chạy code tính là lần số 1
 
Upvote 0
Đúng là dữ liệu càng lớn thì mới thấy sự chênh lệch rõ về code VBA và Delphi.
Test trên máy mình với file của Tuân và Dll của Tuân và mình, hàm FastSumRange của mình giờ mới thấy nó chạy nhanh khiếp, có khi 0 ms luôn. Nhanh gấp 23 lần code VBA
Của Tuân vẫn bị chậm do Tuân call direct hàm API SafeArrayGetElement và SafeArrayPutElement trong vòng lặp.
Mỗi lần call vậy nó phải SafeArrayLock và Unlock trong hàm, check kiểu PSafeArray và các phần tử. (Rờ em oleaut32.dll 2 hàm này thấy khiếp. Nên né :D )
Tuy nhiên, cái gì cũng có cái giá của nó. Được này mất kia. Theo document của MS (MSDN) thì SafeArrayGet/PutElement sẽ copy đúng giá trị variant trong trường hợp phần tử variant là string, object....
1636258644041.png
1636259233056.png
 
Lần chỉnh sửa cuối:
Upvote 0
Hì hì, nhậu 3 ngày liên tục đù luôn.
Sẵn đây xin phép bạn Tuân cho mình post source decompiler ra C của hàm TransArray của Tuân để các bạn khác có thể đọc hiểu và viết lại bằng Delphi. Hàm CopyArray cũng tựa tựa vậy, chỉ khác thứ tự index I và J thôi. Tuân dùng trực tiếp các hàm API SafeArray, không dùng các hàm của Delphi RTL.
Vòng for của Delphi compiler nó sinh mã giống vòng do while của C/C++ thôi. Code Delphi là vòng for I := to for J := to đấy.
Cũng là bài tập thêm cho các bạn.
Kiểm tra phần tử VARIANT trong mãng, nếu nó là dạng số thuần thì dùng SafeArrayLock/Unlock để truy xuất trực tiếp tới memory của phần tử.
Nếu là kiểu string, object, IUnknown, IDispatch... thì dùng SafeArrayGet/PutElement.
Code sao cho nhanh nhất có thể.
1636260996746.png
 
Lần chỉnh sửa cuối:
Upvote 0
Các bạn mới code sẽ hay gặp 1 lỗi này, mình gợi ý các bạn tránh luôn. Lỗi này sẽ kéo tộc độ xuống gần 50%.
Vd:
Mã:
var
   flag: Boolean;
....
begin
....
  for I := A to B do
    for J := C to D do
      if flag then
        doA
      else
        doB
....
Không nên viết như vậy, CPU sẽ sinh mã nhảy, instruction cache misses rất nhiều trong vòng for. Nên đưa ra ngoài như sau:
Mã:
....
if flag then
  for I : = A to B do
    for J : = C to D do
       doA
else
  for I : = A to B do
    for J : = C to D do
       doB
....
Nhìn thì thấy dài hơn, compiler sinh code nhiều hơn nhưng tốc độ lại nhanh hơn. Lệnh CPU check flag chỉ có 1, sau đó là vào vòng for chạy 1 lèo luôn.
Chân chọng, bét xì ga.
 
Upvote 0
Hì hì, nhậu 3 ngày liên tục đù luôn.
Sẵn đây xin phép bạn Tuân cho mình post source decompiler ra C của hàm TransArray của Tuân để các bạn khác có thể đọc hiểu và viết lại bằng Delphi. Hàm CopyArray cũng tựa tựa vậy, chỉ khác thứ tự index I và J thôi. Tuân dùng trực tiếp các hàm API SafeArray, không dùng các hàm của Delphi RTL.
Vòng for của Delphi compiler nó sinh mã giống vòng do while của C/C++ thôi. Code Delphi là vòng for I := to for J := to đấy.
Cũng là bài tập thêm cho các bạn.
Kiểm tra phần tử VARIANT trong mãng, nếu nó là dạng số thuần thì dùng SafeArrayLock/Unlock để truy xuất trực tiếp tới memory của phần tử.
Nếu là kiểu string, object, IUnknown, IDispatch... thì dùng SafeArrayGet/PutElement.
Code sao cho nhanh nhất có thể.
View attachment 268869

Vâng. Bài trước em không để source để các bạn chịu tìm hiểu, nếu không làm được kiến thức đó thì em cũng share source chứ mấy cái này chỉ là tip cho cho học tập.
 
Upvote 0
Hì hì, nhậu 3 ngày liên tục đù luôn.
Sẵn đây xin phép bạn Tuân cho mình post source decompiler ra C của hàm TransArray của Tuân để các bạn khác có thể đọc hiểu và viết lại bằng Delphi. Hàm CopyArray cũng tựa tựa vậy, chỉ khác thứ tự index I và J thôi. Tuân dùng trực tiếp các hàm API SafeArray, không dùng các hàm của Delphi RTL.
Vòng for của Delphi compiler nó sinh mã giống vòng do while của C/C++ thôi. Code Delphi là vòng for I := to for J := to đấy.
Cũng là bài tập thêm cho các bạn.
Kiểm tra phần tử VARIANT trong mãng, nếu nó là dạng số thuần thì dùng SafeArrayLock/Unlock để truy xuất trực tiếp tới memory của phần tử.
Nếu là kiểu string, object, IUnknown, IDispatch... thì dùng SafeArrayGet/PutElement.
Code sao cho nhanh nhất có thể.
View attachment 268869
Úp code Delphi cho mạnh + các bạn khác iu thích Delphi học với .... nhìn vậy cứ như mộng du :p
 
Upvote 0
Mình chỉ biết nói dóc, lý thuyết thôi bạn Mạnh.
Code C đó, bạn port qua Delphi tương đương 1 - 1 à.
Mình kg code được, sorry.
Bạn Mạnh tự code đi, khi nào bị bug hay chạy chậm thì quăng code lên, mình fix và optimize cho.
Chứ còn nói mình tự code thì lười lắm :D
 
Lần chỉnh sửa cuối:
Upvote 0
Bữa giờ mình rờ em các hàm API về SafeArray của oleaut32.dll của MS.
Nên chưa vội viết các hàm CopyArray và TransArr bằng Delphi.
Code của Tuân là đúng, an toàn nhất. Nhưng bị memory leak/sai RefCount ở phần tử Variant cuối cùng trong array.
Chú ý field fFeatures trong struct SAFEARRAY.
Nếu nó là IUnknown, IDispatch, IRecordInfo, BSTR thì sụm bà chè hết.
SafeArray API, MS coder đã cung cấp sẵn hàm SafeArrayCopy, copy deep, nên dùng, kg nên phát minh lại bánh xe.
 
Upvote 0
Bữa giờ mình rờ em các hàm API về SafeArray của oleaut32.dll của MS.
Nên chưa vội viết các hàm CopyArray và TransArr bằng Delphi.
Code của Tuân là đúng, an toàn nhất. Nhưng bị memory leak/sai RefCount ở phần tử Variant cuối cùng trong array.
Chú ý field fFeatures trong struct SAFEARRAY.
Nếu nó là IUnknown, IDispatch, IRecordInfo, BSTR thì sụm bà chè hết.
SafeArray API, MS coder đã cung cấp sẵn hàm SafeArrayCopy, copy deep, nên dùng, kg nên phát minh lại bánh xe.

Hàm CopyArrray em cố tình viết vậy để so sánh tốc độ thực hiện với toán tử gán giá trị kiểu Vảiant giữa Delphi và VBA. Tại sao phần tử cuối cùng của mảng lại leak memory nhỉ? Điều này e thấy vô lý thật.
 
Upvote 0
Do hàm VariantCopy mà SafeArrayGet/PutElement gọi bên trong ruột nó em.
Em đọc lại help của hàm VariantCopy đi. Free dest, copy source qua dest.... Cứ vậy thì tới thằng cuối cùng kg ai free nó
 
Upvote 0
Sau 1 thời gian RE các hàm SAFEARRAY API trong oleaut32.dll, mình viết tặng các bạn các hàm Sum, Copy và Trans array. Dùng trực tiếp API và đã test, cải tiến tối đa tốc độ.
Các bạn có thể dùng cho thư viện riêng của mình. Beerware lái sần hết, không lo.
Các bạn test speed thử giúp mình nhé.
Hàm FastTransArrayByCopy thua tốc độ hàm FastTransArrayDirect 1 chút, nhưng dùng an toàn hơn. Nên mình không đưa code hàm FastTransArrayDirect vào, vì sau này, các Windows ver khác, ai biết MS có đổi internal struct của SAFEARRAY nữa hay không.
Cho nên chắc ăn nhất là cứ dùng các API có sẵn của họ.
Bạn nào cần code hàm FastTransArrayDirect thì contact mình.
Các hàm Sum, Copy và Trans array đều an toàn, không memory leak/object leak, chạy đúng với mọi kiểu dữ liệu của từng phần tử, mọi Variant , string (BSTR), các object IUnknown, IDispatch....

Mã:
// VBArray.dll: Library for some VB/VBA array functions
// Coded by: HTC (TQN/TCA)
// Compile:
//    Release: dcc32.exe/dcc64.exe -B -$C- VBArray.dpr
//    Debug: dcc32.exe/dcc64.exe -B -V VBArray.dpr
// Beeware licenses ;)
//

{$DEFINE TEST}

{$IFDEF TEST}

program VBArray;

{$APPTYPE CONSOLE}

{$ELSE}

library VBArray;

{$IFDEF WIN64}
  {$LIBSUFFIX '64'}
{$ELSE}
  {$LIBSUFFIX '32'}
{$ENDIF}

{$ENDIF}

uses
  Winapi.Windows, System.Variants, System.VarUtils;

const
  // Not declared in Delphi until Sydney 10.4.2 verion, declared in wtypes.h
  FADF_RECORD = $20;
  FADF_HAVEIID = $40;
  FADF_HAVEVARTYPE = $80;
  VT_INT = 22;  // signed machine int
  VT_UINT = 23; // unsigned machine int

// We can not sum array of IUnknown, IDispatch, IRecordInfo
// Only support sum array of Variant, BSTR (OleStr), numeric elements...
//
function FastSumArray(const vArr: Variant; var dblSum: Double): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pData: Pointer;
  pva: PVarArray; // TVarArray in Delphi = SAFEARRAY in C/C++ Windows SDK
  dblOut: Double;
  varSum: TVarData;
  I, LTotalElement: NativeUInt;
begin
  dblSum := 0;

  if not VarIsArray(vArr) then
    Exit(VAR_INVALIDARG);

  pva := VarArrayAsPSafeArray(vArr);
  Assert(pva <> nil);

  if (pva.Flags and FADF_RECORD <> 0) or (pva.Flags and FADF_HAVEIID <> 0) then
    Exit(VAR_NOTIMPL);

  if (pva.DimCount < 1) or (pva.Flags and FADF_HAVEVARTYPE = 0) then
    Exit(VAR_TYPEMISMATCH);

  vt := PWord(PByte(pva) - 4)^; // vt stored at offset -4
  if ((pva.Flags and ARR_OLESTR <> 0) and (varOleStr <> vt)) or
    ((pva.Flags and ARR_VARIANT <> 0) and (varVariant <> vt)) then
  begin
    Assert(False, 'Bad VarType');
    Exit(VAR_UNEXPECTED);
  end;

  // we not support VT_DECIMAL (16 byte)
  if not vt in [varSmallInt, varInteger, varSingle, varDouble, varCurrency, varDate, varOleStr,
                varBoolean, varVariant, varShortInt, varByte, varWord, varUInt32, varInt64,
                varUInt64, VT_INT, VT_UINT] then
    Exit(VAR_BADVARTYPE);

  LTotalElement := 1;
  for I := 0 to pva.DimCount - 1 do
    LTotalElement := LTotalElement * pva.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pva, pData);
  if Failed(hr) then
    Exit(hr);

  try
    // Unroll the case inside the loop. Avoid excute many cmp/jnz ASM instructions inside the loop
    // Delphi compiler not create swich/jump table as other C/C++ compilers
    // Avoid access pva.ElementSize in the loop: Inc(PByte(pData), pva.ElementSize), uses constant direct
    //
    case vt of
      varSmallInt, varBoolean:  // 2 bytes, signed, VARIANT_BOOL = WordBool, -1 = TRUE, 0 = FALSE
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSmallInt(pData)^;
          Inc(PByte(pData), 2);
        end;

      varInteger, VT_INT: // 4 bytes, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInteger(pData)^;
          Inc(PByte(pData), 4);
        end;

      varSingle:  // 4 bytes, float
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSingle(pData)^;
          Inc(PByte(pData), 4);
        end;

      varDouble, varDate:  // 8 bytes, DATETIME = Double
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PDouble(pData)^;
          Inc(PByte(pData), 8);
        end;

      varCurrency:  // 8 bytes
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PCurrency(pData)^;
          Inc(PByte(pData), 8);
        end;

      varOleStr:  // SizeOf(Pointer)
        for I := 1 to LTotalElement do
        begin
          hr := VarR8FromStr(PWideChar(pData^), VAR_LOCALE_USER_DEFAULT, 0, dblOut);
          Inc(PByte(pData), SizeOf(Pointer));
          if Succeeded(hr) then
            dblSum := dblSum + dblOut;
        end;

      varVariant: // SizeOf(Variant)
        begin
          VariantInit(varSum);
          varSum.VType := varDouble;
          varSum.VDouble := 0;

          for I := 1 to LTotalElement do
          begin
            VarAdd(varSum, PVarData(pData)^, varSum); // ignore HRESULT return and failed elements
            Inc(PByte(pData), SizeOf(Variant));
          end;

          if varSum.VType <> varDouble then
            VariantChangeType(varSum, varSum, 0, varDouble);  // ignore HRESULT return

          if varSum.VType = varDouble then
            dblSum := varSum.VDouble;

          VariantClear(varSum);
        end;

      varShortInt:  // 1 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PShortInt(pData)^;
          Inc(PByte(pData), 1);
        end;

      varByte:  // 1 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PByte(pData)^;
          Inc(PByte(pData), 1);
        end;

      varWord: // 2 bytes, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PWord(pData)^;
          Inc(PByte(pData), 2);
        end;

      varUInt32, VT_UINT: // 4 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt32(pData)^;
          Inc(PByte(pData), 4);
        end;

      varInt64: // 8 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInt64(pData)^;
          Inc(PByte(pData), 8);
        end;

      varUInt64:  // 8 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt64(pData)^;
          Inc(PByte(pData), 8);
        end;
    else
      Assert(False, 'Invalid VarType');
    end;
  finally
    SafeArrayUnaccessData(pva);
  end;

  Result := VAR_OK; //  = S_OK
end;

function FastCopyArray(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pvaSrc, pvaDst: PVarArray;
begin
  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // copy itself

  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  pvaDst := nil;
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert(pvaDst <> nil);

  // Copy ok, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

function FastTransArrayByCopy(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
begin
  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH); // we only support transfer array have two dimensions

  // SafeArrayCopy calls the string or variant manipulation functions if the array to copy contains
  // either of these data types. If the array being copied contains object references, the reference
  // counts for the objects are incremented.
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert((pvaDst <> nil) and (pvaSrc.DimCount = pvaDst.DimCount) and (pvaSrc.ElementSize = pvaDst.ElementSize));

  // Swap two dimensions, bounds array stored reverse in memory of a SAFEARRAY
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;
  pvaDst.Bounds[0] := pvaSrc.Bounds[I];
  pvaDst.Bounds[I] := pvaSrc.Bounds[0];

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  Assert(pSrc <> nil);

  try
    SafeArrayAccessData(pvaDst, pDst);  // pvaDst^.LockCount/cLocks = 0, don't need to check hr
    Assert(pDst <> nil);

    // Transpose array by swapping raw data position of each element
    // Elements in SAFEARRAY stored as [col, row], so we need only swap to [row, col]
    // Unroll the case inside the loop, use constant
    case pvaDst.ElementSize of
      1:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
            Inc(PByte(pDst), 1);
          end;

      2:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
            Inc(PByte(pDst), 2);
          end;

      4:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
            Inc(PByte(pDst), 4);
          end;

      8:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
            Inc(PByte(pDst), 8);
          end;
    else
      // VARIANT, DECIMAL or another types
      for I := 0 to LOldRows - 1 do
        for J := 0 to LOldCols - 1 do
        begin
          CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
          Inc(PByte(pDst), pvaDst.ElementSize);
        end;
    end;

    SafeArrayUnaccessData(pvaDst);
  finally
    SafeArrayUnaccessData(pvaSrc);
  end;

  // Trans data OK, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to Variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

// Code from RE SafeArrayCreate and SafeArrayCopy in oleaut32.dll
// Unsafe to uses.
function FastTransArrayDirect(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
begin
  Result := VAR_NOTIMPL;
end;

exports
  FastSumArray,
  FastCopyArray,
  FastTransArrayByCopy,
  FastTransArrayDirect;

{$IFDEF TEST}
var
  vArrSrc, vArrDst: Variant;
  I, J: Integer;
  hr: HRESULT;
begin
  vArrSrc := VarArrayCreate([0, 2, 0, 4], varVariant);
  for I := 0 to 2 do
    for J := 0 to 4 do
      vArrSrc[I, J] := I * 10 + J + 1;
  vArrSrc[2, 4] := 'Text';

  for I := 0 to 2 do
  begin
    for J := 0 to 4 do
      Write(vArrSrc[I, J]:8, ' ');
    WriteLn;
  end;

  hr := FastTransArrayByCopy(vArrSrc, vArrDst);
  if Succeeded(hr) then
  begin
    WriteLn('Bound 1: ', VarArrayLowBound(vArrDst, 1), ' - ', VarArrayHighBound(vArrDst, 1));
    WriteLn('Bound 2: ', VarArrayLowBound(vArrDst, 2), ' - ', VarArrayHighBound(vArrDst, 2));
    for I := 0 to 4 do
    begin
      for J := 0 to 2 do
        Write(vArrDst[I, J]:8);
      WriteLn;
    end;
  end;
  ReadLn;
{$ENDIF}
end.
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Sau 1 thời gian RE các hàm SAFEARRAY API trong oleaut32.dll, mình viết tặng các bạn các hàm Sum, Copy và Trans array. Dùng trực tiếp API và đã test, cải tiến tối đa tốc độ.
Các bạn có thể dùng cho thư viện riêng của mình. Beerware lái sần hết, không lo.
Các bạn test speed thử giúp mình nhé.
Hàm FastTransArrayByCopy thua tốc độ hàm FastTransArrayDirect 1 chút, nhưng dùng an toàn hơn. Nên mình không đưa code hàm FastTransArrayDirect vào, vì sau này, các Windows ver khác, ai biết MS có đổi internal struct của SAFEARRAY nữa hay không.
Cho nên chắc ăn nhất là cứ dùng các API có sẵn của họ.
Bạn nào cần code hàm FastTransArrayDirect thì contact mình.
Các hàm Sum, Copy và Trans array đều an toàn, không memory leak/object leak, chạy đúng với mọi kiểu dữ liệu của từng phần tử, mọi Variant , string (BSTR), các object IUnknown, IDispatch....

Mã:
// VBArray.dll: Library for some VB/VBA array functions
// Coded by: HTC (TQN/TCA)
// Compile:
//    Release: dcc32.exe/dcc64.exe -B -$C- VBArray.dpr
//    Debug: dcc32.exe/dcc64.exe -B -V VBArray.dpr
// Beeware licenses ;)
//

{$DEFINE TEST}

{$IFDEF TEST}

program VBArray;

{$APPTYPE CONSOLE}

{$ELSE}

library VBArray;

{$IFDEF WIN64}
  {$LIBSUFFIX '64'}
{$ELSE}
  {$LIBSUFFIX '32'}
{$ENDIF}

{$ENDIF}

uses
  Winapi.Windows, System.Variants, System.VarUtils;

const
  // Not declared in Delphi until Sydney 10.4.2 verion, declared in wtypes.h
  FADF_RECORD = $20;
  FADF_HAVEIID = $40;
  FADF_HAVEVARTYPE = $80;
  VT_INT = 22;  // signed machine int
  VT_UINT = 23; // unsigned machine int

// We can not sum array of IUnknown, IDispatch, IRecordInfo
// Only support sum array of Variant, BSTR (OleStr), numeric elements...
//
function FastSumArray(const vArr: Variant; var dblSum: Double): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pData: Pointer;
  pva: PVarArray; // TVarArray in Delphi = SAFEARRAY in C/C++ Windows SDK
  dblOut: Double;
  varSum: TVarData;
  I, LTotalElement: NativeUInt;
begin
  dblSum := 0;

  if not VarIsArray(vArr) then
    Exit(VAR_INVALIDARG);

  pva := VarArrayAsPSafeArray(vArr);
  Assert(pva <> nil);

  if (pva.Flags and FADF_RECORD <> 0) or (pva.Flags and FADF_HAVEIID <> 0) then
    Exit(VAR_NOTIMPL);

  if (pva.DimCount < 1) or (pva.Flags and FADF_HAVEVARTYPE = 0) then
    Exit(VAR_TYPEMISMATCH);

  vt := PWord(PByte(pva) - 4)^; // vt stored at offset -4
  if ((pva.Flags and ARR_OLESTR <> 0) and (varOleStr <> vt)) or
    ((pva.Flags and ARR_VARIANT <> 0) and (varVariant <> vt)) then
  begin
    Assert(False, 'Bad VarType');
    Exit(VAR_UNEXPECTED);
  end;

  // we not support VT_DECIMAL (16 byte)
  if not vt in [varSmallInt, varInteger, varSingle, varDouble, varCurrency, varDate, varOleStr,
                varBoolean, varVariant, varShortInt, varByte, varWord, varUInt32, varInt64,
                varUInt64, VT_INT, VT_UINT] then
    Exit(VAR_BADVARTYPE);

  LTotalElement := 1;
  for I := 0 to pva.DimCount - 1 do
    LTotalElement := LTotalElement * pva.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pva, pData);
  if Failed(hr) then
    Exit(hr);

  try
    // Unroll the case inside the loop. Avoid excute many cmp/jnz ASM instructions inside the loop
    // Delphi compiler not create swich/jump table as other C/C++ compilers
    // Avoid access pva.ElementSize in the loop: Inc(PByte(pData), pva.ElementSize), uses constant direct
    //
    case vt of
      varSmallInt, varBoolean:  // 2 bytes, signed, VARIANT_BOOL = WordBool, -1 = TRUE, 0 = FALSE
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSmallInt(pData)^;
          Inc(PByte(pData), 2);
        end;

      varInteger, VT_INT: // 4 bytes, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInteger(pData)^;
          Inc(PByte(pData), 4);
        end;

      varSingle:  // 4 bytes, float
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSingle(pData)^;
          Inc(PByte(pData), 4);
        end;

      varDouble, varDate:  // 8 bytes, DATETIME = Double
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PDouble(pData)^;
          Inc(PByte(pData), 8);
        end;

      varCurrency:  // 8 bytes
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PCurrency(pData)^;
          Inc(PByte(pData), 8);
        end;

      varOleStr:  // SizeOf(Pointer)
        for I := 1 to LTotalElement do
        begin
          hr := VarR8FromStr(PWideChar(pData^), VAR_LOCALE_USER_DEFAULT, 0, dblOut);
          Inc(PByte(pData), SizeOf(Pointer));
          if Succeeded(hr) then
            dblSum := dblSum + dblOut;
        end;

      varVariant: // SizeOf(Variant)
        begin
          VariantInit(varSum);
          varSum.VType := varDouble;
          varSum.VDouble := 0;

          for I := 1 to LTotalElement do
          begin
            VarAdd(varSum, PVarData(pData)^, varSum); // ignore HRESULT return and failed elements
            Inc(PByte(pData), SizeOf(Variant));
          end;

          if varSum.VType <> varDouble then
            VariantChangeType(varSum, varSum, 0, varDouble);  // ignore HRESULT return

          if varSum.VType = varDouble then
            dblSum := varSum.VDouble;

          VariantClear(varSum);
        end;

      varShortInt:  // 1 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PShortInt(pData)^;
          Inc(PByte(pData), 1);
        end;

      varByte:  // 1 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PByte(pData)^;
          Inc(PByte(pData), 1);
        end;

      varWord: // 2 bytes, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PWord(pData)^;
          Inc(PByte(pData), 2);
        end;

      varUInt32, VT_UINT: // 4 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt32(pData)^;
          Inc(PByte(pData), 4);
        end;

      varInt64: // 8 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInt64(pData)^;
          Inc(PByte(pData), 8);
        end;

      varUInt64:  // 8 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt64(pData)^;
          Inc(PByte(pData), 8);
        end;
    else
      Assert(False, 'Invalid VarType');
    end;
  finally
    SafeArrayUnaccessData(pva);
  end;

  Result := VAR_OK; //  = S_OK
end;

function FastCopyArray(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pvaSrc, pvaDst: PVarArray;
begin
  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // copy itself

  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  pvaDst := nil;
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert(pvaDst <> nil);

  // Copy ok, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

function FastTransArrayByCopy(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
begin
  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH); // we only support transfer array have two dimensions

  // SafeArrayCopy calls the string or variant manipulation functions if the array to copy contains
  // either of these data types. If the array being copied contains object references, the reference
  // counts for the objects are incremented.
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert((pvaDst <> nil) and (pvaSrc.DimCount = pvaDst.DimCount) and (pvaSrc.ElementSize = pvaDst.ElementSize));

  // Swap two dimensions, bounds array stored reverse in memory of a SAFEARRAY
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;
  pvaDst.Bounds[0] := pvaSrc.Bounds[I];
  pvaDst.Bounds[I] := pvaSrc.Bounds[0];

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  Assert(pSrc <> nil);

  try
    SafeArrayAccessData(pvaDst, pDst);  // pvaDst^.LockCount/cLocks = 0, don't need to check hr
    Assert(pDst <> nil);

    // Transpose array by swapping raw data position of each element
    // Elements in SAFEARRAY stored as [col, row], so we need only swap to [row, col]
    // Unroll the case inside the loop, use constant
    case pvaDst.ElementSize of
      1:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
            Inc(PByte(pDst), 1);
          end;

      2:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
            Inc(PByte(pDst), 2);
          end;

      4:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
            Inc(PByte(pDst), 4);
          end;

      8:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
            Inc(PByte(pDst), 8);
          end;
    else
      // VARIANT, DECIMAL or another types
      for I := 0 to LOldRows - 1 do
        for J := 0 to LOldCols - 1 do
        begin
          CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
          Inc(PByte(pDst), pvaDst.ElementSize);
        end;
    end;

    SafeArrayUnaccessData(pvaDst);
  finally
    SafeArrayUnaccessData(pvaSrc);
  end;

  // Trans data OK, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to Variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

// Code from RE SafeArrayCreate and SafeArrayCopy in oleaut32.dll
// Unsafe to uses.
function FastTransArrayDirect(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
begin
  Result := VAR_NOTIMPL;
end;

exports
  FastSumArray,
  FastCopyArray,
  FastTransArrayByCopy,
  FastTransArrayDirect;

{$IFDEF TEST}
var
  vArrSrc, vArrDst: Variant;
  I, J: Integer;
  hr: HRESULT;
begin
  vArrSrc := VarArrayCreate([0, 2, 0, 4], varVariant);
  for I := 0 to 2 do
    for J := 0 to 4 do
      vArrSrc[I, J] := I * 10 + J + 1;
  vArrSrc[2, 4] := 'Text';

  for I := 0 to 2 do
  begin
    for J := 0 to 4 do
      Write(vArrSrc[I, J]:8, ' ');
    WriteLn;
  end;

  hr := FastTransArrayByCopy(vArrSrc, vArrDst);
  if Succeeded(hr) then
  begin
    WriteLn('Bound 1: ', VarArrayLowBound(vArrDst, 1), ' - ', VarArrayHighBound(vArrDst, 1));
    WriteLn('Bound 2: ', VarArrayLowBound(vArrDst, 2), ' - ', VarArrayHighBound(vArrDst, 2));
    for I := 0 to 4 do
    begin
      for J := 0 to 2 do
        Write(vArrDst[I, J]:8);
      WriteLn;
    end;
  end;
  ReadLn;
{$ENDIF}
end.

Em đã test. code anh làm chạy rất nhanh.
Về logic tính SUM, theo em với kiểu BOOLEAN mình không đưa vào tính tổng để việc ứng dụng sẽ thống nhất với các hàm Excel.
 
Upvote 0
Sau 1 thời gian RE các hàm SAFEARRAY API trong oleaut32.dll, mình viết tặng các bạn các hàm Sum, Copy và Trans array. Dùng trực tiếp API và đã test, cải tiến tối đa tốc độ.
Các bạn có thể dùng cho thư viện riêng của mình. Beerware lái sần hết, không lo.
Các bạn test speed thử giúp mình nhé.
Hàm FastTransArrayByCopy thua tốc độ hàm FastTransArrayDirect 1 chút, nhưng dùng an toàn hơn. Nên mình không đưa code hàm FastTransArrayDirect vào, vì sau này, các Windows ver khác, ai biết MS có đổi internal struct của SAFEARRAY nữa hay không.
Cho nên chắc ăn nhất là cứ dùng các API có sẵn của họ.
Bạn nào cần code hàm FastTransArrayDirect thì contact mình.
Các hàm Sum, Copy và Trans array đều an toàn, không memory leak/object leak, chạy đúng với mọi kiểu dữ liệu của từng phần tử, mọi Variant , string (BSTR), các object IUnknown, IDispatch....

Mã:
// VBArray.dll: Library for some VB/VBA array functions
// Coded by: HTC (TQN/TCA)
// Compile:
//    Release: dcc32.exe/dcc64.exe -B -$C- VBArray.dpr
//    Debug: dcc32.exe/dcc64.exe -B -V VBArray.dpr
// Beeware licenses ;)
//

{$DEFINE TEST}

{$IFDEF TEST}

program VBArray;

{$APPTYPE CONSOLE}

{$ELSE}

library VBArray;

{$IFDEF WIN64}
  {$LIBSUFFIX '64'}
{$ELSE}
  {$LIBSUFFIX '32'}
{$ENDIF}

{$ENDIF}

uses
  Winapi.Windows, System.Variants, System.VarUtils;

const
  // Not declared in Delphi until Sydney 10.4.2 verion, declared in wtypes.h
  FADF_RECORD = $20;
  FADF_HAVEIID = $40;
  FADF_HAVEVARTYPE = $80;
  VT_INT = 22;  // signed machine int
  VT_UINT = 23; // unsigned machine int

// We can not sum array of IUnknown, IDispatch, IRecordInfo
// Only support sum array of Variant, BSTR (OleStr), numeric elements...
//
function FastSumArray(const vArr: Variant; var dblSum: Double): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pData: Pointer;
  pva: PVarArray; // TVarArray in Delphi = SAFEARRAY in C/C++ Windows SDK
  dblOut: Double;
  varSum: TVarData;
  I, LTotalElement: NativeUInt;
begin
  dblSum := 0;

  if not VarIsArray(vArr) then
    Exit(VAR_INVALIDARG);

  pva := VarArrayAsPSafeArray(vArr);
  Assert(pva <> nil);

  if (pva.Flags and FADF_RECORD <> 0) or (pva.Flags and FADF_HAVEIID <> 0) then
    Exit(VAR_NOTIMPL);

  if (pva.DimCount < 1) or (pva.Flags and FADF_HAVEVARTYPE = 0) then
    Exit(VAR_TYPEMISMATCH);

  vt := PWord(PByte(pva) - 4)^; // vt stored at offset -4
  if ((pva.Flags and ARR_OLESTR <> 0) and (varOleStr <> vt)) or
    ((pva.Flags and ARR_VARIANT <> 0) and (varVariant <> vt)) then
  begin
    Assert(False, 'Bad VarType');
    Exit(VAR_UNEXPECTED);
  end;

  // we not support VT_DECIMAL (16 byte)
  if not vt in [varSmallInt, varInteger, varSingle, varDouble, varCurrency, varDate, varOleStr,
                varBoolean, varVariant, varShortInt, varByte, varWord, varUInt32, varInt64,
                varUInt64, VT_INT, VT_UINT] then
    Exit(VAR_BADVARTYPE);

  LTotalElement := 1;
  for I := 0 to pva.DimCount - 1 do
    LTotalElement := LTotalElement * pva.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pva, pData);
  if Failed(hr) then
    Exit(hr);

  try
    // Unroll the case inside the loop. Avoid excute many cmp/jnz ASM instructions inside the loop
    // Delphi compiler not create swich/jump table as other C/C++ compilers
    // Avoid access pva.ElementSize in the loop: Inc(PByte(pData), pva.ElementSize), uses constant direct
    //
    case vt of
      varSmallInt, varBoolean:  // 2 bytes, signed, VARIANT_BOOL = WordBool, -1 = TRUE, 0 = FALSE
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSmallInt(pData)^;
          Inc(PByte(pData), 2);
        end;

      varInteger, VT_INT: // 4 bytes, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInteger(pData)^;
          Inc(PByte(pData), 4);
        end;

      varSingle:  // 4 bytes, float
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSingle(pData)^;
          Inc(PByte(pData), 4);
        end;

      varDouble, varDate:  // 8 bytes, DATETIME = Double
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PDouble(pData)^;
          Inc(PByte(pData), 8);
        end;

      varCurrency:  // 8 bytes
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PCurrency(pData)^;
          Inc(PByte(pData), 8);
        end;

      varOleStr:  // SizeOf(Pointer)
        for I := 1 to LTotalElement do
        begin
          hr := VarR8FromStr(PWideChar(pData^), VAR_LOCALE_USER_DEFAULT, 0, dblOut);
          Inc(PByte(pData), SizeOf(Pointer));
          if Succeeded(hr) then
            dblSum := dblSum + dblOut;
        end;

      varVariant: // SizeOf(Variant)
        begin
          VariantInit(varSum);
          varSum.VType := varDouble;
          varSum.VDouble := 0;

          for I := 1 to LTotalElement do
          begin
            VarAdd(varSum, PVarData(pData)^, varSum); // ignore HRESULT return and failed elements
            Inc(PByte(pData), SizeOf(Variant));
          end;

          if varSum.VType <> varDouble then
            VariantChangeType(varSum, varSum, 0, varDouble);  // ignore HRESULT return

          if varSum.VType = varDouble then
            dblSum := varSum.VDouble;

          VariantClear(varSum);
        end;

      varShortInt:  // 1 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PShortInt(pData)^;
          Inc(PByte(pData), 1);
        end;

      varByte:  // 1 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PByte(pData)^;
          Inc(PByte(pData), 1);
        end;

      varWord: // 2 bytes, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PWord(pData)^;
          Inc(PByte(pData), 2);
        end;

      varUInt32, VT_UINT: // 4 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt32(pData)^;
          Inc(PByte(pData), 4);
        end;

      varInt64: // 8 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInt64(pData)^;
          Inc(PByte(pData), 8);
        end;

      varUInt64:  // 8 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt64(pData)^;
          Inc(PByte(pData), 8);
        end;
    else
      Assert(False, 'Invalid VarType');
    end;
  finally
    SafeArrayUnaccessData(pva);
  end;

  Result := VAR_OK; //  = S_OK
end;

function FastCopyArray(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pvaSrc, pvaDst: PVarArray;
begin
  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // copy itself

  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  pvaDst := nil;
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert(pvaDst <> nil);

  // Copy ok, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

function FastTransArrayByCopy(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
begin
  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH); // we only support transfer array have two dimensions

  // SafeArrayCopy calls the string or variant manipulation functions if the array to copy contains
  // either of these data types. If the array being copied contains object references, the reference
  // counts for the objects are incremented.
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert((pvaDst <> nil) and (pvaSrc.DimCount = pvaDst.DimCount) and (pvaSrc.ElementSize = pvaDst.ElementSize));

  // Swap two dimensions, bounds array stored reverse in memory of a SAFEARRAY
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;
  pvaDst.Bounds[0] := pvaSrc.Bounds[I];
  pvaDst.Bounds[I] := pvaSrc.Bounds[0];

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  Assert(pSrc <> nil);

  try
    SafeArrayAccessData(pvaDst, pDst);  // pvaDst^.LockCount/cLocks = 0, don't need to check hr
    Assert(pDst <> nil);

    // Transpose array by swapping raw data position of each element
    // Elements in SAFEARRAY stored as [col, row], so we need only swap to [row, col]
    // Unroll the case inside the loop, use constant
    case pvaDst.ElementSize of
      1:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
            Inc(PByte(pDst), 1);
          end;

      2:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
            Inc(PByte(pDst), 2);
          end;

      4:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
            Inc(PByte(pDst), 4);
          end;

      8:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
            Inc(PByte(pDst), 8);
          end;
    else
      // VARIANT, DECIMAL or another types
      for I := 0 to LOldRows - 1 do
        for J := 0 to LOldCols - 1 do
        begin
          CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
          Inc(PByte(pDst), pvaDst.ElementSize);
        end;
    end;

    SafeArrayUnaccessData(pvaDst);
  finally
    SafeArrayUnaccessData(pvaSrc);
  end;

  // Trans data OK, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to Variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

// Code from RE SafeArrayCreate and SafeArrayCopy in oleaut32.dll
// Unsafe to uses.
function FastTransArrayDirect(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
begin
  Result := VAR_NOTIMPL;
end;

exports
  FastSumArray,
  FastCopyArray,
  FastTransArrayByCopy,
  FastTransArrayDirect;

{$IFDEF TEST}
var
  vArrSrc, vArrDst: Variant;
  I, J: Integer;
  hr: HRESULT;
begin
  vArrSrc := VarArrayCreate([0, 2, 0, 4], varVariant);
  for I := 0 to 2 do
    for J := 0 to 4 do
      vArrSrc[I, J] := I * 10 + J + 1;
  vArrSrc[2, 4] := 'Text';

  for I := 0 to 2 do
  begin
    for J := 0 to 4 do
      Write(vArrSrc[I, J]:8, ' ');
    WriteLn;
  end;

  hr := FastTransArrayByCopy(vArrSrc, vArrDst);
  if Succeeded(hr) then
  begin
    WriteLn('Bound 1: ', VarArrayLowBound(vArrDst, 1), ' - ', VarArrayHighBound(vArrDst, 1));
    WriteLn('Bound 2: ', VarArrayLowBound(vArrDst, 2), ' - ', VarArrayHighBound(vArrDst, 2));
    for I := 0 to 4 do
    begin
      for J := 0 to 2 do
        Write(vArrDst[I, J]:8);
      WriteLn;
    end;
  end;
  ReadLn;
{$ENDIF}
end.
Cảm ơn nhiều ... qua tuần rảnh Mạnh test các kiểu xem sao có gì báo lại sau ... tin 100/100 là chạy vèo vèo
Cái dòng To màu đen cũng muốn lại mà ngại ....:p
 
Upvote 0
Rảnh chút mạnh mới thử Code của Tuân và Cuanh như hình sau
Rất công bằng chạy mỗi code 3 lần từ trên xuống dưới xong chạy từ dưới lên Trên :p

1637465774011.png
 
Upvote 0
Mới test lại chút thấy khác biệt với bài trước ....:p ...

1637475043751.png
 
Upvote 0
FastTransArrayByCopy sẽ chạy chậm nhất trong trường hợp các phần tử của array là kiểu VARIANT.
Do hàm SafeArrayCopy đã phải gọi hàm VariantCopy trong ruột nó, ở ngoài còn phải swap 2 raw value của 2 phần tử bằng CopyMemory.
Chờ chút mình fix lại chổ CopyMemory và đưa luôn source hàm FastTransArrayDirect
 
Upvote 0
Version mới của VBArray32/64.dll
Source của hàm FastTransArrayDirect và optimize chút ở hàm FastTransArrayByCopy ở chổ dữ liệu là Variant.
Chắc chắn FastTransArrayDirect sẽ nhanh hơn FastTransArrayByCopy cỡ 1/3 - 1/4.
Bà con test tiếp giúp nhé. Dữ liệu càng lớn càng tốt.

Then and bé xì ga ;)

Mã:
// VBArray.dll: Library for some VB/VBA array functions
// Coded by: HTC (TQN/TCA)
// Version: 1.0
// Compile:
//    Release: dcc32.exe/dcc64.exe -B -$C- VBArray.dpr
//    Debug: dcc32.exe/dcc64.exe -B -V VBArray.dpr
// Beeware licenses ;)
//

//{$DEFINE TEST}

{$IFDEF TEST}

program VBArray;

{$APPTYPE CONSOLE}

{$ELSE}

library VBArray;

{$IFDEF WIN64}
  {$LIBSUFFIX '64'}
{$ELSE}
  {$LIBSUFFIX '32'}
{$ENDIF}

{$ENDIF}

uses
  Winapi.Windows, Winapi.ActiveX, System.Variants, System.VarUtils;

const
  // Not declared in Delphi until Sydney 10.4.2 verion, declared in wtypes.h
  FADF_RECORD = $20;
  FADF_HAVEIID = $40;
  FADF_HAVEVARTYPE = $80;
  VT_INT = 22;  // signed machine int
  VT_UINT = 23; // unsigned machine int

function SafeArrayGetVartype(pva: PVarArray; var vt: TVarType): HRESULT; stdcall; external 'oleaut32.dll';

// We can not sum array of IUnknown, IDispatch, IRecordInfo
// Only support sum array of Variant, BSTR (OleStr), numeric elements...
//
function FastSumArray(const vArr: Variant; var dblSum: Double): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pData: Pointer;
  pva: PVarArray; // TVarArray in Delphi = SAFEARRAY in C/C++ Windows SDK
  dblOut: Double;
  varSum: TVarData;
  I, LTotalElement: NativeUInt;
begin
  dblSum := 0;

  if not VarIsArray(vArr) then
    Exit(VAR_INVALIDARG);

  pva := VarArrayAsPSafeArray(vArr);
  Assert(pva <> nil);

  if (pva.Flags and FADF_RECORD <> 0) or (pva.Flags and FADF_HAVEIID <> 0) then
    Exit(VAR_NOTIMPL);

  if (pva.DimCount < 1) or (pva.Flags and FADF_HAVEVARTYPE = 0) then
    Exit(VAR_TYPEMISMATCH);

  vt := PWord(PByte(pva) - 4)^; // vt stored at offset -4
  if ((pva.Flags and ARR_OLESTR <> 0) and (varOleStr <> vt)) or
    ((pva.Flags and ARR_VARIANT <> 0) and (varVariant <> vt)) then
  begin
    Assert(False, 'Bad VarType');
    Exit(VAR_UNEXPECTED);
  end;

  // we not support VT_DECIMAL (16 byte)
  if not vt in [varSmallInt, varInteger, varSingle, varDouble, varCurrency, varDate, varOleStr,
                varBoolean, varVariant, varShortInt, varByte, varWord, varUInt32, varInt64,
                varUInt64, VT_INT, VT_UINT] then
    Exit(VAR_BADVARTYPE);

  LTotalElement := 1;
  for I := 0 to pva.DimCount - 1 do
    LTotalElement := LTotalElement * pva.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pva, pData);
  if Failed(hr) then
    Exit(hr);

  try
    // Unroll the case inside the loop. Avoid excute many cmp/jnz ASM instructions inside the loop
    // Delphi compiler not create swich/jump table as other C/C++ compilers
    // Avoid access pva.ElementSize in the loop: Inc(PByte(pData), pva.ElementSize), uses constant direct
    //
    case vt of
      varSmallInt, varBoolean:  // 2 bytes, signed, VARIANT_BOOL = WordBool, -1 = TRUE, 0 = FALSE
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSmallInt(pData)^;
          Inc(PByte(pData), 2);
        end;

      varInteger, VT_INT: // 4 bytes, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInteger(pData)^;
          Inc(PByte(pData), 4);
        end;

      varSingle:  // 4 bytes, float
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSingle(pData)^;
          Inc(PByte(pData), 4);
        end;

      varDouble, varDate:  // 8 bytes, DATETIME = Double
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PDouble(pData)^;
          Inc(PByte(pData), 8);
        end;

      varCurrency:  // 8 bytes
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PCurrency(pData)^;
          Inc(PByte(pData), 8);
        end;

      varOleStr:  // SizeOf(Pointer)
        for I := 1 to LTotalElement do
        begin
          hr := VarR8FromStr(PWideChar(pData^), VAR_LOCALE_USER_DEFAULT, 0, dblOut);
          Inc(PByte(pData), SizeOf(Pointer));
          if Succeeded(hr) then
            dblSum := dblSum + dblOut;
        end;

      varVariant: // SizeOf(Variant)
        begin
          VariantInit(varSum);
          varSum.VType := varDouble;
          varSum.VDouble := 0;

          for I := 1 to LTotalElement do
          begin
            VarAdd(varSum, PVarData(pData)^, varSum); // ignore HRESULT return and failed elements
            Inc(PByte(pData), SizeOf(Variant));
          end;

          if varSum.VType <> varDouble then
            VariantChangeType(varSum, varSum, 0, varDouble);  // ignore HRESULT return

          if varSum.VType = varDouble then
            dblSum := varSum.VDouble;

          VariantClear(varSum);
        end;

      varShortInt:  // 1 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PShortInt(pData)^;
          Inc(PByte(pData), 1);
        end;

      varByte:  // 1 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PByte(pData)^;
          Inc(PByte(pData), 1);
        end;

      varWord: // 2 bytes, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PWord(pData)^;
          Inc(PByte(pData), 2);
        end;

      varUInt32, VT_UINT: // 4 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt32(pData)^;
          Inc(PByte(pData), 4);
        end;

      varInt64: // 8 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInt64(pData)^;
          Inc(PByte(pData), 8);
        end;

      varUInt64:  // 8 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt64(pData)^;
          Inc(PByte(pData), 8);
        end;
    else
      Assert(False, 'Invalid VarType');
    end;
  finally
    SafeArrayUnaccessData(pva);
  end;

  Result := VAR_OK; //  = S_OK
end;

function FastCopyArray(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pvaSrc, pvaDst: PVarArray;
begin
  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // copy itself

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  pvaDst := nil;
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert(pvaDst <> nil);

  // Copy ok, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

function FastTransArrayByCopy(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
begin
  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH); // we only support transfer array have two dimensions

  // SafeArrayCopy calls the string or variant manipulation functions if the array to copy contains
  // either of these data types. If the array being copied contains object references, the reference
  // counts for the objects are incremented.
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert((pvaDst <> nil) and (pvaSrc.DimCount = pvaDst.DimCount) and (pvaSrc.ElementSize = pvaDst.ElementSize));

  // Swap two dimensions, bounds array stored reverse in memory of a SAFEARRAY
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;
  pvaDst.Bounds[0] := pvaSrc.Bounds[I];
  pvaDst.Bounds[I] := pvaSrc.Bounds[0];

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  SafeArrayAccessData(pvaDst, pDst);  // pvaDst^.LockCount/cLocks = 0, don't need to check hr
  Assert((pSrc <> nil) and (pDst <> nil));

  try
    // Transpose array by swapping raw data position of each element
    // Elements in SAFEARRAY stored as [col, row], so we need only swap to [row, col]
    // Unroll the case inside the loop, use constant
    case pvaDst.ElementSize of
      1:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
            Inc(PByte(pDst), 1);
          end;

      2:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
            Inc(PByte(pDst), 2);
          end;

      4:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
            Inc(PByte(pDst), 4);
          end;

      8:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
            Inc(PByte(pDst), 8);
          end;

      SizeOf(Variant):
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PVarData(pDst)^ := PVarData(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Variant))^;
            Inc(PByte(pDst), SizeOf(Variant));
          end;

    else
      // DECIMAL or another types
      for I := 0 to LOldRows - 1 do
        for J := 0 to LOldCols - 1 do
        begin
          CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
          Inc(PByte(pDst), pvaDst.ElementSize);
        end;
    end;
  finally
    SafeArrayUnaccessData(pvaDst);
    SafeArrayUnaccessData(pvaSrc);
  end;

  // Trans data OK, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to Variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

// Code from RE SafeArrayCreate and SafeArrayCopy in oleaut32.dll
// Unsafe to uses.
function FastTransArrayDirect(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
  pInfTmp, pRecInfo, pszTmp: PNativeUInt;
begin
  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH);

  hr := SafeArrayGetVarType(pvaSrc, vt);
  if Failed(hr) then
    Exit(hr);

  pvaDst := SafeArrayCreate(vt, 2, @pvaSrc.Bounds);
  if pvaDst = nil then
    Exit(VAR_OUTOFMEMORY);

  if (varUnknown = vt) or (varDispatch = vt) then
    PGUID(PByte(pvaDst) - SizeOf(TGUID))^ := PGUID(PByte(pvaSrc) - SizeOf(TGUID))^
  else if (varRecord = vt) then
  begin
    NativeUInt(pRecInfo) := PNativeUInt(PByte(pvaSrc) - SizeOf(Pointer))^;
    PNativeUInt(PByte(pvaDst) - SizeOf(Pointer))^ := NativeUInt(pRecInfo);
    IRecordInfo(pRecInfo)._AddRef();
  end;

  // Copy and trans data
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  SafeArrayAccessData(pvaDst, pDst);
  Assert((pSrc <> nil) and (pDst <> nil));

  try
    case vt of
      varUnknown, varDispatch:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            NativeUInt(pInfTmp) := PNativeUInt(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Pointer))^;
            IUnknown(pInfTmp)._AddRef();
            PNativeUInt(pDst)^ := NativeUInt(pInfTmp);
            Inc(PByte(pDst), SizeOf(Pointer));
          end;

      varRecord:  // SizeOf(Record) = pvaDst/pvaSrc.ElementSize
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            IRecordInfo(pRecInfo).RecordCopy(PByte(pSrc) + (J * LOldRows + I) * pvaSrc.ElementSize, pDst);
            Inc(PByte(pDst), pvaDst.ElementSize);
          end;

      varOleStr:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            NativeUInt(pszTmp) := PNativeUInt(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Pointer))^;
            PPOleStr(pDst)^ := SysAllocStringByteLen(PAnsiChar(pszTmp), SysStringByteLen(PWideChar(pszTmp)));
            Inc(PByte(pDst), SizeOf(Pointer));
          end;

      varVariant:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            VariantCopy(PVarData(pDst)^, PVarData(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Variant))^);
            Inc(PByte(pDst), SizeOf(Variant));
          end;
    else
      case pvaDst.ElementSize of
        1:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
              Inc(PByte(pDst), 1);
            end;

        2:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
              Inc(PByte(pDst), 2);
            end;

        4:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
              Inc(PByte(pDst), 4);
            end;

        8:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
              Inc(PByte(pDst), 8);
            end;
      else
        // DECIMAL or another types
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
            Inc(PByte(pDst), pvaDst.ElementSize);
          end;
      end;
    end;
  finally
    SafeArrayUnaccessData(pvaDst);
    SafeArrayUnaccessData(pvaSrc);
  end;

  VariantClear(TVarData(vArrDst));

  TVarData(vArrDst).VType := varArray or vt;
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

exports
  FastSumArray,
  FastCopyArray,
  FastTransArrayByCopy,
  FastTransArrayDirect;

{$IFDEF TEST}
var
  vArrSrc, vArrDst: Variant;
  I, J: Integer;
  hr: HRESULT;
begin
  vArrSrc := VarArrayCreate([0, 2, 0, 4], varOleStr);
  for I := 0 to 2 do
    for J := 0 to 4 do
      vArrSrc[I, J] := I * 10 + J + 1;
  vArrSrc[2, 4] := 'Text';

  for I := 0 to 2 do
  begin
    for J := 0 to 4 do
      Write(vArrSrc[I, J]:8, ' ');
    WriteLn;
  end;

  hr := FastTransArrayDirect(vArrSrc, vArrDst);
  if Succeeded(hr) then
  begin
    WriteLn('Bound 1: ', VarArrayLowBound(vArrDst, 1), ' - ', VarArrayHighBound(vArrDst, 1));
    WriteLn('Bound 2: ', VarArrayLowBound(vArrDst, 2), ' - ', VarArrayHighBound(vArrDst, 2));
    for I := 0 to 4 do
    begin
      for J := 0 to 2 do
        Write(vArrDst[I, J]:8);
      WriteLn;
    end;
  end;
  ReadLn;
{$ENDIF}
end.
 
Upvote 0
Version mới của VBArray32/64.dll
Source của hàm FastTransArrayDirect và optimize chút ở hàm FastTransArrayByCopy ở chổ dữ liệu là Variant.
Chắc chắn FastTransArrayDirect sẽ nhanh hơn FastTransArrayByCopy cỡ 1/3 - 1/4.
Bà con test tiếp giúp nhé. Dữ liệu càng lớn càng tốt.

Then and bé xì ga ;)

Mã:
// VBArray.dll: Library for some VB/VBA array functions
// Coded by: HTC (TQN/TCA)
// Version: 1.0
// Compile:
//    Release: dcc32.exe/dcc64.exe -B -$C- VBArray.dpr
//    Debug: dcc32.exe/dcc64.exe -B -V VBArray.dpr
// Beeware licenses ;)
//

//{$DEFINE TEST}

{$IFDEF TEST}

program VBArray;

{$APPTYPE CONSOLE}

{$ELSE}

library VBArray;

{$IFDEF WIN64}
  {$LIBSUFFIX '64'}
{$ELSE}
  {$LIBSUFFIX '32'}
{$ENDIF}

{$ENDIF}

uses
  Winapi.Windows, Winapi.ActiveX, System.Variants, System.VarUtils;

const
  // Not declared in Delphi until Sydney 10.4.2 verion, declared in wtypes.h
  FADF_RECORD = $20;
  FADF_HAVEIID = $40;
  FADF_HAVEVARTYPE = $80;
  VT_INT = 22;  // signed machine int
  VT_UINT = 23; // unsigned machine int

function SafeArrayGetVartype(pva: PVarArray; var vt: TVarType): HRESULT; stdcall; external 'oleaut32.dll';

// We can not sum array of IUnknown, IDispatch, IRecordInfo
// Only support sum array of Variant, BSTR (OleStr), numeric elements...
//
function FastSumArray(const vArr: Variant; var dblSum: Double): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pData: Pointer;
  pva: PVarArray; // TVarArray in Delphi = SAFEARRAY in C/C++ Windows SDK
  dblOut: Double;
  varSum: TVarData;
  I, LTotalElement: NativeUInt;
begin
  dblSum := 0;

  if not VarIsArray(vArr) then
    Exit(VAR_INVALIDARG);

  pva := VarArrayAsPSafeArray(vArr);
  Assert(pva <> nil);

  if (pva.Flags and FADF_RECORD <> 0) or (pva.Flags and FADF_HAVEIID <> 0) then
    Exit(VAR_NOTIMPL);

  if (pva.DimCount < 1) or (pva.Flags and FADF_HAVEVARTYPE = 0) then
    Exit(VAR_TYPEMISMATCH);

  vt := PWord(PByte(pva) - 4)^; // vt stored at offset -4
  if ((pva.Flags and ARR_OLESTR <> 0) and (varOleStr <> vt)) or
    ((pva.Flags and ARR_VARIANT <> 0) and (varVariant <> vt)) then
  begin
    Assert(False, 'Bad VarType');
    Exit(VAR_UNEXPECTED);
  end;

  // we not support VT_DECIMAL (16 byte)
  if not vt in [varSmallInt, varInteger, varSingle, varDouble, varCurrency, varDate, varOleStr,
                varBoolean, varVariant, varShortInt, varByte, varWord, varUInt32, varInt64,
                varUInt64, VT_INT, VT_UINT] then
    Exit(VAR_BADVARTYPE);

  LTotalElement := 1;
  for I := 0 to pva.DimCount - 1 do
    LTotalElement := LTotalElement * pva.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pva, pData);
  if Failed(hr) then
    Exit(hr);

  try
    // Unroll the case inside the loop. Avoid excute many cmp/jnz ASM instructions inside the loop
    // Delphi compiler not create swich/jump table as other C/C++ compilers
    // Avoid access pva.ElementSize in the loop: Inc(PByte(pData), pva.ElementSize), uses constant direct
    //
    case vt of
      varSmallInt, varBoolean:  // 2 bytes, signed, VARIANT_BOOL = WordBool, -1 = TRUE, 0 = FALSE
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSmallInt(pData)^;
          Inc(PByte(pData), 2);
        end;

      varInteger, VT_INT: // 4 bytes, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInteger(pData)^;
          Inc(PByte(pData), 4);
        end;

      varSingle:  // 4 bytes, float
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PSingle(pData)^;
          Inc(PByte(pData), 4);
        end;

      varDouble, varDate:  // 8 bytes, DATETIME = Double
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PDouble(pData)^;
          Inc(PByte(pData), 8);
        end;

      varCurrency:  // 8 bytes
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PCurrency(pData)^;
          Inc(PByte(pData), 8);
        end;

      varOleStr:  // SizeOf(Pointer)
        for I := 1 to LTotalElement do
        begin
          hr := VarR8FromStr(PWideChar(pData^), VAR_LOCALE_USER_DEFAULT, 0, dblOut);
          Inc(PByte(pData), SizeOf(Pointer));
          if Succeeded(hr) then
            dblSum := dblSum + dblOut;
        end;

      varVariant: // SizeOf(Variant)
        begin
          VariantInit(varSum);
          varSum.VType := varDouble;
          varSum.VDouble := 0;

          for I := 1 to LTotalElement do
          begin
            VarAdd(varSum, PVarData(pData)^, varSum); // ignore HRESULT return and failed elements
            Inc(PByte(pData), SizeOf(Variant));
          end;

          if varSum.VType <> varDouble then
            VariantChangeType(varSum, varSum, 0, varDouble);  // ignore HRESULT return

          if varSum.VType = varDouble then
            dblSum := varSum.VDouble;

          VariantClear(varSum);
        end;

      varShortInt:  // 1 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PShortInt(pData)^;
          Inc(PByte(pData), 1);
        end;

      varByte:  // 1 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PByte(pData)^;
          Inc(PByte(pData), 1);
        end;

      varWord: // 2 bytes, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PWord(pData)^;
          Inc(PByte(pData), 2);
        end;

      varUInt32, VT_UINT: // 4 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt32(pData)^;
          Inc(PByte(pData), 4);
        end;

      varInt64: // 8 byte, signed
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PInt64(pData)^;
          Inc(PByte(pData), 8);
        end;

      varUInt64:  // 8 byte, unsigned
        for I := 1 to LTotalElement do
        begin
          dblSum := dblSum + PUInt64(pData)^;
          Inc(PByte(pData), 8);
        end;
    else
      Assert(False, 'Invalid VarType');
    end;
  finally
    SafeArrayUnaccessData(pva);
  end;

  Result := VAR_OK; //  = S_OK
end;

function FastCopyArray(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pvaSrc, pvaDst: PVarArray;
begin
  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // copy itself

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  pvaDst := nil;
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert(pvaDst <> nil);

  // Copy ok, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

function FastTransArrayByCopy(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
begin
  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH); // we only support transfer array have two dimensions

  // SafeArrayCopy calls the string or variant manipulation functions if the array to copy contains
  // either of these data types. If the array being copied contains object references, the reference
  // counts for the objects are incremented.
  hr := SafeArrayCopy(pvaSrc, pvaDst);
  if Failed(hr) then
    Exit(hr);

  Assert((pvaDst <> nil) and (pvaSrc.DimCount = pvaDst.DimCount) and (pvaSrc.ElementSize = pvaDst.ElementSize));

  // Swap two dimensions, bounds array stored reverse in memory of a SAFEARRAY
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;
  pvaDst.Bounds[0] := pvaSrc.Bounds[I];
  pvaDst.Bounds[I] := pvaSrc.Bounds[0];

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  SafeArrayAccessData(pvaDst, pDst);  // pvaDst^.LockCount/cLocks = 0, don't need to check hr
  Assert((pSrc <> nil) and (pDst <> nil));

  try
    // Transpose array by swapping raw data position of each element
    // Elements in SAFEARRAY stored as [col, row], so we need only swap to [row, col]
    // Unroll the case inside the loop, use constant
    case pvaDst.ElementSize of
      1:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
            Inc(PByte(pDst), 1);
          end;

      2:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
            Inc(PByte(pDst), 2);
          end;

      4:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
            Inc(PByte(pDst), 4);
          end;

      8:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
            Inc(PByte(pDst), 8);
          end;

      SizeOf(Variant):
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            PVarData(pDst)^ := PVarData(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Variant))^;
            Inc(PByte(pDst), SizeOf(Variant));
          end;

    else
      // DECIMAL or another types
      for I := 0 to LOldRows - 1 do
        for J := 0 to LOldCols - 1 do
        begin
          CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
          Inc(PByte(pDst), pvaDst.ElementSize);
        end;
    end;
  finally
    SafeArrayUnaccessData(pvaDst);
    SafeArrayUnaccessData(pvaSrc);
  end;

  // Trans data OK, clear old variant
  VariantClear(TVarData(vArrDst));

  // Change vArrDst to Variant of array, ignore varByRef
  TVarData(vArrDst).VType := varArray or (TVarData(vArrSrc).VType and varTypeMask);
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

// Code from RE SafeArrayCreate and SafeArrayCopy in oleaut32.dll
// Unsafe to uses.
function FastTransArrayDirect(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;
var
  hr: HRESULT;
  vt: TVarType;
  pSrc, pDst: Pointer;
  pvaSrc, pvaDst: PVarArray;
  I, J, LOldCols, LOldRows: NativeUInt;
  pInfTmp, pRecInfo, pszTmp: PNativeUInt;
begin
  if not VarIsArray(vArrSrc) then
    Exit(VAR_INVALIDARG);

  if @vArrSrc = @vArrDst then
    Exit(VAR_OK); // trans itself

  pvaSrc := VarArrayAsPSafeArray(vArrSrc);
  Assert(pvaSrc <> nil);

  if (pvaSrc.DimCount <> 2) then
    Exit(VAR_TYPEMISMATCH);

  hr := SafeArrayGetVarType(pvaSrc, vt);
  if Failed(hr) then
    Exit(hr);

  pvaDst := SafeArrayCreate(vt, 2, @pvaSrc.Bounds);
  if pvaDst = nil then
    Exit(VAR_OUTOFMEMORY);

  if (varUnknown = vt) or (varDispatch = vt) then
    PGUID(PByte(pvaDst) - SizeOf(TGUID))^ := PGUID(PByte(pvaSrc) - SizeOf(TGUID))^
  else if (varRecord = vt) then
  begin
    NativeUInt(pRecInfo) := PNativeUInt(PByte(pvaSrc) - SizeOf(Pointer))^;
    PNativeUInt(PByte(pvaDst) - SizeOf(Pointer))^ := NativeUInt(pRecInfo);
    IRecordInfo(pRecInfo)._AddRef();
  end;

  // Copy and trans data
  I := 1;
  LOldCols := pvaSrc.Bounds[0].ElementCount;
  LOldRows := pvaSrc.Bounds[I].ElementCount;

  hr := SafeArrayAccessData(pvaSrc, pSrc);
  if Failed(hr) then
  begin
    SafeArrayDestroy(pvaDst);
    Exit(hr);
  end;

  SafeArrayAccessData(pvaDst, pDst);
  Assert((pSrc <> nil) and (pDst <> nil));

  try
    case vt of
      varUnknown, varDispatch:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            NativeUInt(pInfTmp) := PNativeUInt(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Pointer))^;
            IUnknown(pInfTmp)._AddRef();
            PNativeUInt(pDst)^ := NativeUInt(pInfTmp);
            Inc(PByte(pDst), SizeOf(Pointer));
          end;

      varRecord:  // SizeOf(Record) = pvaDst/pvaSrc.ElementSize
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            IRecordInfo(pRecInfo).RecordCopy(PByte(pSrc) + (J * LOldRows + I) * pvaSrc.ElementSize, pDst);
            Inc(PByte(pDst), pvaDst.ElementSize);
          end;

      varOleStr:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            NativeUInt(pszTmp) := PNativeUInt(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Pointer))^;
            PPOleStr(pDst)^ := SysAllocStringByteLen(PAnsiChar(pszTmp), SysStringByteLen(PWideChar(pszTmp)));
            Inc(PByte(pDst), SizeOf(Pointer));
          end;

      varVariant:
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            VariantCopy(PVarData(pDst)^, PVarData(PByte(pSrc) + (J * LOldRows + I) * SizeOf(Variant))^);
            Inc(PByte(pDst), SizeOf(Variant));
          end;
    else
      case pvaDst.ElementSize of
        1:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PByte(pDst)^ := PByte(PByte(pSrc) + J * LOldRows + I)^;
              Inc(PByte(pDst), 1);
            end;

        2:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PWord(pDst)^ := PWord(PByte(pSrc) + (J * LOldRows + I) * 2)^;
              Inc(PByte(pDst), 2);
            end;

        4:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PUInt(pDst)^ := PUInt(PByte(pSrc) + (J * LOldRows + I) * 4)^;
              Inc(PByte(pDst), 4);
            end;

        8:
          for I := 0 to LOldRows - 1 do
            for J := 0 to LOldCols - 1 do
            begin
              PUInt64(pDst)^ := PUInt64(PByte(pSrc) + (J * LOldRows + I) * 8)^;
              Inc(PByte(pDst), 8);
            end;
      else
        // DECIMAL or another types
        for I := 0 to LOldRows - 1 do
          for J := 0 to LOldCols - 1 do
          begin
            CopyMemory(pDst, Pointer(PByte(pSrc) + (J * LOldRows + I) * pvaDst.ElementSize), pvaDst.ElementSize);
            Inc(PByte(pDst), pvaDst.ElementSize);
          end;
      end;
    end;
  finally
    SafeArrayUnaccessData(pvaDst);
    SafeArrayUnaccessData(pvaSrc);
  end;

  VariantClear(TVarData(vArrDst));

  TVarData(vArrDst).VType := varArray or vt;
  TVarData(vArrDst).VArray := pvaDst;

  Result := VAR_OK;
end;

exports
  FastSumArray,
  FastCopyArray,
  FastTransArrayByCopy,
  FastTransArrayDirect;

{$IFDEF TEST}
var
  vArrSrc, vArrDst: Variant;
  I, J: Integer;
  hr: HRESULT;
begin
  vArrSrc := VarArrayCreate([0, 2, 0, 4], varOleStr);
  for I := 0 to 2 do
    for J := 0 to 4 do
      vArrSrc[I, J] := I * 10 + J + 1;
  vArrSrc[2, 4] := 'Text';

  for I := 0 to 2 do
  begin
    for J := 0 to 4 do
      Write(vArrSrc[I, J]:8, ' ');
    WriteLn;
  end;

  hr := FastTransArrayDirect(vArrSrc, vArrDst);
  if Succeeded(hr) then
  begin
    WriteLn('Bound 1: ', VarArrayLowBound(vArrDst, 1), ' - ', VarArrayHighBound(vArrDst, 1));
    WriteLn('Bound 2: ', VarArrayLowBound(vArrDst, 2), ' - ', VarArrayHighBound(vArrDst, 2));
    for I := 0 to 4 do
    begin
      for J := 0 to 2 do
        Write(vArrDst[I, J]:8);
      WriteLn;
    end;
  end;
  ReadLn;
{$ENDIF}
end.
Chạy rất nhanh nhưng có 1 cái lỗi và 1 cái hỏi cách sử dụng

1/ Lỗi với hàm chuyển mảng là Array do ADODB lấy lên... code như sau + File Data ... Code Hàm chuyển Array sau Mạnh sử dụng tốt cho Mảng trên Sheet hay ADODB

Mã:
Private Sub Transpose_Data()
    Dim tmpArray ''() As Variant
    Dim tmpArray2 ''() As Variant
    Dim Cnn As Object, Rs As Object
    Dim strCon As String
    Dim ExcelPath As String
    Dim srtQry As String
    Rem ========== Khai bao mo ket noi
    Set Cnn = CreateObject("ADODB.Connection")
    Set Rs = CreateObject("ADODB.Recordset")
    ExcelPath = ThisWorkbook.Path & "\Data.xlsb"
    strCon = ("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & ExcelPath _
            & ";Extended Properties=""Excel 12.0 Xml;HDR=Yes;"";")
    Rem ========== Tuy chon Lay du lieu SQL
    Rem srtQry = "Select *" & "From [" & Data_Nhap$ & "]"
    srtQry = "SELECT * FROM [Data_Nhap$]"
    Cnn.Open strCon
    Set Rs = Cnn.Execute(srtQry)
    tmpArray = Rs.GetRows
    Cnn.Close
    Rem ==========
    Dim t, d As Double
    t = Timer
    Call FastTransArrayByCopy(tmpArray, tmpArray2)
  
    With Range("A2")
        .Resize(UBound(tmpArray2, 1), UBound(tmpArray2, 2)).ClearContents
        .Resize(UBound(tmpArray2, 1), UBound(tmpArray2, 2)).Value = tmpArray2
    End With
    Debug.Print "FastTransArrayByCopy ... CuAnh", vbTab & (Timer - t), "Result=", d
    Rem ========== Thuc hien chuyen mang 2dArray len Sheet
'    Call Transpose_Array(tmpArray, tmpArray2)   ''Chuyen Mang tmpArray Sang tmpArray2
'    With Range("A2")
'        .Resize(UBound(tmpArray2, 1), UBound(tmpArray2, 2)).ClearContents
'        .Resize(UBound(tmpArray2, 1), UBound(tmpArray2, 2)).Value = tmpArray2
'    End With
End Sub

Private Sub Transpose_Array(ByRef InputArr() As Variant, ByRef ReturnArray() As Variant)
    Rem Khai bao Dim tmpArray(), tmpArray2() As Variant     ''Tang Toc 50%
    Rem Su Dung Call Transpose_Array (tmpArray, tmpArray2)  ''Chuyen Mang tmpArray Sang tmpArray2
    Dim RowNdx As Long, ColNdx As Long
    Dim LB1 As Long, LB2 As Long
    Dim UB1 As Long, UB2 As Long
    LB1 = LBound(InputArr, 1)
    LB2 = LBound(InputArr, 2)
    UB1 = UBound(InputArr, 1)
    UB2 = UBound(InputArr, 2)
    ReDim ReturnArray(LB2 To UB2, LB1 To UB1)

    For RowNdx = LB2 To UB2
    For ColNdx = LB1 To UB1
        ReturnArray(RowNdx, ColNdx) = InputArr(ColNdx, RowNdx)
    Next ColNdx, RowNdx ''Viet gon lai Bo Next
    Erase InputArr
End Sub

2/ Cho Mạnh hỏi cách sử dụng hàm sau và khai báo nó từ VBA

Và tại sao ko viết nó chỉ 1 đối số như hàm Sum trên VBA cho nó thuận tiện hơn
Mã:
function FastSumArray(const vArr: Variant; var dblSum: Double): HRESULT; stdcall;

3/ Nếu được giải thích cho mạnh hiểu 1 chút về HRESULT ( kiểu trả về của hàm và cách sử dụng nó phù hợp )

4/ Hàm sau sử dụng rất tốt + chính xác cho các kiểu dữ liệu ( Arr trên Sheet + ADODB )
Mã:
function FastTransArrayDirect(const vArrSrc: Variant; var vArrDst: Variant): HRESULT; stdcall;

.....
Code của tuân cũng bị chuyển cái Array do ADODB lấy lên là đơ Excel .....
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
HRESULT chỉ là kiểu Long/LongInt thôi. Không có gì đặc biệt.
MS họ quy ước cho các hàm API COM/OLE phải trả về kiểu Long. Khi mọi thứ OK thì trả về 0 (S_OK) hay > 0. Còn âm thì là lỗi.
Delphi coder họ khai báo lại file source System.VarUtils.pas để dùng cho code Delphi và có comment:
// These equate to Window's constants but are renamed to less OS dependent
Các bạn download lại file đính kèm này, mình có kèm file Excel để test, 10 triệu cell.
Các bạn cứ bung ra 1 thư mục và test trên Excel của bạn, không cần phải quan tâm phải chép Dll nào vào chổ nào.
Trong code Aut__Open và Auto_Close mình đã làm sẵn cho các bạn việc đó, tự load đúng dll lên với Excel các bạn đang dùng.

Và các bạn chú ý. Khi hàm API đã khai báo là nhận biến Variant, tức là Variant đó trong ruột là array, nhưng các bạn lại cố truyền 1 array của Variant hay kiểu khác vào hàm API, thì VBA sẽ sinh ra 1 Variant tạm, có VarType là vtByRef or vtArray or vt của kiểu.

Kết quả test trên máy mình nhanh hơn code VBA khoảng 5-7 lần.
Thanks


1637573084123.png
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
HRESULT chỉ là kiểu Long/LongInt thôi. Không có gì đặc biệt.
MS họ quy ước cho các hàm API COM/OLE phải trả về kiểu Long. Khi mọi thứ OK thì trả về 0 (S_OK) hay > 0. Còn âm thì là lỗi.
Delphi coder họ khai báo lại file source System.VarUtils.pas để dùng cho code Delphi và có comment:
// These equate to Window's constants but are renamed to less OS dependent
Các bạn download lại file đính kèm này, mình có kèm file Excel để test, 10 triệu cell.
Các bạn cứ bung ra 1 thư mục và test trên Excel của bạn, không cần phải quan tâm phải chép Dll nào vào chổ nào.
Trong code Aut__Open và Auto_Close mình đã làm sẵn cho các bạn việc đó, tự load đúng dll lên với Excel các bạn đang dùng.

Và các bạn chú ý. Khi hàm API đã khai báo là nhận biến Variant, tức là Variant đó trong ruột là array, nhưng các bạn lại cố truyền 1 array của Variant hay kiểu khác vào hàm API, thì VBA sẽ sinh ra 1 Variant tạm, có VarType là vtByRef or vtArray or vt của kiểu.

Kết quả test trên máy mình nhanh hơn code VBA khoảng 5-7 lần.
Thanks


View attachment 269473
Mạnh mới test nhanh lấy cái Array ADO lên được ... sau đó lỗi thoát Excel luôn ... mai Rảnh mới xem chi tiết
 
Upvote 0
Uhm, có thể chết ở IUnknown, IDispatch hay IRecordInfo. 3 case này mình chưa test, debug.
Thử đổi qua hàm ByCopy xem có chết kg ?
 
Upvote 0
Kiến thức các anh chia sẻ thật tuyệt, em có tải về chạy test khi em chạy file thì tốc độ thật tuyệt. Nhưng hiện em test tại máy ảo vì khi chạy ứng dụng này trên máy tính cài các phần mềm diệt virus như kaspersky thì nó xóa sạch khi tiến hành built file
Mã:
Sự kiện: Đã xóa đối tượng
Người dùng: DESK6789\Admin
Kiểu người dùng: Người dùng hiện hoạt
Tên ứng dụng: WinRAR.exe
Đường dẫn ứng dụng: C:\Program Files\WinRAR
Thành phần: Chống virus cho tập tin
Mô tả kết quả: Đã xóa
Loại: Trojan
Tên: VHO:Trojan-Banker.Win32.Danabot.gen
Độ chính xác: Phân tích hành vi
Mức độ mối đe dọa: Cao
Loại đối tượng: Tập tin
Tên đối tượng: VBArray64.dll
1637596473284.png
 
Upvote 0
Trên máy Mạnh check có 2 em virus thôi ... Fix lại code đó là hết à ... code đó Mạnh nhìn cũng xin thua còn Fix ít nhất sau 10 năm nữa quá :p

1637627105134.png
 
Upvote 0
Không liên quan gì tới fix hay không fix code hết. AV của KAS và thằng kia trên VirusTotal nhận dạng nhầm các pattern của Delphi RTL trùng với 1 con virus cũng viết bằng Delphi nào đó, Banker.Win32.Danabot.gen của tụi châu Mỹ.

FastTransArrayDirect chạy tốt với ADO của bạn Mạnh. Nhưng FastTransArrayByCopy làm văng Excel, khi free BSTR ở oleaut32.dll
Bug ngay chổ SizeOf(Variant) - copy raw value. Kiểu Variant nhưng có varByRef và Pointer nên bị free 2 lần.
Bài tập cho các bạn fix đó. Dùng VariantCopy, chấp nhận chậm thêm.

Mình đóng code này ở đây, quay lại làm tiếp các project của mình còn dang dỡ.
 
Upvote 0
Không liên quan gì tới fix hay không fix code hết. AV của KAS và thằng kia trên VirusTotal nhận dạng nhầm các pattern của Delphi RTL trùng với 1 con virus cũng viết bằng Delphi nào đó, Banker.Win32.Danabot.gen của tụi châu Mỹ.

FastTransArrayDirect chạy tốt với ADO của bạn Mạnh. Nhưng FastTransArrayByCopy làm văng Excel, khi free BSTR ở oleaut32.dll
Bug ngay chổ SizeOf(Variant) - copy raw value. Kiểu Variant nhưng có varByRef và Pointer nên bị free 2 lần.
Bài tập cho các bạn fix đó. Dùng VariantCopy, chấp nhận chậm thêm.

Mình đóng code này ở đây, quay lại làm tiếp các project của mình còn dang dỡ.
Mạnh cảm ơn nhiều ... chắc dùng cái Hàm FastTransArrayDirect thôi
Còn cái kia lưu lại đó sau này trình code khá lên may ra sửa được ... còn giờ xin chịu thua 100/100
Ngay code VBA trên GPE này có code mạnh tải về xem tới lui mấy na9m sau mới hiểu ra đấy
Còn code đó đoán là ít nhất sau 10 na9m quá :p

Lưu vào Delphi luôn ... mỗi lần xem nó... nó nhắc nhở mình .... xem diết biết đâu sau này sẻ ngộ ra

1637631107494.png
 
Lần chỉnh sửa cuối:
Upvote 0
Uhm, để mình viết lại hàm rtcTypeName luôn, dùng pure Pointer to interface để tránh bug do các hàm internal về interface mà Delphi compiler tự động thêm vào.
Các hàm về string cho VBA, các bạn cần thêm hàm nào ?
Vừa xem lại hàm rtcInStr và rtcInStrRev của VB và VBA, đúng là chậm thiệt. MS coder check kỹ, chuyển kiểu, làm nhiều trường hợp quá.
Prototype của hàm rtcInStrRev:
Mã:
function rctInStrRev(BSTR bstrSource; BSTR bstrSearch; nPos: Integer; vbCompMode: Integer): Integer; stdcall; external "vba7.dll"; delayed;
 
Lần chỉnh sửa cuối:
Upvote 0
Ai biết cho Mạnh hỏi chút
1/ Khi viết 1 DLL = Delphi trong đó có trên 10 Unit + vô số code
2/ code trong đó ko thay đổi mọi cái y trang nhau ...
3/ Khi Buil DLL 32 bit và DLL 64 bit chỉ chuyển chế độ buil thôi
....
Vậy tại sao cái DLL 32 bít báo có vài Em virus ... còn cái DLL 64 bít thì ko có virus = tại sao = Cách xừ lý nó cho 32 bít
Xin cảm ơn
 
Upvote 0
Tiếp tục với 2 hàm xử lý string trong VBA, tốc độ nhanh hơn. Nhờ các bạn test giúp.

Hàm InStrA/W và InStrRevA/W. Đặc điểm của 4 hàm này là dùng các hàm API của Windows trong shlwapi.dll để thực hiện compare, search đúng string theo language, locale và support ignore case hay không.
Dĩ nhiên nó phải hy sinh chút về tốc độ chứ không như binary compare bình thường.

4 hàm này các bạn có thể dùng tham khảo như prototype cho các hàm xử lý string giữa VBA và Dll sau này theo nhu cầu của các bạn.

Về vấn đề giao tiếp giữa VBA string và Dll, các bạn xem kỹ khai báo hàm trong VBA và khai báo trong source Delphi của Dll. Và cả cách gọi hàm từ VBA, khi nào thì truyền là string, khi nào thì truyền = hàm VBA StrPtr. Cho nên các bạn thấy 1 hàm tại sao có 2 biến thể A và W: AnsiChar và WideChar.

Nói ra hết dài dòng nên mình hơi lười. Ai hỏi gì thì trả lời đấy thôi.
Quan trọng nhất 1 điều là khi truyền 1 string xuống 1 hàm API ở 1 Dll, dù ByVal hay ByRef aStr as String thì xuống tới Dll, ta luôn có 1 vùng nhớ là 1 kiểu BSTR, 4 byte trước vùng nhớ là tổng byte len của string, không tính 2 NULL char, và content của string là Ansi/MBCS, chứ không còn là Unicode nữa. Do VBA luôn convert string Unicode của ta trên VBA code thành kiểu BSTR ANSI trên trước khi truyền địa chỉ vùng nhớ đó xuống cho hàm Dll của ta.
Và ngay cả string ta trả về cho VBA, cũng phải trả về kiểu BSTR ANSI như trên, dù là qua var hay là return function Result. VBA tiếp tục convert ngược string ta trả về từ BSTR ANSI thành BSTR Unicode. Nên nếu ta trả về BSTR Unicode thì sẽ bị double Unicode, 1 ký tự thành 4 byte.
Ngược lại, nếu Dll của ta là COM, thì VBA và Office sẽ không thực hiện việc chuyển kiểu này, có Unicode sao thì truyền Unicode vậy.

Pointer, kiểu này kiểu kia gì thì cuối cùng chỉ là 1 con số trong memory thôi. Mình muốn suy diễn, ép kiểu, dùng kiểu nào, miễn compile ra được, sinh mã máy đúng thì được. Không quan trọng.
Mã máy nó không hề biết, không hề quan tâm tới PAnsiChar hay PWideChar, string hay WideString. Nó chỉ hiểu giá trị ABC trong ô nhớ XYZ.
Giờ mình code tiếp, code lại hàm rtcTypeName. Phát hiện ra mấy năm trước mình RE sai và thiếu ở kiểu SAFEARRAY và IRecordInfo.

Then en bé xì ga :D

Mã:
function InStrA(nStart: Integer; const pacSource, pacSearch: PAnsiChar; bIgnoreCase: Boolean): Integer; stdcall;
var
  lenSource, lenSearch: Integer;
  pPos: PAnsiChar;
begin
  if (pacSource = nil) or (pacSearch = nil) or (nStart <= 0) then
    Exit(0);

  lenSource := SysStringByteLen(PWideChar(pacSource));
  if (lenSource = 0) or (nStart > lenSource) then
    Exit(0);

  lenSearch := SysStringByteLen(PWideChar(pacSearch));
  if (lenSearch = 0) then
    Exit(nStart);

  if bIgnoreCase then
    pPos := StrStrIA(@pacSource[nStart - 1], pacSearch)
  else
    pPos := StrStrA(@pacSource[nStart - 1], pacSearch);

  if pPos = nil then
    Result := 0
  else
    Result := pPos - pacSource + 1; // VB/VBA string index from 1
end;

function InStrW(nStart: Integer; const pwcSource, pwcSearch: PWideChar; bIgnoreCase: Boolean): Integer; stdcall;
var
  lenSource, lenSearch: Integer;
  pPos: PWideChar;
begin
  if (pwcSource = nil) or (pwcSearch = nil) or (nStart <= 0) then
    Exit(0);

  lenSource := SysStringLen(pwcSource);
  if (lenSource = 0) or (nStart > lenSource) then
    Exit(0);

  lenSearch := SysStringLen(pwcSearch);
  if (lenSearch = 0) then
    Exit(nStart);

  if bIgnoreCase then
    pPos := StrStrIW(@pwcSource[nStart - 1], pwcSearch)
  else
    pPos := StrStrW(@pwcSource[nStart - 1], pwcSearch);

  if pPos = nil then
    Result := 0
  else
    Result := pPos - pwcSource + 1;
end;

function InStrRevA(const pacSource, pacSearch: PAnsiChar; bIgnoreCase: Boolean): Integer; stdcall;
var
  lenSource, lenSearch: Integer;
  pPos: PAnsiChar;
begin
  if (pacSource = nil) or (pacSearch = nil) then
    Exit(0);

  lenSource := SysStringByteLen(PWideChar(pacSource));
  if (lenSource = 0) then
    Exit(0);

  lenSearch := SysStringByteLen(PWideChar(pacSearch));
  if (lenSearch = 0) or (lenSearch > lenSource) then
    Exit(0);

  if bIgnoreCase then
    pPos := StrRStrIA(pacSource, nil, pacSearch)
  else
  begin
    var ach := pacSearch^;
    pPos := @pacSource[lenSource - lenSearch];
    while (pPos >= pacSource) do
      if (pPos^ = ach) and (StrCmpNA(pPos, pacSearch, lenSearch) = 0) then
        Break
      else
        Dec(pPos);
  end;

  if (pPos = nil) or (pPos < pacSource) then
    Result := 0
  else
    Result := pPos - pacSource + 1;
end;

function InStrRevW(const pwcSource, pwcSearch: PWideChar; bIgnoreCase: Boolean): Integer; stdcall;
var
  lenSource, lenSearch: Integer;
  pPos: PWideChar;
begin
  if (pwcSource = nil) or (pwcSearch = nil) then
    Exit(0);

  lenSource := SysStringLen(pwcSource);
  if (lenSource = 0) then
    Exit(0);

  lenSearch := SysStringLen(pwcSearch);
  if (lenSearch = 0) or (lenSearch > lenSource) then
    Exit(0);

  if bIgnoreCase then
    pPos := StrRStrIW(pwcSource, nil, pwcSearch)
  else
  begin
    var awh := pwcSearch^;
    pPos := @pwcSource[lenSource - lenSearch];
    while (pPos >= pwcSource) do
      if (pPos^ = awh) and (StrCmpNW(pPos, pwcSearch, lenSearch) = 0) then
        Break
      else
        Dec(pPos);
  end;

  if (pPos = nil) or (pPos < pwcSource) then
    Result := 0
  else
    Result := pPos - pwcSource + 1;
end;
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Một mẹo để ta có thể vượt qua chuyển bị ép convert VBA string từ BSTR Unicode qua BSTR Ansi là ta truyền thẳng địa chỉ của biến VBA string xuống Dll API của ta. Dll API của chúng ta sẽ có tham số là var xxx: PWideChar. Trên VB và VBA, string luôn là kiểu BSTR (từ VB5 trở đi, VB4 là khác)
Dưới API của chúng ta, chúng ta nhớ phải gọi SysFreeString với biến string truyền xuống để free memory cũ mà biến string đó giữ. Và nhớ chỉ dùng các hàm BSTR API để thao tác, hay ép kiểu qua WideString để dùng các hàm WideString của Delphi. Kiểu WideString của Delphi cũng chỉ là wrapper cho BSTR.
Tất cả bộ nhớ cấp phát, giải phóng, cache string BSTR đều do Windows quản lý, thực hiện ở oleaut32.dll. Nên chúng ta không lo vấn đề cấp 1 nơi giải phóng 1 nẽo.
Dưới đây là code minh họa cho các bạn tham khảo:

VBA code:
Mã:
Declare PtrSafe Function rtcTypeName Lib "VBArray64.dll" (ByRef var As Variant, ByVal strAddr As LongPtr) As Long
....
Sub TestRtcTypeName()
    Dim str As String
    Dim var As Variant
    Dim hr As Long

    str = "I am a Unicode string from VBA"
    Debug.Print str
 
    hr = rtcTypeName(var, VarPtr(str))
    Debug.Print str
End Sub

Delphi code:
Mã:
function rtcTypeName(const AVar: Variant; var pwcTypeName: PWideChar): HRESULT; stdcall;
begin
  SysFreeString(pwcTypeName);
  pwcTypeName := SysAllocString('Delphi DLL return a string to VBA');
  Result := VAR_OK;
end;

Và kết quả
1638005180449.png
Bài đã được tự động gộp:

Nhấn mạnh thêm cho 2 post trên, nếu ta vẫn khăng khăng truyền VBA string xuống và muốn nhận về bằng cách ByRef thì chúng ta sẽ bị double Unicode, như hình.
VBA code đổi, code Delphi vẫn không đổi, vẫn chạy đúng. Nhưng khi return về VBA thì bị VBA chụp đầu ép đổi double tiếp
Mã:
Declare PtrSafe Function rtcTypeName Lib "VBArray64.dll" (ByRef var As Variant, ByRef strAddr As String) As Long

1638005627978.png

Nếu chúng ta thấy khai báo kiểu ByRef str as String trên code VBA tiện hơn thì chúng ta phải đổi code ở Delphi lại. Thay vì dùng hàm SysAllocString để cấp phát và trả về 1 BSTR của kiểu Unicode thì chúng ta phải dùng hàm SysAllocStringByteLen để cấp phát và trả về 1 BSTR của kiểu ANSI.

1638007108035.png

VBA chụp đổi lại đúng cho ta.

1638007151859.png

Tùy các bạn. Nhưng nếu API trong Dll của chúng ta mà mang dùng cho các môi trường, ngôn ngữ khác thì phải cân nhắc là BSTR Unicode hay BSTR ANSI.

Then en bét xì ga.
Cuối tuần rồi, nghỉ sớm, nhậu :D
 

File đính kèm

  • 1638007091924.png
    1638007091924.png
    83.1 KB · Đọc: 5
Lần chỉnh sửa cuối:
Upvote 0
To @ThangCuAnh
Cho Mạnh nhờ chút đồng thời cũng là cho các bạn khác có cơ hội học Delphi Với ... nếu vấn đề Mạnh nêu ra sau đây mà khó quá thì bỏ qua ... không sao cả

Mạnh đang tập viết COM trên Delphi
1/ Tạo 1 Class là COM Object
2/ Tạo 1 class là COM Auto
...
làm cái hàm sum để test
khi build DLL xong chưa đăng ký ActiveX COM với Window thì từ VBE (VBA hay VB6) check Refenrences tới DLL đó vào được OK ... thấy các Class + Hàm vv

Nhưng ko sử dụng được vì class đó chưa đăng ký với Windows .... Nếu đăng ký DLL đó với Windows thì sử dụng tốt thì vấn đề nêu trên hỏi sẻ thừa ko cần thiết

Vậy Mạnh muốn nhờ có cách nào đó mà khi check Refenrences tới DLL đó xong là nó tự đăng ký luôn DLL đó với Windows không (Auto Register Class )

Có tìm google rất nhiều thì nó có dẫn tới link sau ... xem diết mà ko biết họ nói gì và sử dụng nó như thế nào


Hình sau là sau khi Build DLL check thì thấy hàm mà ko sử dụng được như mô tả phía trên

Refenrences_DLL.png
Nhìn thấy Class + Hàm

Class.png

Code Mẫu đính kèm
Xin cảm ơn
 

File đính kèm

Upvote 0
Bắt buộc phải đăng ký, hay tự viết 1 exe load dll nó lên, gọi hàm DllRegisterServer.
Link blog kia là nói về Attributes, không liên quan gì ở đây hết
 
Lần chỉnh sửa cuối:
Upvote 0
Bắt buộc phải đăng ký, hay tự viết 1 exe load dll nó lên, gọi hàm DllRegisterServer.
Link blog kia là nói về Attributes, không liên quan gì ở đây hết
Thôi bài kia khó quá bỏ qua :p... chỉ mạnh học code sau 1 chút

Chưa biết cách khai báo tương tác với Control bên ngoài Delphi sao cả mà đang loay hoay lúng túng :D
1/ Code sau sử dụng trên Delphi chạy rất tốt ... Lấy List File gán lên ListBox Delphi = ok
Mã:
procedure ListFileDir(Path: string; FileList: TStrings);
var
  SR: TSearchRec;
begin
  if FindFirst(Path + '*.*', faAnyFile, SR) = 0 then
  begin
    repeat
      if (SR.Attr <> faDirectory) then
      begin
        FileList.Add(SR.Name);
      end;
    until FindNext(SR) <> 0;
    FindClose(SR);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ListFileDir('C:\WINDOWS\', ListBox1.Items);
end;

2/ Vậy Mạnh muốn hỏi với code đó xuất API thì khai báo truyền cái ListBox từ VBA vào làm sao cho Hàm trong Delphi nó nhận và gán dữ liệu ngược vào listBox trên VBA

3/ Như mục số 2 ... nếu xuất nó qua COM Delphi thì khai báo và sử dụng sao ???

rất mong trợ giúp .... :D
 
Upvote 0
Hàm cuối cùng của VBArray32/64.dll mà mình đã hứa, hàm rtcTypeName. Nhờ các bạn test giúp, đặc biệt là kiểu record, tức là kiểu User Define Type/End Type trong VBA đó. Mấy cái IUnknown, IDispatch OK hết rồi.
Bỏ VBA lâu quá nên khai báo hoài 1 cái Type không được :D

Mã:
// Rewrite in Delphi code from RCE rtcTypeName function in VB/VBA: msvbvm60.dll and vbexxx.dll
//
function rtcTypeName(const AVar: TVarData; var pwcTypeName: PWideChar): HRESULT; stdcall;
const
  // Delphi until 10.4.2 only support vt to varUInt64 (VT_UI8)
  // Copy from VarTypeAsText function in System.Variants.pas, add VT_INT, VT_UINT and VT_RECORD
  CText: array [varEmpty..varRecord] of PWideChar = ('Empty', 'Null', 'Integer',
    'Long', 'Single', 'Double', 'Currency', 'Date', 'String', 'Object',
    'Error', 'Boolean', 'Variant', 'Unknown', 'Decimal', nil, 'ShortInt',
    'Byte', 'Word', 'Cardinal', 'Int64', 'UInt64', 'Signed Machine Int', 'Unsigned Machine Int',
    nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 'Record');
var
  hr: HRESULT;
  vt: TVarType;
  pwcRet: PWideChar;
  IsArray, IsByRef: Boolean;
  pRecInfo, pUnk, pDisp, pProvider, pTypeInfo: Pointer;
  pva: PVarArray;
begin
  // First, free old memory
  SysFreeString(pwcTypeName);
  pwcTypeName := nil;

  vt := AVar.VType and varTypeMask;
  IsArray := (AVar.VType and varArray) <> 0;
  IsByRef := (AVar.VType and varByRef) <> 0;

  // Validate vt
  if (vt > VT_UINT) and (vt < varRecord) or (vt = $0F) then
    Exit(VAR_BADVARTYPE);

  pwcRet := nil;
  if (vt = varRecord) then
  begin
    pRecInfo := nil;

    if not IsArray then
      pRecInfo := AVar.VRecord.RecInfo
    else
    begin
      if IsByRef then
        pva := PVarArray(AVar.VPointer^)
      else
        pva := AVar.VArray;

      if (pva <> nil) and (pva.Flags and FADF_RECORD <> 0) then
        pRecInfo := PPointer(PByte(pva) - SizeOf(Pointer))^;
    end;

    if (pRecInfo <> nil) then
      IRecordInfo(pRecInfo).GetName(pwcRet);
  end
  else if (vt = varUnknown) or (vt = varDispatch) then
  begin
    if IsArray then
      Exit(VAR_TYPEMISMATCH);

    if IsByRef then
      pUnk := PPointer(AVar.VUnknown)^
    else
      pUnk := AVar.VUnknown;

    if (pUnk = nil) then
    begin
      pwcRet := SysAllocString('Nothing');
      if (nil = pwcRet) then
        Exit(VAR_OUTOFMEMORY);
    end
    else
    begin
      pDisp := nil;
      pProvider := nil;
      pTypeInfo := nil;

      hr := IUnknown(pUnk).QueryInterface(IProvideClassInfo, pProvider);
      if Failed(hr) then
      begin
        hr := IUnknown(pUnk).QueryInterface(IDispatch, pDisp);
        if Succeeded(hr) then
          IDispatch(pDisp).GetTypeInfo(0, LOCALE_USER_DEFAULT, pTypeInfo);
      end
      else
        IProvideClassInfo(pProvider).GetClassInfo(ITypeInfo(pTypeInfo));

      if (pTypeInfo <> nil) then
      begin
        ITypeInfo(pTypeInfo).GetDocumentation(-1, PWideString(@pwcRet), nil, nil, nil);
        ITypeInfo(pTypeInfo)._Release;
        pTypeInfo := nil;
      end;

      // Release all interface pointers get by QueryInterface
      if (pProvider <> nil) then
      begin
        IProvideClassInfo(pProvider)._Release;
        pProvider := nil;
      end;

      if (pDisp <> nil) then
      begin
        IDispatch(pDisp)._Release;
        pDisp := nil;
      end;

      if (pwcRet <> nil) then
      begin
        if (pwcRet[0] = '_') then   // remove '_' char
        begin
          pwcTypeName := SysAllocStringLen(@pwcRet[1], SysStringLen(pwcRet) - 1);
          SysFreeString(pwcRet);
          if (nil = pwcTypeName) then
            Exit(VAR_OUTOFMEMORY);
        end
        else
          pwcTypeName := pwcRet;

        Exit(VAR_OK);
      end;
    end;
  end;

  // All above failed, get default, or vt not in [varRecord, varUnknown, varDispatch]
  if (pwcRet = nil) then
  begin
    pwcRet := SysAllocString(CText[vt]);
    if (nil = pwcRet) then
      Exit(VAR_OUTOFMEMORY);
  end;

  if IsArray then
  begin
    var oldLen := SysStringLen(pwcRet);
    pwcTypeName := SysAllocStringLen(pwcRet, oldLen + 2);
    if (pwcTypeName <> nil) then
    begin
      pwcTypeName[oldLen] := '(';
      pwcTypeName[oldLen + 1] := ')';
    end;
  end
  else
    pwcTypeName := SysAllocString(pwcRet);

  if (pwcRet <> nil) then
  begin
    SysFreeString(pwcRet);
    pwcRet := nil;
  end;

  if (nil <> pwcTypeName) then
    Result := VAR_OK
  else
    Result := VAR_OUTOFMEMORY;
end;

Nhờ admin xóa hết mấy cái file VBArray.zip ở các post trước trong chủ đề này.
Thanks
 

File đính kèm

Upvote 0
chạy nhanh... còn 1 hàm đơ Excel xong thoát
 
Upvote 0
Test hàm rtcTypeName thôi, hàm này quan trọng chạy đúng chứ không phải chạy nhanh.
Thử test với mọi kiểu dữ liệu của VBA và mọi thứ trên Excel.
Bị sai hay crash chổ nào thì gởi dùm file Excel test lên.
Thanks
 
Upvote 0
Upvote 0
Code với trả két ... chi phí mất thời gian quá
kể từ ngày biết viết cái Hàm kiểu 365 trên VBA ... sau gần 2 năm mới chuyển nó vào Delphi thành công
ko thể ngờ được nó đơn giản lắm chỉ có trên 10 dòng code thôi ... viết 1 cái Hàm chung nhất gán bất cứ 1 Array nào vào là ok

code ngắn lắm nó như sau.. Trong File có sử dụng code của @ThangCuAnh = Cảm ơn lắm lắm
Mã:
Rem https://youtu.be/XICP6C0yJQc
Declare PtrSafe Function ResizeArrayA Lib "MyLibrary64.dll" (ByVal arr As Variant) As Variant
Rem ==========
Function TransArray(ByVal rngIn As Range) As Variant
    Dim arr As Variant
    arr = rngIn.value
    TransArray = ResizeArrayA(arr)
End Function
Rem ==========
Function GetSQLArray(ByVal aPath As Variant, ByVal SQL As Variant) As Variant
    Dim hr As Long
    Dim arr As Variant, ArrDest As Variant
    Dim VB As New MyLibrary.VBLib                                   ''Check References ...MyLibrary64.dll
    arr = DataBaseToArray(aPath, SQL)
    Rem hr = FastTransArrayDirect(arr, ArrDest)                     ''Su dung ham API
    ArrDest = VB.TransposeArray(arr)                                ''Su dung COM
    If IsArray(ArrDest) Then GetSQLArray = ResizeArrayA(ArrDest)
End Function
Rem ========== Tao 1 mang voi so dong va cot tren Range
Function TaoArr(dong As Long, cot As Long) As Variant
    Rem Cu Phap: =TaoArr(10,10) Tao ra 10 dong x 10 cot
    Dim i As Long, J As Long
    ReDim arr(1 To dong, 1 To cot)
    For i = 1 To dong
        For J = 1 To cot
            arr(i, J) = i & "_" & J
        Next
    Next
    TaoArr = ResizeArrayA(arr)
End Function
Rem ==========
Video Demos
Liên kết: https://youtu.be/XICP6C0yJQc
 
Upvote 0
Code với trả két ... chi phí mất thời gian quá
kể từ ngày biết viết cái Hàm kiểu 365 trên VBA ... sau gần 2 năm mới chuyển nó vào Delphi thành công
ko thể ngờ được nó đơn giản lắm chỉ có trên 10 dòng code thôi ... viết 1 cái Hàm chung nhất gán bất cứ 1 Array nào vào là ok

code ngắn lắm nó như sau.. Trong File có sử dụng code của @ThangCuAnh = Cảm ơn lắm lắm
Mã:
Rem https://youtu.be/XICP6C0yJQc
Declare PtrSafe Function ResizeArrayA Lib "MyLibrary64.dll" (ByVal arr As Variant) As Variant
Rem ==========
Function TransArray(ByVal rngIn As Range) As Variant
    Dim arr As Variant
    arr = rngIn.value
    TransArray = ResizeArrayA(arr)
End Function
Rem ==========
Function GetSQLArray(ByVal aPath As Variant, ByVal SQL As Variant) As Variant
    Dim hr As Long
    Dim arr As Variant, ArrDest As Variant
    Dim VB As New MyLibrary.VBLib                                   ''Check References ...MyLibrary64.dll
    arr = DataBaseToArray(aPath, SQL)
    Rem hr = FastTransArrayDirect(arr, ArrDest)                     ''Su dung ham API
    ArrDest = VB.TransposeArray(arr)                                ''Su dung COM
    If IsArray(ArrDest) Then GetSQLArray = ResizeArrayA(ArrDest)
End Function
Rem ========== Tao 1 mang voi so dong va cot tren Range
Function TaoArr(dong As Long, cot As Long) As Variant
    Rem Cu Phap: =TaoArr(10,10) Tao ra 10 dong x 10 cot
    Dim i As Long, J As Long
    ReDim arr(1 To dong, 1 To cot)
    For i = 1 To dong
        For J = 1 To cot
            arr(i, J) = i & "_" & J
        Next
    Next
    TaoArr = ResizeArrayA(arr)
End Function
Rem ==========
Video Demos
Liên kết: https://youtu.be/XICP6C0yJQc
Chưa thấy hàm ResizeArrayA () :p
 
Upvote 0
Bạn nào cần các prototype của các hàm export từ VBA6/7 Dll mà code VBA ở trên Office cuối cùng phải gọi xuống tới, có thể tham khảo ở đây.
Vd Shell, CreateObject trên VBA sẽ xuống tới hàm nào: rtcShell, rtcCreateObject2... prototype của hàm ra sao....
Hy vọng giúp ích các bạn.
 
Upvote 0
Chưa thấy hàm ResizeArrayA () :p
đó là hàm độc nhất GPE này đấy ... tính tới thời điểm hiện tại ... nên thông cảm cho Mạnh ko phổ biến nó

ứng dụng nó viết Hàm cho Excel rất hay khi ta tính toán mọi cái xong gán vào 1 Array xong ResizeArrayA() ... là nó trả kết quả lên 1 Celss duy nhất trên Sheet ... nên rất nhẹ ... xóa 1 Cells gõ hàm đó là mất hết

kể cả khi ta lập trình viết hàm trên Office 365 thì nó cũng rất tiện ích
 
Upvote 0
đó là hàm độc nhất GPE này đấy ... tính tới thời điểm hiện tại ... nên thông cảm cho Mạnh ko phổ biến nó

ứng dụng nó viết Hàm cho Excel rất hay khi ta tính toán mọi cái xong gán vào 1 Array xong ResizeArrayA() ... là nó trả kết quả lên 1 Celss duy nhất trên Sheet ... nên rất nhẹ ... xóa 1 Cells gõ hàm đó là mất hết

kể cả khi ta lập trình viết hàm trên Office 365 thì nó cũng rất tiện ích

Theo tôi quan sát thì kết quả chạy hàm của bạn đang là Excel 365 chạy hàm tạo mảng. Tôi nhìn ra thế bởi quan sát:
1- Excel tự bao viền màu xanh tự động quanh mảng
2- Ô đầu tiên là ô duy nhất có công thức màu đen, các ô trong mảng trả về thì màu xám.
3- Nếu test thì chỉ cần nhập giá trị chen vào vùng mảng này thì công thức sẽ chỉ trả về một giá trị tại ô đầu tiên là #SPILL!
(Ba đặc điểm trên chỉ có Excel 365 làm được. Những hàm UDF viết trong VBA hay bất kể ngôn ngữ nào mà Excel nhận diện được mà trả về một array, chạy trên Excel 365 thì hiển nhiên chạy được như trên)

Tôi chưa thấy vai trò của hàm ResizeArrayA() làm gì tới cơ chế tạo mảng kiểu resize trên sheet, hay để mọi người thấy nó độc ra sao. Có thể bạn demo nhầm ví dụ? Bạn thử demo chạy nó trên Excel không phải 365, hay tốt hơn là gửi DLL lên để tôi và mọi người xem nó hoạt động như thế nào thì sẽ có nhận định đúng hơn.
 
Lần chỉnh sửa cuối:
Upvote 0
Theo tôi quan sát thì kết quả chạy hàm của bạn đang là Excel 365 chạy hàm tạo mảng. Tôi nhìn ra thế bởi quan sát:
1- Excel tự bao viền màu xanh tự động quanh mảng
2- Ô đầu tiên là ô duy nhất có công thức màu đen, các ô trong mảng trả về thì màu xám.
3- Nếu test thì chỉ cần nhập giá trị chen vào vùng mảng này thì công thức sẽ chỉ trả về một giá trị tại ô đầu tiên là #SPILL!
(Ba đặc điểm trên chỉ có Excel 365 làm được. Những hàm UDF viết trong VBA hay bất kể ngôn ngữ nào mà Excel nhận diện được mà trả về một array, chạy trên Excel 365 thì hiển nhiên chạy được như trên)

Tôi chưa thấy vai trò của hàm ResizeArrayA() làm gì tới cơ chế tạo mảng kiểu resize trên sheet, hay để mọi người thấy nó độc ra sao. Có thể bạn demo nhầm ví dụ? Bạn thử demo chạy nó trên Excel không phải 365, hay tốt hơn là gửi DLL lên để tôi và mọi người xem nó hoạt động như thế nào thì sẽ có nhận định đúng hơn.
1/ mục số 1
1.png
2/ Mục số 2
2.png

3/ Mục số 3 thử lỗi
3.png


4/ Video
 

File đính kèm

Upvote 0

Chính là 3 đặc điểm tôi nó đó là Excel 365 chứ không phải hàm ResizeArrayA() tạo ra như vậy. Thêm một quan sát. Viền màu xanh khi công thức trả về #SPILL! là đường đứt đoạn, còn không lỗi thì viền nét liên. Nên nếu bạn gửi DLL lên tôi xem nó hoạt động rao sao thì nhận định của tôi có thể đúng hơn với hàm của bạn.
 
Upvote 0
Chính là 3 đặc điểm tôi nó đó là Excel 365 chứ không phải hàm ResizeArrayA() tạo ra như vậy. Thêm một quan sát. Viền màu xanh khi công thức trả về #SPILL! là đường đứt đoạn, còn không lỗi thì viền nét liên. Nên nếu bạn gửi DLL lên tôi xem nó hoạt động rao sao thì nhận định của tôi có thể đúng hơn với hàm của bạn.
3 mục có hết rồi mà ... nếu ko có hàm ResizeArrayA() thì làm sao nó trả kết quả lên Sheet ???!!!
Mới thử hàm của Ms Excel 365 nó cũng như sau
1/ ...
1.PNG
2/ ...
2.PNG
3/ ...
3.PNG
 
Lần chỉnh sửa cuối:
Upvote 0
Bạn nào cần các prototype của các hàm export từ VBA6/7 Dll mà code VBA ở trên Office cuối cùng phải gọi xuống tới, có thể tham khảo ở đây.
Vd Shell, CreateObject trên VBA sẽ xuống tới hàm nào: rtcShell, rtcCreateObject2... prototype của hàm ra sao....
Hy vọng giúp ích các bạn.

Cảm ơn and vì sự kỳ công RE ra bộ hàm có prototype từ VBAxxx.dll.
Từ tài liệu C anh gửi đây
https://github.com/HongThatCong/VB_VBA_567/blob/main/VB_VBA_567.h

Em test hàm rtcInputBox thì đang gặp lỗi không biết ví sao

Theo khai báo C trong tài liêu của anh là

C:
#define VBAPI   __declspec(dllimport) __stdcall
VBAPI LPVARIANT     rtcInputBox(LPVARIANT pvarPrompt, LPVARIANT pvarTitle, LPVARIANT pvarDefault,
                                LPVARIANT pvarXPos, LPVARIANT pvarYPos,
                                LPVARIANT pvarHelpFile, LPVARIANT pvarContext);

Kiểu LPVARIANT em không thấy có định nghĩa, có thể là
C:
typedef  VARIANT* LPVARIANT;

Em port nó vào Delphi như sau:

SQL:
unit VBADef;

interface
uses
  Windows,
  ActiveX,
  Variants;

const
  VBADLL7 = 'C:\Program Files (x86)\Common Files\Microsoft Shared\VBA\VBA7\VBE7.DLL';

type
  LPVARIANT = ^TVariantArg;

function rtcInputBox1(const pvarPrompt, pvarTitle, pvarDefault,
                                pvarXPos, pvarYPos,
                                pvarHelpFile, pvarContext: LPVARIANT): LPVARIANT; stdcall;

function rtcInputBox2(pvarPrompt, pvarTitle, pvarDefault,
                                pvarXPos, pvarYPos,
                                pvarHelpFile, pvarContext: TVariantArg): TVariantArg; stdcall;

implementation

function rtcInputBox1; external VBADLL7 name 'rtcInputBox';
function rtcInputBox2; external VBADLL7 name 'rtcInputBox';

end.

Khi chạy thì gặp lỗi với cả hai cách khai báo rtcInputBox1, rtcInputBox2.

Khi port sang VBA thì cũng gặp lỗi.
C#:
Declare PtrSafe Function VBAInputBox Lib _
                        "C:\Program Files (x86)\Common Files\Microsoft Shared\VBA\VBA7\VBE7.DLL" _
                        Alias "rtcInputBox" _
                            (ByVal pvarPrompt As Variant, _
                             Optional ByVal pvarTitle As Variant = vbNullString, _
                             Optional ByVal pvarDefault As Variant = vbNullString, _
                             Optional ByVal pvarXPos As Variant = 0, _
                             Optional ByVal pvarYPos As Variant = 0, _
                             Optional ByVal pvarHelpFile As Variant = vbNullString, _
                             Optional ByVal pvarContext As Variant = 0) As Variant

Sub TestVBAMon()
    VBAInputBox "Tuan", "abc"
End Sub

Các lỗi này đều làm crash Excel. Anh kiểm tra xem cả hai trường hợp này vì sao lại bị vậy?
 
Upvote 0
3 mục có hết rồi mà ... nếu ko có hàm ResizeArrayA() thì làm sao nó trả kết quả lên Sheet ???!!!
Mới thử hàm của Ms Excel 365 nó cũng như sau
1/ ...
View attachment 270501
2/ ...
View attachment 270502
3/ ...
View attachment 270503

Trong các code của bạn nếu bỏ hàm ResizeArrayA() thì Excel 365 đã trả về mảng trên sheet rồi. Hy vọng nhìn thấy demo khác của bạn cho rõ ràng hơn.

 

File đính kèm

  • UDFinExcel365.gif
    UDFinExcel365.gif
    47.2 KB · Đọc: 1
Upvote 0
Trong các code của bạn nếu bỏ hàm ResizeArrayA() thì Excel 365 đã trả về mảng trên sheet rồi. Hy vọng nhìn thấy demo khác của bạn cho rõ ràng hơn.

á hiểu nói gì rồi ... thì 2 năm trước viết trên VBA chạy trên Office 2019 ... nó cũng ra như thế ... chỉ là ko có viền quanh vùng dữ liệu của hàm thôi .... có thể xem lại video 2 na9m trước

chỉ khác là ko có viền màu xanh bao quanh vùng dữ liệu của hàm thôi

1639973269319.png


mà thấy hơi kỳ lạ chút ... ít ngày nữa mới thử trên các bản Office sau
cũng là trên Excel 365 chạy code thuần VBA thì ko có viền màu xanh ... mà chạy code trong Delphi lại có ???!!!

khó hiểu + tò mò chút ... ít ngày nữa rảnh xem lại sau
 
Lần chỉnh sửa cuối:
Upvote 0
Uhm Tuân, 2 hàm rtcMsgBox và rtcInput mình chưa debug, mình có ghi trên github.
À, Tuân khai báo sai rồi, Optional trong VB/VBA không có nghĩa là truyền NULL/nil/0, mà là phải truyền vào 1 variant rõ ràng, có varType = VT_ERROR và scode = DISP_E_PARAMNOTFOUND (0x80020004)
Trong C, Windows SDK thì VARIANT = Variant/TVarData/OleVariant trong Delphi.
Khác cái tên, type thôi, chứ ở dưới, cấu trúc record/struct, trong memory đều là VARIANT hết. Miễn sao truyền đúng là được. Mã máy nó không biết Variant, variiếc gì đâu.
LPVARIANT = VARIANT * trong C, = PVarData, PVariant, POleVariant trong Delphi.

Trong code VBA của Tuân phải là ByRef nhen, không phải ByVal
Bạn @Kiều Mạnh khiêm tốn lại chút, chỉ là 1 cái hàm không có gì mà phải nổ là độc nhất, chưa bao giờ có, rồi không share, cả Dll cũng không úp. Bạn up dll đi, tôi cho bạn lại code hàm đó của bạn.
Bớt lại đi
 
Lần chỉnh sửa cuối:
Upvote 0
Uhm Tuân, 2 hàm rtcMsgBox và rtcInput mình chưa debug, mình có ghi trên github.
À, Tuân khai báo sai rồi, Optional trong VB/VBA không có nghĩa là truyền NULL/nil/0, mà là phải truyền vào 1 variant rõ ràng, có varType = VT_ERROR và scode = DISP_E_PARAMNOTFOUND (0x80020004)
Trong C, Windows SDK thì VARIANT = Variant/TVarData/OleVariant trong Delphi.
Khác cái tên, type thôi, chứ ở dưới, cấu trúc record/struct, trong memory đều là VARIANT hết. Miễn sao truyền đúng là được. Mã máy nó không biết Variant, variiếc gì đâu.
LPVARIANT = VARIANT * trong C, = PVarData, PVariant, POleVariant trong Delphi.

Trong code VBA của Tuân phải là ByRef nhen, không phải ByVal
Bạn @Kiều Mạnh khiêm tốn lại chút, chỉ là 1 cái hàm không có gì mà phải nổ là độc nhất, chưa bao giờ có, rồi không share, cả Dll cũng không úp. Bạn up dll đi, tôi cho bạn lại code hàm đó của bạn.
Bớt lại đi
chỉ là cái hàm thôi ... nhưng chưa thấy cái thứ 2 thì tạm keo nó là thế ... khi nào thấy cái thứ 2 thì keo lại nha
ko có gì mà nổ cả ... đó là sự thật ... ko tin lục hết GPE lên mà xem :D:p

mới thử lại trên Office thường ... ko có viền bao quanh màu xanh .... chỉ có vậy còn lại chạy tốt
còn nhiều thứ nữa rảnh mới xem lại sau

đồng ý là sau bài này sẻ bớt lại hehehehehe-0-0-0-
 
Lần chỉnh sửa cuối:
Upvote 0
Share code, public cho mọi người thì tốt, chứ đúng là share code cho cậu là đúng sai lầm.
Cậu chỉ biết đạo code, copy & paste code của người khác cho ra 1 đống tả pín lù, mà chả hiểu cái gì bên trong, ở dưới cả.
 
Upvote 0
Share code, public cho mọi người thì tốt, chứ đúng là share code cho cậu là đúng sai lầm.
Cậu chỉ biết đạo code, copy & paste code của người khác cho ra 1 đống tả pín lù, mà chả hiểu cái gì bên trong, ở dưới cả.
mạnh cảm ơn nhưng code đã chia sẻ nha. ... nhưng cái gì ra cái đó ... ko phải copy tất cả của ai đó hết đâu
nói vậy là thái quá đấy ... xem xét lại
Bài đã được tự động gộp:

thôi ko nói qua lại nũa mất lòng ... vẫn câu nói mấy na9m trước ... Mạnh rất ngưởng mộ nếu có đi qua bình dương alo nhậu vài lon
 
Lần chỉnh sửa cuối:
Upvote 0
Uhm Tuân, 2 hàm rtcMsgBox và rtcInput mình chưa debug, mình có ghi trên github.
À, Tuân khai báo sai rồi, Optional trong VB/VBA không có nghĩa là truyền NULL/nil/0, mà là phải truyền vào 1 variant rõ ràng, có varType = VT_ERROR và scode = DISP_E_PARAMNOTFOUND (0x80020004)
Trong C, Windows SDK thì VARIANT = Variant/TVarData/OleVariant trong Delphi.
Khác cái tên, type thôi, chứ ở dưới, cấu trúc record/struct, trong memory đều là VARIANT hết. Miễn sao truyền đúng là được. Mã máy nó không biết Variant, variiếc gì đâu.
LPVARIANT = VARIANT * trong C, = PVarData, PVariant, POleVariant trong Delphi.

Trong code VBA của Tuân phải là ByRef nhen, không phải ByVal
Bạn @Kiều Mạnh khiêm tốn lại chút, chỉ là 1 cái hàm không có gì mà phải nổ là độc nhất, chưa bao giờ có, rồi không share, cả Dll cũng không úp. Bạn up dll đi, tôi cho bạn lại code hàm đó của bạn.
Bớt lại đi

Em đã sửa khai báo API trong VBA chuyển ByVal về ByRef nhưng vẫn crash.
C#:
Declare PtrSafe Function VBAInputBox2 Lib _
                        "C:\Program Files (x86)\Common Files\Microsoft Shared\VBA\VBA7\VBE7.DLL" _
                        Alias "rtcInputBox" _
                            (pvarPrompt As Variant, _
                             pvarTitle As Variant, _
                             pvarDefault As Variant, _
                             pvarXPos As Variant, _
                             pvarYPos As Variant, _
                             pvarHelpFile As Variant, _
                             pvarContext As Variant) As Variant

Sub TestVBAMon()
    VBAInputBox2 "Tuan", "abc", "", 0, 0, "", 0
End Sub

Khi nào rảnh anh kiểm tra xem?
 
Upvote 0
Uhm Tuân, để rảnh mình xem, đang bận nhiều việc quá.
Có vài hàm, mà mình quên note lại mất, khi RE, là trên x64 tham số cuối là LPVARIANT, còn trên x86 là VARIANT truyền thẳng struct luôn.
 
Upvote 0
Mạnh xóa vì ko cần thiết
 
Lần chỉnh sửa cuối:
Upvote 0
Nếu bỏ hàm này ra thì trên excel 365 nó trả kết quả 1 ô hay sao anh
HÔM NAY RẢNH TÔI MỚI THỬ TEST THẤY NHƯ SAU

Test trên Excel 365
1/ năm ngoái khi ta viết hàm trên Excel 365 xong gõ trên Cells thì lỗi Or ko thấy gì cả

2/ Hình như Ms mới nâng cấp mọi thứ cho Excel 365 ... giờ trên VBA bạn viết cái gì bạn thích xong gán nó vào 1 Array xong gõ lên Cells là nó trả về 1 Array trên Cells như các hàm khác của Excel 365

3/ và các đặc tính như mô tả mấy bài trước ( 3 mục )

Test trên Office 2016
1/ mọi vấn đề gần như Excel 365

2/ chỉ khác là ko có viền bao quanh đứt nét như trên Office 365 khi ta bấm vào vùng dữ liệu của hàm

3/ khi ta bấm ra ngoài vùng dữ liệu của hàm thì cái viền đó hide đi

4/ chỉ khác duy nhất cái viền đó ... chắc mất ít nhất vài năm nữa để code cho 1 cái viền đó quá ===> không cần thiết nên tôi bỏ

Dự đoán là:
trong 1 vài năm tới thì các bản Office cũng sẻ có nhưng tính năng như Excel 365 thôi vì vậy tôi chốt lại tại đây ko mất thời gian nghiên cứu thêm nữa chỉ vì cái viền màu xanh

Bạn nên test mọi cái trên bản Office thường thì mới thấy hết được ... còn trên Excel 365 hiện tại thì gõ mọi cái nó trả về Sheet như mô tả phía trên
 
Upvote 0
Hàm BS_SQL, BS_VLOOKUP hay các hàm mảng của Add-in A-Tools chạy trả về mảng tự co giãn, có cơ chế chèn dòng và cột, nhiều báo cáo trong một sheet cùng co giãn. Lập trình trên Delphi từ năm 2007 và được hoàn thiện theo thời gian. A-Tools chạy từ Excel XP đến phiên bản Excel mới nhất hiện nay. Giới thiệu lại để các bạn biết, nếu có hướng nghiên cứu tạo hàm mảng thì tham khảo cơ chế hoạt động của nó.
Nếu tìm các bài viết về hàm BS_SQL tìm trên Youtube "Tạo báo cáo động trong Excel và Add-in A-Tools". Đây là phương pháp làm báo cáo với hàm BS_SQL mà công ty tôi đào tạo cho rất nhiều doanh nghiệp để ứng dụng thực tế nhiều năm nay.

(Nhiều công thức hàm BS_SQL, BS_VLOOKUP tự co giãn trong một sheet)

(Báo cáo với hàm BS_SQL phân nhóm theo nhiều cấp)

(Báo cáo với hàm BS_SQL tách cột tự động)
Download Add-in A-Tools
 
Lần chỉnh sửa cuối:
Upvote 0
có lẻ các bạn nên nâng cấp lên Office 365 mà sử dụng ... có nhiều hàm hay + công nghệ tốt hơn các bản Office thường vv
1/ khi trên các bản Office thường dùng hàm {=TenHam( value1, value2)} .... nếu dữ liệu nhiều nó rất chậm + ì ạch

vì thế nó mới để ra trên Office 365 cái hàm chỉ có trong 1 Cells và cái hàm to màu đen trên sẻ bỏ dần theo thời gian thôi

2/ nếu có khả năng thì nên viết như tôi mà dùng cho dù nó ko có viền màu xanh thì hàm nó cũng chỉ có trong 1 Cells duy nhất thôi

3/ tại sao nên dùng 365 vì nhiều vần đề khác có liên quan dữ liệu qua Internet + giải pháp công nghệ vvv .... vì nhiều lý do đó nên cố giắng kiếm bản 365 mà sử dụng ( đâu đó trên Internet nếu ai đó biết có thể xin được ... còn mua chắc trên 100 k thôi = 1 ly cafe ngon thôi)
...
.... thong thả nghiên cứu và có sự lựa chọn tốt nhất cho mình
 
Upvote 0
1/ khi trên các bản Office thường dùng hàm {=TenHam( value1, value2)} .... nếu dữ liệu nhiều nó rất chậm + ì ạch

vì thế nó mới để ra trên Office 365 cái hàm chỉ có trong 1 Cells và cái hàm to màu đen trên sẻ bỏ dần theo thời gian thôi

2/ nếu có khả năng thì nên viết như tôi mà dùng cho dù nó ko có viền màu xanh thì hàm nó cũng chỉ có trong 1 Cells duy nhất thôi
1. Excel đời cao họ đã tối ưu rất nhiều. Khi viết một hàm xử lý dữ liệu lớn thì tốc độ tối ưu nằm ở bên trong hàm là chính. Việc đổ dữ liệu ra Excel chỉ là khâu cuối cùng mà thôi. Viết hàm cho nhiều người dùng phải viết cho nhiều Excel khác nhau chạy được, trừ khi viết cho bản thân mình thì tự khoanh phạm vi của nó.

2. Tôi chưa thấy sản phẩm (hàm chạy như Excel 365) của bạn đưa ra để mọi người test, duy nhất cái clip bạn làm thì hoàn toàn là do bản thân Excel 365 tạo nên. Tôi đang chờ bạn đưa cái bạn viết lên đây để tôi và mọi người cùng test rồi nhận xét một thể mà chưa có? Vậy như tôi là như cái nào?
 
Upvote 0
đã nói rồi ko nói lại nữa ... Mạnh không sống trên từng dòng code và ko viết cho ai bao giờ ... chỉ vì công việc và đam mê viết chơi thế thôi .... còn gì đi nữa thì cộng đồng họ cũng sẻ biết và đánh giá thôi ...

ko nhắc lại Hàm kiểu 365 nữa vì biết rồi nói lại làm chi /*-*//*-*//*-*//*-*//*-*//*-*/-0-0-0--0-0-0--0-0-0--0-0-0--0-0-0--0-0-0-
 
Upvote 0
Ha ha ==]]]. Lại có kịch hay để xem.

Kiểu thứ Sáu máu chảy lên *** nhiều.

Bài #1363 người ta đăng vầy. Chứ đâu có cần ai nhận xét, oánh giá gì đâu chời?
Người ta làm được gì đó thì đăng lên giới thiệu vậy thôi, đâu có nhu cầu, đâu có ghi cần gì *** đó xyz đâu...
Người ta đâu có so sánh, so siếc gì đâu ta. Kỳ lạ thật.

1640320634572.png

1640320793103.png


(Tốc độ sửa bài nhanh ghê ta).
 
Upvote 0
đã nói rồi ko nói lại nữa ... Mạnh không sống trên từng dòng code và ko viết cho ai bao giờ ... chỉ vì công việc và đam mê viết chơi thế thôi .... còn gì đi nữa thì cộng đồng họ cũng sẻ biết và đánh giá thôi ...

ko nhắc lại Hàm kiểu 365 nữa vì biết rồi nói lại làm chi /*-*//*-*//*-*//*-*//*-*//*-*/-0-0-0--0-0-0--0-0-0--0-0-0--0-0-0--0-0-0-

Không chỉ bạn đâu mà bất kể ai khi nói ra mà không có sản phẩm để chứng minh thì tôi cũng sẽ đặt câu hỏi như vậy thôi. Thông cảm nhé.
Bài đã được tự động gộp:

Ha ha ==]]]. Lại có kịch hay để xem.

Kiểu thứ Sáu máu chảy lên *** nhiều.

Bài #1363 người ta đăng vầy. Chứ đâu có cần ai nhận xét, oánh giá gì đâu chời?
Người ta làm được gì đó thì đăng lên giới thiệu vậy thôi, đâu có nhu cầu, đâu có ghi cần gì *** đó xyz đâu...
Người ta đâu có so sánh, so siếc gì đâu ta. Kỳ lạ thật.

View attachment 270693

View attachment 270694


(Tốc độ sửa bài nhanh ghê ta).

Đây là topic học Delphi và cụ thể là viết DLL nhé. Muốn xem kịch thì ra nơi khác nhé :D .
Tôi nhấn mạnh lại. Chủ đề này là học Delphi, mọi người có thể hỏi, share kết quả , có mã nguồn hay sản phẩm để người khác test hay trải nghiệm. Như vậy mới phù hợp với chủ đề này.
 
Lần chỉnh sửa cuối:
Upvote 0
Chính xác. Diễn đàn là nơi các thành viên trao đổi, học tập hòa đồng, vui vẻ.
Còn viết kiểu như trên chối *** lắm.

1640322303517.png

Cũng kiểu ở bài #89.

Cái gì mà kêu người ta "tự thấy hứng thú là được rồi".

Vậy mới nói là có kịch hay và miễn phí để xem. Nơi nào có kịch hay và miễn phí (không ngăn cấm) thì vào xem thôi.
 
Upvote 0
Chính xác. Diễn đàn là nơi các thành viên trao đổi, học tập hòa đồng, vui vẻ.
Còn viết kiểu như trên chối *** lắm.

View attachment 270697

Cũng kiểu ở bài #89.

Cái gì mà kêu người ta "tự thấy hứng thú là được rồi".

Vậy mới nói là có kịch hay và miễn phí để xem. Nơi nào có kịch hay và miễn phí (không ngăn cấm) thì vào xem thôi.

Kiểu viết hàm trả về mảng như bạn Kiều Mạnh nói trong chủ để này, như cách bạn ấy mô tả là "chưa từng có trên GPE" và một số đặc tính giống như hàm Excel 365 thì tôi mới muốn hỏi để test. Còn hàm chỉ để trả về mảng thì trên GPE này nhiều rồi.

Vẫn còn muốn xem kịch thì xin mời đi nơi khác nhé. Đây là nơi chia sẻ và học tập.
 
Upvote 0
Tôi nghĩ thế này

cái thớt này vài năm nay và có trên 1000 bài viết các kiểu có liên quan và ko liên qua tè le ra hết rồi ...
Nếu nói thì hãy nói ngay từ đầu còn ko thì thôi ..... tại sao sau nhiều năm nay mới nói .............................
ai đó sẻ nghĩ gì + phán xét như thế nào ????????????????!!!!!!!!!!!!!!!!!!!!! thế thôi
 
Upvote 0
Chớt dở.
Chắc có vấn đề *** đọc và hiểu tiếng Việt xừ rồi.
Hoặc cũng có thể giả vờ không hiểu.
Thôi thôi.
(Mượn câu của anh phía trên)
Bớt bớt lại đi.
 
Upvote 0
Chớt dở.
Chắc có vấn đề *** đọc và hiểu tiếng Việt xừ rồi.
Hoặc cũng có thể giả vờ không hiểu.
Thôi thôi.
(Mượn câu của anh phía trên)
Bớt bớt lại đi.

Bạn hiểu được như vậy thì tốt hơn rồi đó. Thôi để cho những người muốn học và chia sẻ kiến thức liên quan đến chủ đề này tiếp tục nhé.
Xin cảm ơn!
 
Upvote 0
1/ Vui lòng xem lại bài số 12 link sau Úp ngày 19/04/2020 được viết trên Office 2016
Video
Liên kết: https://www.youtube.com/watch?v=8JeX9QWDQxQ&t=139s


2/ bài số 93 viết ngày 26/10/2020 viết trên Office 2016
...
còn nhiều video + Link úp trên GPE này .... hãy xem thật kỹ xem đó là Office 2016 hay 365

1640351337096.png


Không còn gì nói nữa ... Xin cảm ơn
 
Upvote 0

Bài viết mới nhất

Back
Top Bottom